Передача кода bash через среду (для docker-compose)

Я пытаюсь решить известную проблему с порядком загрузки контейнеров в docker-compose, поэтому я остановился на простом решении, имея сценарий для проверки, готова ли среда к запуску команды.

В этом заключается идея, что для контейнера определена некоторая переменная среды:

  • WAIT_COMMAND должен быть строкой, определяющей логический тест в сценарии sh, который должен возвращать логическое значение
  • START_CMD строка с командой для запуска в зависимости от результата WAIT_COOMAND, в противном случае повторите попытку, пока не будет достигнут LOOPS.
  • ПЕТЛИ, сколько раз пытаться
  • СОН, сколько спать между попытками

В этом примере я жду ответа от elasticSearch перед запуском моего приложения, которое зависит от данных ES для загрузки.

Следующий скрипт будет моим ENTRYPOINT в контейнере докеров.

Экспортированные вары ENV

WAIT_COMMAND="$(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) == 200"
LOOPS=3
START_CMD="python my_script_depending_on_elastic.py"
SLEEP=2

Имея указанные выше переменные ENV, скрипт должен дождаться, пока запрос ES не вернет код 200.

#!/bin/bash

is_ready() {
    eval $WAIT_COMMAND
}

# wait until is ready
i=0
while ! is_ready; do
    i=`expr $i + 1`
    if [ $i -ge $LOOPS ]; then
        echo "$(date) - still not ready, giving up"
        exit 1
    fi
    echo "$(date) - waiting to be ready"
    sleep $SLEEP
done

#start the script
exec $START_CMD

Проблема в том, что код не работает в строке функции is_ready, он не возвращает логическое значение, а пытается выполнить 200 как команду

# ./wait_elastic.sh 
./wait_elastic.sh: line 9: 200: command not found
Fri Jul  3 18:26:43 UTC 2015 - waiting to be ready
./wait_elastic.sh: line 9: 200: command not found
Fri Jul  3 18:26:45 UTC 2015 - waiting to be ready
./wait_elastic.sh: line 9: 200: command not found
Fri Jul  3 18:26:47 UTC 2015 - still not ready, giving up

Как я могу провести тест, чтобы проверить, в порядке ли ответ curl?

Как указать логическую тестовую команду как:

WAIT_COMMAND="$(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) == 200"

и как это оценить:

is_ready() {
    eval $WAIT_COMMAND
}

?


person Bruno Rocha - rochacbruno    schedule 03.07.2015    source источник
comment
BashFAQ № 50 - mywiki.wooledge.org/BashFAQ/050 - прямо на- точка.   -  person Charles Duffy    schedule 03.07.2015
comment
Кроме того, я бы также подумал об использовании современного математического синтаксиса: i=$(( i + 1 )); if (( i >= LOOPS )); и т.д. $(( )) присутствует в стандарте POSIX; (( )) - это расширение bash / ksh / zsh.   -  person Charles Duffy    schedule 03.07.2015


Ответы (3)


Этот ответ - плохая практика. Пожалуйста, рассмотрите подход, который вместо этого не включает eval.

wait_command='[ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]'
is_ready() {
  eval "$wait_command"
}

Отличия от исходного кода:

  • Фактически включает test синоним команды [ внутри оцениваемой переменной среды (предыдущая версия не была допустимым синтаксисом сравнения bash).
  • Переключается с == (который не является допустимым оператором в POSIX [) на = (что разрешено POSIX). См. стандарт для команды test.
  • Переключение с двойных кавычек на одинарные во время присваивания, так что curl не запускается до вызова eval.
  • Заключает в кавычки содержимое, переданное в eval, чтобы предотвратить разделение строк и расширение глобуса до того, как код достигнет команды eval.

Реализация этого в контексте docker-compose может выглядеть следующим образом:

app:
    command: docker/wait
    environment:
        - wait_command=[ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]
        - wait_loops=10
        - wait_sleep=30

... скриптом вроде следующего:

#!/bin/bash
s_ready() { eval "$wait_command"; }

# wait until is ready
i=0
while ! is_ready; do
    if (( ++i >= wait_loops )); then
        echo "$(date) - still not ready, giving up"
        exit 1
    fi
    echo "$(date) - waiting to be ready"
    sleep $wait_sleep
done
person Charles Duffy    schedule 03.07.2015
comment
Спасибо! Я знаю, что использовать eval - плохо, но пока команда docker-compose не решит проблему наличия параметра WAIT, я буду использовать его. Спасибо - person Bruno Rocha - rochacbruno; 03.07.2015
comment
Я изменил свой другой ответ, чтобы продемонстрировать представление экспортированной функции в YAML для использования docker-compose. Пожалуйста, примите это во внимание. - person Charles Duffy; 03.07.2015
comment
Я добавил эти примечания к вопросу - person Bruno Rocha - rochacbruno; 03.07.2015
comment
Действительно. (Кстати, в большинстве случаев редактирование вопросов для включения ответа здесь не одобряется; если принятый ответ недостаточно хорош, чтобы помочь кому-то еще с той же проблемой, общий подход состоит в том, чтобы либо отредактировать его, чтобы улучшить это или добавить другой ответ - и действительно, для человека, задавшего вопрос, рекомендуется добавить свой собственный ответ, если его реальное исправление существенно отличается от любого из других предложенных ответов. С точки зрения почему это приветствуется - идея состоит в том, что собственный ответ OP должен подвергаться голосованию за / против так же, как и любой другой). - person Charles Duffy; 03.07.2015
comment
ОК, скопировано из вопроса в этот ответ! - person Bruno Rocha - rochacbruno; 04.07.2015
comment
Принимаются части того, с правками. sh scriptname никогда не следует использовать со сценарием, начинающимся с #!/bin/bash, поскольку он запускает его с sh, а не bash, вопреки заявлению shebang относительно совместимости. Лучше установить разрешение на выполнение напрямую и позволить shebang управлять запуском. Кроме того, использование расширения .sh не является хорошей практикой - сценарии bash не являются sh сценариями, как указано выше; более того, команды в Unix по умолчанию не имеют расширений - вы запускаете ls, а не ls.elf или ls.coff. - person Charles Duffy; 04.07.2015
comment
Если бы это был достаточно новый выпуск bash, я бы также подумал о замене (медленного) вызова date на встроенный, а значит, гораздо более быстрый, printf '%(%c)T - waiting to be ready\n' -1. - person Charles Duffy; 04.07.2015
comment
... также можно использовать curl --fail и полностью отказаться от явного тестирования статуса HTTP. - person Charles Duffy; 04.07.2015

Если вы хотите сохранить код, не используйте скалярную переменную - используйте функцию; вот для чего они нужны.

Если вы хотите передать код через переменную среды, то ответ будет экспортированной функцией:

wait_command() {
  [ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]
}
export -f wait_command

... создает переменную среды, которая выглядит следующим образом (в современном, post-official-shellshock-patch bash):

BASH_FUNC_wait_command%%=() {  [ $(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]

... который вы можете просто запустить вот так, без eval:

wait_command

Повторим еще раз, более четко: Экспортированные функции - это просто переменные среды со специальными именами и значениями. Если вы создаете переменную среды, имя которой начинается с BASH_FUNC_ и заканчивается на %%, а значение начинается с () {, вы только что создали экспортированную функцию, которая будет доступна для запуска в оболочках, запущенных с ее помощью в среде.


Таким образом, в вашем .yml файле вы можете использовать что-то вроде этого:

environment: {BASH_FUNC_wait_command%%: '() {  [ $(curl --write-out %{http_code} --silent
    --output /dev/null http://elastic:9200/_cat/health?h=st) = 200 ]'}
person Charles Duffy    schedule 03.07.2015
comment
docker-compose позволяет мне экспортировать env_vars, я не уверен, что могу определить функцию внутри параметров docker-compose.yml - person Bruno Rocha - rochacbruno; 03.07.2015
comment
Вот моя точка зрения: экспортируемые функции - это не что иное, как переменные среды со специальными именами (начиная с BASH_FUNC_ и заканчивая %%) и значениями (начиная с () {). Если вы можете создать произвольную переменную среды, вы можете создать экспортированную функцию. - person Charles Duffy; 03.07.2015

Не используйте eval. Вместо этого просто получите статус

status=$(curl --write-out %{http_code} --silent --output /dev/null http://elastic:9200/_cat/health?h=st)"

затем проверьте это напрямую.

is_ready () {
    [[ $status -eq 200 ]]
}
person chepner    schedule 03.07.2015
comment
Мне нужно, чтобы тест был в WAIT_COMMAND в виде строки в переменной среды: WAIT_COMMAND = $ (curl --write-out% {http_code} --silent --output / dev / null elastic: 9200 / _cat / health? h = st) -eq 200 Как оценить этот код внутри функция is_ready? - person Bruno Rocha - rochacbruno; 03.07.2015
comment
@rochacbruno, это не переменная среды, это просто обычная переменная оболочки. И поскольку это не переменная среды и не встроенная оболочка, вам не следует называть ее заглавными буквами. - person Charles Duffy; 03.07.2015
comment
@CharlesDuffy Я знаю, но при экспорте с помощью docker-compose он будет доступен как ENV VAR - person Bruno Rocha - rochacbruno; 03.07.2015
comment
Ах! Это полезный контекст с точки зрения понимания почему вы хотите делать то, что предложили. (Не то чтобы этот подход был правильным - это то, для чего предназначены экспортируемые функции). - person Charles Duffy; 03.07.2015
comment
Я отредактировал вопрос, разделяющий VARS, чтобы было понятнее .. спасибо - person Bruno Rocha - rochacbruno; 03.07.2015