Dockerfile condition if else avec des arguments externes

131

J'ai dockerfile

FROM centos:7
ENV foo=42

alors je le construis

docker build -t my_docker .

et exécutez-le.

docker run -it -d  my_docker

Est-il possible de passer des arguments à partir de la ligne de commande et de l'utiliser avec if else dans Dockerfile? Je veux dire quelque chose comme

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

et construisez avec cet argument.

 docker build -t my_docker . --my_arg=42
nick_gabpe
la source
1
Cela devrait probablement être géré à partir d'un script de construction.
Snps
@ Зелёный qui est incorrect. Voir la réponse ci-dessous, cela peut être accompli avec --build-arg
devnul3
La réponse acceptée ne couvre pas la partie «condition sinon» de la question. Il serait préférable de le renommer en "Dockerfile avec des arguments externes" si la vérification des conditions ne signifiait pas être une exigence.
Ruslan Kabalin
@RuslanKabalin - la réponse acceptée a à la fois des clauses "alors" et "else". La seule différence est ce qui est testé dans "si condition". Pour le code montré en question: RUN if [ "$arg" == "42" ]; then ENV=TRUE; else ENV=FALSE; fi. Ou si l'argument peut être manquant: RUN if [ "x$arg" == "x42" ]; then ...
ToolmakerSteve

Réponses:

193

Cela n'a peut-être pas l'air aussi propre, mais vous pouvez avoir votre Dockerfile (conditionnel) comme suit:

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

puis construisez l'image comme:

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

ou

docker build -t my_docker .

Qasim Sarfraz
la source
20
Ne devrait-il pas être à la [ "$arg" = "x" ]place de [ "x$arg" = "x" ]?
Quannt
4
Le ternaire vérifie ici si un argument est fourni, plutôt que de vérifier un argument spécifique. Cela semblerait plus évident s'il était réécrit comme if [ $arg != "" ] ;mais je suis sûr qu'il y a des
pièges
21
if [[ -n "$arg" ]]true si un paramètre n'est pas vide, if [[ -z "$arg" ]]true si un paramètre est vide
acumartini
6
Comment écrire une instruction if multiligne?
Ashutosh Chamoli
12
@Quannt - non, voir Pourquoi les scripts shell ... [ "x$arg" = "x" ] on dirait qu'il compare deux chaînes entre guillemets, mais les guillemets sont supprimés; Cela maintient la syntaxe correcte. Après devis de décapage: Bon: if x = x ... Bad: if = . CEPENDANT, il existe de meilleures façons de vérifier l'existence d'un paramètre: Vérifiez l'existence des arguments d'entrée .
ToolmakerSteve
20

Pour une raison quelconque, la plupart des réponses ici ne m'ont pas aidé (peut-être est-ce lié à mon image FROM dans le Dockerfile)

J'ai donc préféré créer un bash scriptdans mon espace de travail combiné avec --build-argafin de gérer l'instruction if pendant la construction de Docker en vérifiant si l'argument est vide ou non

Script 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
....

Construction de Docker:

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

Remarque: cela ira à l'autre (faux) dans le script bash

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

Remarque: cela ira au if (true)

Modifier 1:

Après plusieurs essais, j'ai trouvé l' article suivant et celui- ci qui m'a aidé à comprendre 2 choses:

1) ARG avant FROM est en dehors de la construction

2) Le shell par défaut est / bin / sh, ce qui signifie que if else fonctionne un peu différemment dans la construction du docker. par exemple, vous n'avez besoin que d'un seul "=" au lieu de "==" pour comparer les chaînes.

Vous pouvez donc le faire à l'intérieur du Dockerfile

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

et dans le docker build:

docker build --pull -f "Dockerfile" --label "service_name=${SERVICE_NAME}" -t $SERVICE_NAME --build-arg argname=true .
Dsaydon
la source
17

Il existe une alternative intéressante aux solutions proposées, qui fonctionne avec un seul Dockerfile , ne nécessite qu'un seul appel à docker build par build conditionnel et évite le bash .

Solution:

Ce qui suit Dockerfilerésout ce problème. Copiez-collez-le et essayez-le vous-même.

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}"

Explication de Dockerfile:

Nous obtenons d'abord une baseimage ( centos:7dans votre cas) et la mettons dans sa propre scène. La basescène doit contenir les choses que vous voulez faire avant la condition. Après cela, nous avons encore deux étapes, représentant les branches de notre condition: branch-version-1et branch-version-2. Nous construisons les deux. La finalscène choisit l'une de ces étapes, en fonction de my_arg. Dockerfile conditionnel. Voilà.

Sortie lors de l'exécution:

(J'ai abrégé cela un peu ...)

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

Merci à Tõnis pour cette incroyable idée!

Utilisateur12547645
la source
Jusqu'à présent, la meilleure approche.
Felipe Desiderati
Je le pensais aussi, c'est pourquoi je l'ai posté ici. Spead the news @FelipeDesiderati
User12547645
1
C'est la meilleure solution de toutes celles présentées ici, car vous n'avez pas besoin d'utiliser des solutions de contournement avec scripts / bash, mais utilisez une méthode qui n'implique que la connaissance de Dockerfile. Très bonne réponse.
Akito
Que fait "ARG ENV"? pouvez-vous nous éclairer là-dessus? Merci.
Max
@Max merci de l'avoir signalé. Ce n'est pas nécessaire
User12547645
12

Utilisez simplement le binaire "test" pour ce faire. Vous devez également utiliser la commande noop ":" si vous ne souhaitez pas spécifier une condition "else", afin que docker ne s'arrête pas avec une erreur de valeur de retour différente de zéro.

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
la source
2
Idem RUN [ -z "$YOURVAR" ] && ... || :, je crois
OneCricketeer
4
Pour l'instruction suivante, si la variable var est définie, les deux échos sont exécutés. RUN test -z "$YOURVAR" || echo "var is set" && echo "var is not set"
Harshad Vyawahare
En effet. Rappelez-vous que ce ne sont pas des blocs conditionnels branchés, ils s'exécutent en série, placés &&devant ||:test ! -z "$YOURVAR" && echo "var is set" || echo "var is not set"
Brandt
5

Exactement comme d'autres l'ont dit, le script shell aiderait.

Juste un cas supplémentaire, à mon humble avis, il convient de mentionner (pour quelqu'un d'autre qui trébuche ici, à la recherche d'un cas plus facile), c'est le remplacement de l'environnement .

Les variables d'environnement (déclarées avec l' ENVinstruction) peuvent également être utilisées dans certaines instructions comme des variables à interpréter par le Dockerfile.

La ${variable_name}syntaxe prend également en charge quelques-uns des modificateurs bash standard comme spécifié ci-dessous:

  • ${variable:-word}indique que si variableest défini, le résultat sera cette valeur. Si variablen'est pas défini, alors wordsera le résultat.

  • ${variable:+word}indique que si variableest défini, alors wordsera le résultat, sinon le résultat est la chaîne vide.

Jing Li
la source
5

La réponse acceptée peut résoudre la question, mais si vous voulez des ifconditions multilignes dans le fichier docker, vous pouvez le faire en plaçant \à la fin de chaque ligne (comme vous le feriez dans un script shell) et en terminant chaque commande par ;. Vous pouvez même définir quelque chose comme set -euxla 1ère commande.

Exemple:

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

Dans ton cas:

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

Puis construisez avec:

docker build -t my_docker . --build-arg arg=42
Lucas Basquerotto
la source
4

Utilisation du script Bash et 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

Passer arg: 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

Sans argument: 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
la source