@@ -204,7 +204,7 @@ func (s *Spec) Compile(fs billy.Filesystem, devcontainerDir, scratchDir string,
204204 // We should make a best-effort attempt to find the user.
205205 // Features must be executed as root, so we need to swap back
206206 // to the running user afterwards.
207- params .User , err = UserFromDockerfile (params .DockerfileContent , params .BuildArgs )
207+ params .User , err = UserFromDockerfile (params .DockerfileContent , BuildArgsMap ( params .BuildArgs ) )
208208 if err != nil {
209209 return nil , fmt .Errorf ("user from dockerfile: %w" , err )
210210 }
@@ -306,9 +306,20 @@ func (s *Spec) compileFeatures(fs billy.Filesystem, devcontainerDir, scratchDir
306306 return strings .Join (lines , "\n " ), featureContexts , err
307307}
308308
309+ // BuildArgsMap converts a slice of "KEY=VALUE" strings to a map.
310+ func BuildArgsMap (buildArgs []string ) map [string ]string {
311+ m := make (map [string ]string , len (buildArgs ))
312+ for _ , arg := range buildArgs {
313+ if key , val , ok := strings .Cut (arg , "=" ); ok {
314+ m [key ] = val
315+ }
316+ }
317+ return m
318+ }
319+
309320// UserFromDockerfile inspects the contents of a provided Dockerfile
310321// and returns the user that will be used to run the container.
311- func UserFromDockerfile (dockerfileContent string , buildArgs [ ]string ) (user string , err error ) {
322+ func UserFromDockerfile (dockerfileContent string , buildArgs map [ string ]string ) (user string , err error ) {
312323 res , err := parser .Parse (strings .NewReader (dockerfileContent ))
313324 if err != nil {
314325 return "" , fmt .Errorf ("parse dockerfile: %w" , err )
@@ -319,27 +330,18 @@ func UserFromDockerfile(dockerfileContent string, buildArgs []string) (user stri
319330 lexer := shell .NewLex ('\\' )
320331 var argEnvs []string
321332 for _ , child := range res .AST .Children {
322- if strings .EqualFold (child .Value , "arg" ) {
323- arg := strings .TrimSpace (child .Original [len ("ARG " ):])
324- if strings .Contains (arg , "=" ) {
325- parts := strings .SplitN (arg , "=" , 2 )
326- key := parts [0 ]
327- val := parts [1 ]
328- // Allow buildArgs to override.
329- for _ , ba := range buildArgs {
330- if k , v , ok := strings .Cut (ba , "=" ); ok && k == key {
331- val = v
332- break
333- }
334- }
335- argEnvs = append (argEnvs , key + "=" + val )
336- } else {
337- for _ , ba := range buildArgs {
338- if k , v , ok := strings .Cut (ba , "=" ); ok && k == arg {
339- argEnvs = append (argEnvs , k + "=" + v )
340- break
341- }
342- }
333+ if ! strings .EqualFold (child .Value , "arg" ) || child .Next == nil {
334+ continue
335+ }
336+ if key , val , ok := strings .Cut (child .Next .Value , "=" ); ok {
337+ if override , has := buildArgs [key ]; has {
338+ val = override
339+ }
340+ argEnvs = append (argEnvs , key + "=" + val )
341+ } else {
342+ arg := child .Next .Value
343+ if val , has := buildArgs [arg ]; has {
344+ argEnvs = append (argEnvs , arg + "=" + val )
343345 }
344346 }
345347 }
@@ -362,9 +364,10 @@ func UserFromDockerfile(dockerfileContent string, buildArgs []string) (user stri
362364 case * instructions.Stage :
363365 // Substitute ARG values in the base image name.
364366 baseName , _ , err := lexer .ProcessWord (i .BaseName , shell .EnvsFromSlice (argEnvs ))
365- if err = = nil {
366- i .BaseName = baseName
367+ if err ! = nil {
368+ return "" , fmt . Errorf ( "processing ARG substitution in FROM %q: %w" , i .BaseName , err )
367369 }
370+ i .BaseName = baseName
368371 stages = append (stages , i )
369372 if i .Name != "" {
370373 stageNames [i .Name ] = i
@@ -423,7 +426,7 @@ func UserFromDockerfile(dockerfileContent string, buildArgs []string) (user stri
423426
424427// ImageFromDockerfile inspects the contents of a provided Dockerfile
425428// and returns the image that will be used to run the container.
426- func ImageFromDockerfile (dockerfileContent string , buildArgs [ ]string ) (name.Reference , error ) {
429+ func ImageFromDockerfile (dockerfileContent string , buildArgs map [ string ]string ) (name.Reference , error ) {
427430 lexer := shell .NewLex ('\\' )
428431 var args []string
429432 var imageRef string
@@ -433,31 +436,24 @@ func ImageFromDockerfile(dockerfileContent string, buildArgs []string) (name.Ref
433436 line := lines [i ]
434437 if arg , ok := strings .CutPrefix (line , "ARG " ); ok {
435438 arg = strings .TrimSpace (arg )
436- if strings .Contains (arg , "=" ) {
437- parts := strings .SplitN (arg , "=" , 2 )
438- key , _ , err := lexer .ProcessWord (parts [0 ], shell .EnvsFromSlice (args ))
439+ if key , val , ok := strings .Cut (arg , "=" ); ok {
440+ key , _ , err := lexer .ProcessWord (key , shell .EnvsFromSlice (args ))
439441 if err != nil {
440442 return nil , fmt .Errorf ("processing %q: %w" , line , err )
441443 }
442- val , _ , err := lexer .ProcessWord (parts [ 1 ] , shell .EnvsFromSlice (args ))
444+ val , _ , err := lexer .ProcessWord (val , shell .EnvsFromSlice (args ))
443445 if err != nil {
444446 return nil , fmt .Errorf ("processing %q: %w" , line , err )
445447 }
446448 // Allow buildArgs to override Dockerfile ARG defaults.
447- for _ , ba := range buildArgs {
448- if k , v , ok := strings .Cut (ba , "=" ); ok && k == key {
449- val = v
450- break
451- }
449+ if override , has := buildArgs [key ]; has {
450+ val = override
452451 }
453452 args = append (args , key + "=" + val )
454453 } else {
455454 // ARG without a default — look up in buildArgs.
456- for _ , ba := range buildArgs {
457- if k , v , ok := strings .Cut (ba , "=" ); ok && k == arg {
458- args = append (args , k + "=" + v )
459- break
460- }
455+ if val , has := buildArgs [arg ]; has {
456+ args = append (args , arg + "=" + val )
461457 }
462458 }
463459 continue
0 commit comments