Warunek Dockerfile if else z argumentami zewnętrznymi

131

Mam plik dockerfile

FROM centos:7
ENV foo=42

potem go buduję

docker build -t my_docker .

i uruchom go.

docker run -it -d  my_docker

Czy jest możliwe przekazywanie argumentów z wiersza poleceń i używanie ich z if else w Dockerfile? Mam na myśli coś takiego

FROM centos:7
if (my_arg==42)
     {ENV=TRUE}
else:
     {ENV=FALSE}

i buduj z tym argumentem.

 docker build -t my_docker . --my_arg=42
nick_gabpe
źródło
1
Prawdopodobnie powinno to być obsługiwane za pomocą skryptu kompilacji.
Snps
@ Зелёный to jest niepoprawne. Zobacz poniżej odpowiedź, można to osiągnąć za pomocą --build-arg
devnul3
Przyjęta odpowiedź nie obejmuje części pytania „jeśli inaczej”. Lepiej byłoby zmienić jego nazwę na „Dockerfile z zewnętrznymi argumentami”, gdyby sprawdzenie warunków nie oznaczało, że jest to wymaganie.
Ruslan Kabalin
@RuslanKabalin - zaakceptowana odpowiedź zawiera klauzule „then” i „else”. Jedyną różnicą jest to, co jest testowane w „warunku jeśli”. Dla kodu pokazano na pytanie: RUN if [ "$arg" == "42" ]; then ENV=TRUE; else ENV=FALSE; fi. Lub jeśli brakuje argumentu: RUN if [ "x$arg" == "x42" ]; then ...
ToolmakerSteve

Odpowiedzi:

193

Może nie wyglądać tak czysto, ale możesz mieć swój plik Dockerfile (warunkowy) w następujący sposób:

FROM centos:7
ARG arg
RUN if [ "x$arg" = "x" ] ; then echo Argument not provided ; else echo Argument is $arg ; fi

a następnie skompiluj obraz jako:

docker build -t my_docker . --build-arg arg=45

lub

docker build -t my_docker .

Qasim Sarfraz
źródło
20
Czy nie powinno być [ "$arg" = "x" ]zamiast tego [ "x$arg" = "x" ]?
Quannt,
4
Trójargument tutaj sprawdza, czy podano jakikolwiek argument, zamiast sprawdzania określonego argumentu. Wydawałoby się to bardziej oczywiste, gdyby przepisano jako, if [ $arg != "" ] ;ale jestem pewien, że jest pewna
pułapka, której
21
if [[ -n "$arg" ]]prawda, jeśli parametr nie jest pusty, if [[ -z "$arg" ]]prawda, jeśli parametr jest pusty
acumartini
6
Jak napisać wielowierszową instrukcję if?
Ashutosh Chamoli
12
@Quannt - nie, zobacz Dlaczego skrypty powłoki ... [ "x$arg" = "x" ] wygląda na to, że porównuje dwa cytowane ciągi, ale cudzysłowy są usuwane; Dzięki temu składnia jest poprawna. Po cytując złuszczaniu: Dobrze: if x = x ... Źle: if = . JEDNAK istnieją lepsze sposoby sprawdzania istnienia parametru: Sprawdź istnienie argumentów wejściowych .
ToolmakerSteve
20

Z jakiegoś powodu większość odpowiedzi mi nie pomogła (może jest to związane z moim obrazem FROM w pliku Dockerfile)

Więc wolałem utworzyć bash scriptw moim obszarze roboczym w połączeniu z --build-arg, aby obsłużyć instrukcję if podczas kompilacji Dockera, sprawdzając, czy argument jest pusty, czy nie

Skrypt Bash:

#!/bin/bash -x

if test -z $1 ; then 
    echo "The arg is empty"
    ....do something....
else 
    echo "The arg is not empty: $1"
    ....do something else....
fi

Dockerfile:

FROM ...
....
ARG arg
COPY bash.sh /tmp/  
RUN chmod u+x /tmp/bash.sh && /tmp/bash.sh $arg
....

Kompilacja Dockera:

docker build --pull -f "Dockerfile" -t $SERVICE_NAME --build-arg arg="yes" .

Uwaga: To przejdzie do else (false) w skrypcie bash

docker build --pull -f "Dockerfile" -t $SERVICE_NAME .

Uwaga: to przejdzie do if (true)

Edycja 1:

Po kilku próbach znalazłem następujący artykuł i ten jeden , który pomógł mi zrozumieć 2 rzeczy:

1) ARG przed FROM jest poza kompilacją

2) Domyślną powłoką jest / bin / sh, co oznacza, że ​​if else działa nieco inaczej w kompilacji dockera. na przykład potrzebujesz tylko jednego „=” zamiast „==”, aby porównać ciągi.

Możesz więc to zrobić wewnątrz Dockerfile

ARG argname=false   #default argument when not provided in the --build-arg
RUN if [ "$argname" = "false" ] ; then echo 'false'; else echo 'true'; fi

aw docker build:

docker build --pull -f "Dockerfile" --label "service_name=${SERVICE_NAME}" -t $SERVICE_NAME --build-arg argname=true .
dsaydon
źródło
17

Istnieje interesująca alternatywa dla proponowanych rozwiązań, która działa z pojedynczym plikiem Dockerfile , wymaga tylko jednego wywołania kompilacji dockera na kompilację warunkową i unika bash .

Rozwiązanie:

Poniższe informacje Dockerfilerozwiązują ten problem. Skopiuj, wklej i wypróbuj sam.

ARG my_arg

FROM centos:7 AS base
RUN echo "do stuff with the centos image"

FROM base AS branch-version-1
RUN echo "this is the stage that sets VAR=TRUE"
ENV VAR=TRUE

FROM base AS branch-version-2
RUN echo "this is the stage that sets VAR=FALSE"
ENV VAR=FALSE

FROM branch-version-${my_arg} AS final
RUN echo "VAR is equal to ${VAR}"

Wyjaśnienie Dockerfile:

Najpierw otrzymujemy baseobraz ( centos:7w twoim przypadku) i umieszczamy go na swoim własnym etapie. baseEtap powinien zawierać rzeczy, które chcesz zrobić przed chorobą. Następnie mamy jeszcze dwa etapy, reprezentujące gałęzie naszego stanu: branch-version-1i branch-version-2. Budujemy je obie. finalEtap niż wybiera jeden z tych etapów, na podstawie my_arg. Warunkowy plik Dockerfile. Proszę bardzo.

Wyjście podczas pracy:

(Skróciłem to trochę ...)

my_arg==2

docker build --build-arg my_arg=2 .
Step 1/12 : ARG my_arg
Step 2/12 : ARG ENV
Step 3/12 : FROM centos:7 AS base
Step 4/12 : RUN echo "do stuff with the centos image"
do stuff with the centos image
Step 5/12 : FROM base AS branch-version-1
Step 6/12 : RUN echo "this is the stage that sets VAR=TRUE"
this is the stage that sets VAR=TRUE
Step 7/12 : ENV VAR=TRUE
Step 8/12 : FROM base AS branch-version-2
Step 9/12 : RUN echo "this is the stage that sets VAR=FALSE"
this is the stage that sets VAR=FALSE
Step 10/12 : ENV VAR=FALSE
Step 11/12 : FROM branch-version-${my_arg}
Step 12/12 : RUN echo "VAR is equal to ${VAR}"
VAR is equal to FALSE

my_var==1

docker build --build-arg my_arg=1 .
...
Step 11/12 : FROM branch-version-${my_arg}
Step 12/12 : RUN echo "VAR is equal to ${VAR}"
VAR is equal to TRUE

Dzięki Tõnisowi za ten niesamowity pomysł!

Użytkownik12547645
źródło
Jak dotąd najlepsze podejście.
Felipe Desiderati
Też tak myślałem, dlatego zamieściłem to tutaj. Przeczytaj wiadomości @FelipeDesiderati
User12547645
1
Jest to najlepsze rozwiązanie ze wszystkich przedstawionych tutaj, ponieważ nie musisz używać obejść ze skryptami / bash, ale użyć sposobu, który wymaga tylko znajomości Dockerfile. Świetna odpowiedź.
Akito
Co robi „ARG ENV”? czy możesz rzucić na to trochę światła? Dzięki.
Max
@Max dzięki za wskazanie tego. Nie jest to konieczne
User12547645
12

Aby to zrobić, użyj bezpośrednio pliku binarnego „test”. Powinieneś także użyć polecenia noop „:”, jeśli nie chcesz określać warunku „else”, aby docker nie zatrzymał się z niezerowym błędem wartości zwracanej.

RUN test -z "$YOURVAR" || echo "var is set" && echo "var is not set"
RUN test -z "$YOURVAR" && echo "var is not set" || :
RUN test -z "$YOURVAR" || echo "var is set" && :
benjhess
źródło
2
To samo co RUN [ -z "$YOURVAR" ] && ... || :, jak sądzę
OneCricketeer
4
W poniższej instrukcji, jeśli zmienna jest ustawiona, wykonywane są oba echa. RUN test -z "$YOURVAR" || echo "var is set" && echo "var is not set"
Harshad Vyawahare
W rzeczy samej. Pamiętaj, że nie są to rozgałęzione bloki warunkowe, wykonywane są seryjnie, umieszczane &&przed ||:test ! -z "$YOURVAR" && echo "var is set" || echo "var is not set"
Brandt
5

Dokładnie tak, jak powiedzieli inni, pomoże skrypt powłoki.

Tylko dodatkowy przypadek, o którym warto wspomnieć o IMHO (dla kogoś, kto się tutaj natknie, szukając łatwiejszego przypadku), czyli Wymiana środowiska .

Zmienne środowiskowe (zadeklarowane za pomocą ENVinstrukcji) mogą być również używane w niektórych instrukcjach jako zmienne, które mają być interpretowane przez Dockerfile.

${variable_name}Składni obsługuje kilka standardowych modyfikatorów bash jak podano poniżej:

  • ${variable:-word}wskazuje, że jeśli variablejest ustawione, wynikiem będzie ta wartość. Jeśli variablenie jest ustawione, to wordbędzie wynik.

  • ${variable:+word}wskazuje, że jeśli variablejest ustawione, to wordbędzie wynikiem, w przeciwnym razie wynikiem jest pusty ciąg.

Jing Li
źródło
5

Odpowiedź Zaakceptowany może rozwiązać problem, ale jeśli chcesz multilinii ifwarunki w dockerfile można zrobić, że umieszczenie \na końcu każdej linii (podobny do tego, jak to zrobić w skrypcie powłoki) i kończąc każdym poleceniu ;. Możesz nawet zdefiniować coś takiego set -euxjak pierwsze polecenie.

Przykład:

RUN set -eux; \
  if [ -f /path/to/file ]; then \
    mv /path/to/file /dest; \
  fi; \
  if [ -d /path/to/dir ]; then \
    mv /path/to/dir /dest; \
  fi

W Twoim przypadku:

FROM centos:7
ARG arg
RUN if [ -z "$arg" ] ; then \
    echo Argument not provided; \
  else \
    echo Argument is $arg; \
  fi

Następnie zbuduj za pomocą:

docker build -t my_docker . --build-arg arg=42
Lucas Basquerotto
źródło
4

Korzystanie ze skryptu Bash i Alpine / Centos

Dockerfile

FROM alpine  #just change this to centos 

ARG MYARG=""
ENV E_MYARG=$MYARG

ADD . /tmp
RUN chmod +x /tmp/script.sh && /tmp/script.sh

script.sh

#!/usr/bin/env sh

if [ -z "$E_MYARG" ]; then
    echo "NO PARAM PASSED"
else
    echo $E_MYARG
fi

Przechodząc argument: docker build -t test --build-arg MYARG="this is a test" .

....
Step 5/5 : RUN chmod +x /tmp/script.sh && /tmp/script.sh
 ---> Running in 10b0e07e33fc
this is a test
Removing intermediate container 10b0e07e33fc
 ---> f6f085ffb284
Successfully built f6f085ffb284

Bez argumentu: docker build -t test .

....
Step 5/5 : RUN chmod +x /tmp/script.sh && /tmp/script.sh
 ---> Running in b89210b0cac0
NO PARAM PASSED
Removing intermediate container b89210b0cac0
....
Muhammad Soliman
źródło