@@ -327,15 +327,59 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
327327 return err
328328 }
329329
330+ // stash the cmd
330331 cmd := b .Config .Cmd
331- // set Cmd manually, this is special case only for Dockerfiles
332- b .Config .Cmd = config .Cmd
333332 runconfig .Merge (b .Config , config )
333+ // stash the config environment
334+ env := b .Config .Env
334335
335336 defer func (cmd * stringutils.StrSlice ) { b .Config .Cmd = cmd }(cmd )
337+ defer func (env []string ) { b .Config .Env = env }(env )
338+
339+ // derive the net build-time environment for this run. We let config
340+ // environment override the build time environment.
341+ // This means that we take the b.buildArgs list of env vars and remove
342+ // any of those variables that are defined as part of the container. In other
343+ // words, anything in b.Config.Env. What's left is the list of build-time env
344+ // vars that we need to add to each RUN command - note the list could be empty.
345+ //
346+ // We don't persist the build time environment with container's config
347+ // environment, but just sort and prepend it to the command string at time
348+ // of commit.
349+ // This helps with tracing back the image's actual environment at the time
350+ // of RUN, without leaking it to the final image. It also aids cache
351+ // lookup for same image built with same build time environment.
352+ cmdBuildEnv := []string {}
353+ configEnv := runconfig .ConvertKVStringsToMap (b .Config .Env )
354+ for key , val := range b .buildArgs {
355+ if ! b .isBuildArgAllowed (key ) {
356+ // skip build-args that are not in allowed list, meaning they have
357+ // not been defined by an "ARG" Dockerfile command yet.
358+ // This is an error condition but only if there is no "ARG" in the entire
359+ // Dockerfile, so we'll generate any necessary errors after we parsed
360+ // the entire file (see 'leftoverArgs' processing in evaluator.go )
361+ continue
362+ }
363+ if _ , ok := configEnv [key ]; ! ok {
364+ cmdBuildEnv = append (cmdBuildEnv , fmt .Sprintf ("%s=%s" , key , val ))
365+ }
366+ }
336367
337- logrus .Debugf ("[BUILDER] Command to be executed: %v" , b .Config .Cmd )
368+ // derive the command to use for probeCache() and to commit in this container.
369+ // Note that we only do this if there are any build-time env vars. Also, we
370+ // use the special argument "|#" at the start of the args array. This will
371+ // avoid conflicts with any RUN command since commands can not
372+ // start with | (vertical bar). The "#" (number of build envs) is there to
373+ // help ensure proper cache matches. We don't want a RUN command
374+ // that starts with "foo=abc" to be considered part of a build-time env var.
375+ saveCmd := config .Cmd
376+ if len (cmdBuildEnv ) > 0 {
377+ sort .Strings (cmdBuildEnv )
378+ tmpEnv := append ([]string {fmt .Sprintf ("|%d" , len (cmdBuildEnv ))}, cmdBuildEnv ... )
379+ saveCmd = stringutils .NewStrSlice (append (tmpEnv , saveCmd .Slice ()... )... )
380+ }
338381
382+ b .Config .Cmd = saveCmd
339383 hit , err := b .probeCache ()
340384 if err != nil {
341385 return err
@@ -344,6 +388,13 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
344388 return nil
345389 }
346390
391+ // set Cmd manually, this is special case only for Dockerfiles
392+ b .Config .Cmd = config .Cmd
393+ // set build-time environment for 'run'.
394+ b .Config .Env = append (b .Config .Env , cmdBuildEnv ... )
395+
396+ logrus .Debugf ("[BUILDER] Command to be executed: %v" , b .Config .Cmd )
397+
347398 c , err := b .create ()
348399 if err != nil {
349400 return err
@@ -358,6 +409,12 @@ func run(b *builder, args []string, attributes map[string]bool, original string)
358409 if err != nil {
359410 return err
360411 }
412+
413+ // revert to original config environment and set the command string to
414+ // have the build-time env vars in it (if any) so that future cache look-ups
415+ // properly match it.
416+ b .Config .Env = env
417+ b .Config .Cmd = saveCmd
361418 if err := b .commit (c .ID , cmd , "run" ); err != nil {
362419 return err
363420 }
@@ -557,3 +614,47 @@ func stopSignal(b *builder, args []string, attributes map[string]bool, original
557614 b .Config .StopSignal = sig
558615 return b .commit ("" , b .Config .Cmd , fmt .Sprintf ("STOPSIGNAL %v" , args ))
559616}
617+
618+ // ARG name[=value]
619+ //
620+ // Adds the variable foo to the trusted list of variables that can be passed
621+ // to builder using the --build-arg flag for expansion/subsitution or passing to 'run'.
622+ // Dockerfile author may optionally set a default value of this variable.
623+ func arg (b * builder , args []string , attributes map [string ]bool , original string ) error {
624+ if len (args ) != 1 {
625+ return fmt .Errorf ("ARG requires exactly one argument definition" )
626+ }
627+
628+ var (
629+ name string
630+ value string
631+ hasDefault bool
632+ )
633+
634+ arg := args [0 ]
635+ // 'arg' can just be a name or name-value pair. Note that this is different
636+ // from 'env' that handles the split of name and value at the parser level.
637+ // The reason for doing it differently for 'arg' is that we support just
638+ // defining an arg and not assign it a value (while 'env' always expects a
639+ // name-value pair). If possible, it will be good to harmonize the two.
640+ if strings .Contains (arg , "=" ) {
641+ parts := strings .SplitN (arg , "=" , 2 )
642+ name = parts [0 ]
643+ value = parts [1 ]
644+ hasDefault = true
645+ } else {
646+ name = arg
647+ hasDefault = false
648+ }
649+ // add the arg to allowed list of build-time args from this step on.
650+ b .allowedBuildArgs [name ] = true
651+
652+ // If there is a default value associated with this arg then add it to the
653+ // b.buildArgs if one is not already passed to the builder. The args passed
654+ // to builder override the defaut value of 'arg'.
655+ if _ , ok := b .buildArgs [name ]; ! ok && hasDefault {
656+ b .buildArgs [name ] = value
657+ }
658+
659+ return b .commit ("" , b .Config .Cmd , fmt .Sprintf ("ARG %s" , arg ))
660+ }
0 commit comments