@@ -71,7 +71,7 @@ type executor struct {
7171 cacheTTL time.Duration
7272 containerSuffix string
7373 logger * logrus.Logger
74- stages map [string ]* stageExecutor
74+ stages map [int ]* stageExecutor // Maps from stage indexes to their stageExecutors, serialized by stagesLock.
7575 store storage.Store
7676 contextDir string
7777 contextDirWritesAreDiscarded bool
@@ -115,17 +115,16 @@ type executor struct {
115115 layers bool
116116 saveStages bool
117117 stageLabels bool
118- stageImageIDs map [string ]string // Tracks image IDs for each stage (by name and position) for label references
118+ stageImageIDs map [int ]string // Tracks image IDs for output of each stage (indexed by position) for label references. Serialized by stagesLock.
119119 noHostname bool
120120 noHosts bool
121121 useCache bool
122122 removeIntermediateCtrs bool
123123 forceRmIntermediateCtrs bool
124- imageMap map [string ]string // Used to map images that we create to handle the AS construct.
124+ imageMap map [int ]string // Used to map from stage indexes to images that we create to be used in a later FROM... AS construct. Serialized by stagesLock .
125125 containerMap map [string ]* buildah.Builder // Used to map from image names to only-created-for-the-rootfs containers.
126- baseMap map [string ]struct {} // Holds the names of every base image, as given.
127- rootfsMap map [string ]struct {} // Holds the names of every stage whose rootfs is referenced in a COPY or ADD instruction.
128- afterDependency map [string ]string // Maps stage names to their --after dependency.
126+ baseMap map [string ]struct {} // Holds the set of names of every stage's base image, with ARGs resolved.
127+ rootfsMap map [int ]struct {} // Holds the set of indexes for every stage whose rootfs is referenced in a COPY or ADD instruction.
129128 blobDirectory string
130129 excludes []string
131130 groupAdd []string
@@ -148,8 +147,8 @@ type executor struct {
148147 cachePushDestinationLookupReferenceFunc libimage.LookupReferenceFunc
149148 ociDecryptConfig * encconfig.DecryptConfig
150149 lastError error
151- terminatedStage map [string ]error
152- stagesLock sync.Mutex
150+ terminatedStage map [int ]error // maps from stage indexes to error results, serialized by stagesLock
151+ stagesLock sync.Mutex // serializes stages, stageImageIDs, imageMap, terminatedStage
153152 stagesSemaphore * semaphore.Weighted
154153 logRusage bool
155154 rusageLogFile io.Writer
@@ -270,7 +269,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
270269 cacheTTL : options .CacheTTL ,
271270 containerSuffix : options .ContainerSuffix ,
272271 logger : logger ,
273- stages : make (map [string ]* stageExecutor ),
272+ stages : make (map [int ]* stageExecutor ),
274273 store : store ,
275274 contextDir : options .ContextDirectory ,
276275 contextDirWritesAreDiscarded : contextWritesDiscarded ,
@@ -318,16 +317,16 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
318317 layers : options .Layers ,
319318 saveStages : options .SaveStages ,
320319 stageLabels : options .StageLabels ,
321- stageImageIDs : make (map [string ]string ),
320+ stageImageIDs : make (map [int ]string ),
322321 noHostname : options .CommonBuildOpts .NoHostname ,
323322 noHosts : options .CommonBuildOpts .NoHosts ,
324323 useCache : ! options .NoCache ,
325324 removeIntermediateCtrs : options .RemoveIntermediateCtrs ,
326325 forceRmIntermediateCtrs : options .ForceRmIntermediateCtrs ,
327- imageMap : make (map [string ]string ),
326+ imageMap : make (map [int ]string ),
328327 containerMap : make (map [string ]* buildah.Builder ),
329328 baseMap : make (map [string ]struct {}),
330- rootfsMap : make (map [string ]struct {}),
329+ rootfsMap : make (map [int ]struct {}),
331330 blobDirectory : options .BlobDirectory ,
332331 unusedArgs : make (map [string ]struct {}),
333332 capabilities : capabilities ,
@@ -343,7 +342,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
343342 cachePushSourceLookupReferenceFunc : options .CachePushSourceLookupReferenceFunc ,
344343 cachePushDestinationLookupReferenceFunc : options .CachePushDestinationLookupReferenceFunc ,
345344 ociDecryptConfig : options .OciDecryptConfig ,
346- terminatedStage : make (map [string ]error ),
345+ terminatedStage : make (map [int ]error ),
347346 stagesSemaphore : options .JobSemaphore ,
348347 logRusage : options .LogRusage ,
349348 rusageLogFile : rusageLogFile ,
@@ -442,10 +441,7 @@ func (b *executor) startStage(ctx context.Context, stage *imagebuilder.Stage, st
442441 output : output ,
443442 stage : stage ,
444443 }
445- b .stages [stage .Name ] = stageExec
446- if idx := strconv .Itoa (stage .Position ); idx != stage .Name {
447- b .stages [idx ] = stageExec
448- }
444+ b .stages [stage .Position ] = stageExec
449445 return stageExec
450446}
451447
@@ -483,7 +479,7 @@ func (b *executor) stageIndex(nameOrIndex string, stages imagebuilder.Stages) (i
483479func (b * executor ) stageIndexUnlocked (nameOrIndex string , stages imagebuilder.Stages ) (int , * stageExecutor ) {
484480 for _ , otherStage := range slices .Backward (stages ) {
485481 if otherStage .Name == nameOrIndex || strconv .Itoa (otherStage .Position ) == nameOrIndex {
486- return otherStage .Position , b .stages [strconv . Itoa ( otherStage .Position ) ]
482+ return otherStage .Position , b .stages [otherStage .Position ]
487483 }
488484 }
489485 return - 1 , nil
@@ -505,7 +501,7 @@ func (b *executor) waitForStage(ctx context.Context, name string, stages imagebu
505501 }
506502
507503 b .stagesLock .Lock ()
508- terminationError , terminated := b .terminatedStage [name ]
504+ terminationError , terminated := b .terminatedStage [otherStageIndex ]
509505 b .stagesLock .Unlock ()
510506
511507 if terminationError != nil {
@@ -628,7 +624,7 @@ func (b *executor) buildStage(ctx context.Context, cleanupStages map[int]*stageE
628624 // Skip if stage has no instructions
629625 if b .saveStages && b .stageLabels && len (stage .Node .Children ) > 0 {
630626 // Wait for base stage if it references a previous stage
631- // This ensures stageImageIDs map is populated before buildStageLabelLine reads it
627+ // This ensures that stageImageIDs has the base stage's output image's ID before buildStageLabelLine reads it
632628 if isStage , err := b .waitForStage (ctx , base , stages [:stageIndex ]); isStage && err != nil {
633629 return "" , nil , false , fmt .Errorf ("waiting for base stage %s: %w" , base , err )
634630 }
@@ -693,13 +689,10 @@ func (b *executor) buildStage(ctx context.Context, cleanupStages map[int]*stageE
693689 return "" , nil , onlyBaseImage , err
694690 }
695691
696- // Store image ID for this stage so subsequent stages can reference it in labels
692+ // Store image ID for this stage's result so subsequent stages can reference it in labels
697693 if imageID != "" {
698694 b .stagesLock .Lock ()
699- if stage .Name != "" {
700- b .stageImageIDs [stage .Name ] = imageID
701- }
702- b .stageImageIDs [strconv .Itoa (stageIndex )] = imageID
695+ b .stageImageIDs [stageIndex ] = imageID
703696 b .stagesLock .Unlock ()
704697 }
705698
@@ -742,17 +735,12 @@ func (b *executor) buildStageLabelLine(stage *imagebuilder.Stage, base string, s
742735 // Check if base of the stage is another (previous) stage.
743736 // If yes, base is set as image ID of this stage.
744737 // If not original base name is set (pullspec).
745- for i , st := range stages {
746- if st .Name == base || strconv .Itoa (st .Position ) == base {
747- b .stagesLock .Lock ()
748- if imgID , ok := b .stageImageIDs [base ]; ok {
749- base = imgID
750- } else if imgID , ok := b .stageImageIDs [strconv .Itoa (i )]; ok {
751- base = imgID
752- }
753- b .stagesLock .Unlock ()
754- break
738+ if otherStageIndex , _ := b .stageIndex (base , stages ); otherStageIndex != - 1 {
739+ b .stagesLock .Lock ()
740+ if imgID , ok := b .stageImageIDs [otherStageIndex ]; ok {
741+ base = imgID
755742 }
743+ b .stagesLock .Unlock ()
756744 }
757745 labelLine += fmt .Sprintf (" %q=%q" , "io.buildah.stage.base" , base )
758746 return labelLine
@@ -892,7 +880,8 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
892880 // dependencyInfo is used later to work out if a particular stage is
893881 // needed by the target stage.
894882 dependencyMap := make (map [string ]* stageDependencyInfo )
895- // Initialize afterDependency map to track --after= dependency per stage
883+ // Initialize afterDependency map to track --after= dependency per stage, mapping
884+ // from a stage's index to the index of the one it's supposed to be built "after"
896885 afterDependency := make (map [int ]int )
897886 // Build maps of every named base image and every referenced stage root
898887 // filesystem. Individual stages can use them to determine whether or
@@ -940,7 +929,6 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
940929 logrus .Debugf ("base for stage %d: %q resolves to %q" , stageIndex , base , baseWithArg )
941930 // Check if selected base is not an additional
942931 // build context and if base is a valid stage
943- // add it to current stage's dependency tree.
944932 if _ , ok := b .additionalBuildContexts [baseWithArg ]; ! ok {
945933 if _ , ok := dependencyMap [baseWithArg ]; ok {
946934 // update current stage's dependency info
@@ -955,7 +943,7 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
955943 // only allow one --after flag per FROM for now; nothing necessarily
956944 // semantically wrong with multiple --after, but keeping it conservative until a
957945 // use case shows up
958- if _ , exists := b . afterDependency [stage .Name ]; exists {
946+ if _ , exists := afterDependency [stage .Position ]; exists {
959947 return "" , nil , fmt .Errorf ("FROM --after=%s: only one --after flag is allowed per FROM instruction" , after )
960948 }
961949 builtinArgs := argsMapToSlice (stage .Builder .BuiltinArgDefaults )
@@ -969,57 +957,57 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
969957 if err != nil {
970958 return "" , nil , fmt .Errorf ("while replacing arg variables with values for --after=%q: %w" , after , err )
971959 }
972- // If --after=<index> convert index to name
973- if index , err := strconv .Atoi (afterResolved ); err == nil && index >= 0 && index < stageIndex {
974- afterResolved = stages [index ].Name
975- }
976960 depInfo , ok := dependencyMap [afterResolved ]
977961 if ! ok {
978962 return "" , nil , fmt .Errorf ("FROM --after=%s: stage %q not found" , after , afterResolved )
979- } else if depInfo .Position >= stageIndex {
963+ }
964+ if depInfo .Position >= stageIndex {
980965 return "" , nil , fmt .Errorf ("FROM --after=%s: cannot depend on later stage %q" , after , afterResolved )
981966 }
982- // Mark the stage as a dep so we actually build it
983- currentStageInfo .Needs = append (currentStageInfo .Needs , afterResolved )
984- // And mark it on the stage executor itself so it knows to wait before even pulling
967+ // Mark the stage as a dep so we don't skip
968+ // building it
969+ currentStageInfo .Needs = append (currentStageInfo .Needs , strconv .Itoa (depInfo .Position ))
970+ // And mark it for the stage executor itself
971+ // so it knows to wait before even starting
985972 afterDependency [stage .Position ] = depInfo .Position
986973 logrus .Debugf ("stage %d: explicit dependency on %q(%d) via --after" , stageIndex , afterResolved , depInfo .Position )
987974 }
988975 }
989976 case "ADD" , "COPY" :
990977 for _ , flag := range child .Flags { // flags for this instruction
991- if after , ok := strings .CutPrefix (flag , "--from=" ); ok {
978+ if copyFrom , ok := strings .CutPrefix (flag , "--from=" ); ok {
992979 // Populate dependency tree and check
993980 // if following ADD or COPY needs any other
994981 // stage.
995- stageName := after
996982 builtinArgs := argsMapToSlice (stage .Builder .BuiltinArgDefaults )
997983 headingArgs := argsMapToSlice (stage .Builder .HeadingArgs )
998984 userArgs := argsMapToSlice (stage .Builder .Args )
999985 localScopeArgs := argsMapToSlice (stageLocalScopeArgs )
1000986 // ProcessWord uses first match; put highest priority first so
1001987 // --build-arg overrides stage ARG overrides header ARG overrides builtin.
1002988 userArgs = slices .Concat (userArgs , localScopeArgs , headingArgs , builtinArgs )
1003- baseWithArg , err := imagebuilder .ProcessWord (stageName , userArgs )
989+ copyFromWithArg , err := imagebuilder .ProcessWord (copyFrom , userArgs )
1004990 if err != nil {
1005- return "" , nil , fmt .Errorf ("while replacing arg variables with values for format %q: %w" , stageName , err )
1006- }
1007- if baseWithArg != "" {
1008- b .rootfsMap [baseWithArg ] = struct {}{}
1009- }
1010- logrus .Debugf ("stage %d name: %q resolves to %q" , stageIndex , stageName , baseWithArg )
1011- stageName = baseWithArg
1012- // If --from=<index> convert index to name
1013- if index , err := strconv .Atoi (stageName ); err == nil && index >= 0 && index < stageIndex {
1014- stageName = stages [index ].Name
991+ return "" , nil , fmt .Errorf ("while replacing arg variables with values for format %q: %w" , copyFrom , err )
1015992 }
1016- // Check if selected base is not an additional
1017- // build context and if base is a valid stage
1018- // add it to current stage's dependency tree.
1019- if _ , ok := b .additionalBuildContexts [stageName ]; ! ok {
1020- if _ , ok := dependencyMap [stageName ]; ok {
993+ logrus .Debugf ("stage %d name: %q resolves to %q" , stageIndex , copyFrom , copyFromWithArg )
994+ // Check if this "from" is a stage; add it
995+ // to the current stage's dependency tree
996+ // unless it's been replaced by an
997+ // additional context.
998+ if _ , ok := b .additionalBuildContexts [copyFromWithArg ]; ! ok {
999+ // Treat the "from" as a rootfs we need to preserve
1000+ if otherStageIndex , _ := b .stageIndex (copyFromWithArg , stages [:stageIndex ]); otherStageIndex != - 1 {
1001+ b .rootfsMap [otherStageIndex ] = struct {}{}
10211002 // update current stage's dependency info
1022- currentStageInfo .Needs = append (currentStageInfo .Needs , stageName )
1003+ depInfo , ok := dependencyMap [copyFromWithArg ]
1004+ if ! ok {
1005+ return "" , nil , fmt .Errorf ("COPY --from=%s: stage %q not found" , copyFrom , copyFromWithArg )
1006+ }
1007+ if depInfo .Position >= stageIndex {
1008+ return "" , nil , fmt .Errorf ("COPY --from=%s: cannot depend on later stage %q" , copyFrom , copyFromWithArg )
1009+ }
1010+ currentStageInfo .Needs = append (currentStageInfo .Needs , strconv .Itoa (otherStageIndex ))
10231011 }
10241012 }
10251013 }
@@ -1041,11 +1029,9 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
10411029 // if it is using `--mount` and `from=` field is set
10421030 // and `from=` points to a stage consider it in
10431031 // dependency calculation.
1044- if strings .HasPrefix (flag , "--mount=" ) && strings .Contains (flag , "from" ) {
1045- mountFlags := strings .TrimPrefix (flag , "--mount=" )
1046- fields := strings .SplitSeq (mountFlags , "," )
1047- for field := range fields {
1048- if mountFrom , hasFrom := strings .CutPrefix (field , "from=" ); hasFrom {
1032+ if mountFlags , ok := strings .CutPrefix (flag , "--mount=" ); ok {
1033+ for field := range strings .SplitSeq (mountFlags , "," ) {
1034+ if mountFrom , ok := strings .CutPrefix (field , "from=" ); ok {
10491035 builtinArgs := argsMapToSlice (stage .Builder .BuiltinArgDefaults )
10501036 headingArgs := argsMapToSlice (stage .Builder .HeadingArgs )
10511037 userArgs := argsMapToSlice (stage .Builder .Args )
@@ -1057,15 +1043,15 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
10571043 if err != nil {
10581044 return "" , nil , fmt .Errorf ("while replacing arg variables with values for format %q: %w" , mountFrom , err )
10591045 }
1060- // Check if this base is a stage if yes
1046+ // Check if this "from" is a stage; if yes
10611047 // add base to current stage's dependency tree
10621048 // unless it's been replaced by an additional context.
10631049 if _ , ok := b .additionalBuildContexts [mountFromWithArg ]; ! ok {
1064- // Treat from as a rootfs we need to preserve
1065- b . rootfsMap [ mountFromWithArg ] = struct {}{}
1066- if _ , ok := dependencyMap [ mountFromWithArg ]; ok {
1050+ // Treat the " from" as a rootfs we need to preserve
1051+ if mountStageIndex , _ := b . stageIndex ( mountFromWithArg , stages [: stageIndex ]); mountStageIndex != - 1 {
1052+ b . rootfsMap [ mountStageIndex ] = struct {}{}
10671053 // update current stage's dependency info
1068- currentStageInfo .Needs = append (currentStageInfo .Needs , mountFromWithArg )
1054+ currentStageInfo .Needs = append (currentStageInfo .Needs , strconv . Itoa ( mountStageIndex ) )
10691055 }
10701056 }
10711057 }
@@ -1076,10 +1062,8 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
10761062 }
10771063 node = node .Next // next line
10781064 }
1079- // Last stage is always target stage.
1080- // Since last/target stage is processed
1081- // let's calculate dependency map of stages
1082- // so we can mark stages which can be skipped.
1065+ // Last stage is always target stage. Since last/target stage is processed let's
1066+ // calculate dependency map of stages so we can mark stages which can be skipped.
10831067 if stage .Position == (len (stages ) - 1 ) {
10841068 markDependencyStagesForTarget (dependencyMap , stage .Name )
10851069 }
@@ -1183,8 +1167,7 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
11831167 stage := stages [r .Index ]
11841168
11851169 b .stagesLock .Lock ()
1186- b .terminatedStage [stage .Name ] = r .Error
1187- b .terminatedStage [strconv .Itoa (stage .Position )] = r .Error
1170+ b .terminatedStage [stage .Position ] = r .Error
11881171
11891172 if r .Error != nil {
11901173 b .stagesLock .Unlock ()
@@ -1195,7 +1178,7 @@ func (b *executor) Build(ctx context.Context, stages imagebuilder.Stages) (image
11951178 // If this is an intermediate stage, make a note of the ID, so
11961179 // that we can look it up later.
11971180 if r .Index < len (stages )- 1 && r .ImageID != "" {
1198- b .imageMap [stage .Name ] = r .ImageID
1181+ b .imageMap [stage .Position ] = r .ImageID
11991182 // We're not populating the cache with intermediate
12001183 // images, so add this one to the list of images that
12011184 // we'll remove later.
0 commit comments