Skip to content

Inherit arguments between stages #5845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions imagebuildah/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"os"
"sort"
"strconv"
Expand Down Expand Up @@ -164,6 +165,7 @@ type Executor struct {
compatVolumes types.OptionalBool
compatScratchConfig types.OptionalBool
noPivotRoot bool
argResult map[string]map[string]string
}

type imageTypeAndHistoryAndDiffIDs struct {
Expand Down Expand Up @@ -324,6 +326,7 @@ func newExecutor(logger *logrus.Logger, logPrefix string, store storage.Store, o
compatVolumes: options.CompatVolumes,
compatScratchConfig: options.CompatScratchConfig,
noPivotRoot: options.NoPivotRoot,
argResult: make(map[string]map[string]string),
}
if exec.err == nil {
exec.err = os.Stderr
Expand Down Expand Up @@ -413,7 +416,7 @@ func (b *Executor) resolveNameToImageRef(output string) (types.ImageReference, e
// that the specified stage has finished. If there is no stage defined by that
// name, then it will return (false, nil). If there is a stage defined by that
// name, it will return true along with any error it encounters.
func (b *Executor) waitForStage(ctx context.Context, name string, stages imagebuilder.Stages) (bool, error) {
func (b *Executor) waitForStage(ctx context.Context, name string, stages imagebuilder.Stages) (bool, map[string]string, error) {
found := false
for _, otherStage := range stages {
if otherStage.Name == name || strconv.Itoa(otherStage.Position) == name {
Expand All @@ -422,32 +425,41 @@ func (b *Executor) waitForStage(ctx context.Context, name string, stages imagebu
}
}
if !found {
return false, nil
return false, nil, nil
}
for {
if b.lastError != nil {
return true, b.lastError
return true, nil, b.lastError
}

b.stagesLock.Lock()
terminationError, terminated := b.terminatedStage[name]
args := b.argResult[name]
b.stagesLock.Unlock()

if terminationError != nil {
return false, terminationError
return false, nil, terminationError
}
if terminated {
return true, nil
return true, nil, nil
}

b.stagesSemaphore.Release(1)
time.Sleep(time.Millisecond * 10)
if err := b.stagesSemaphore.Acquire(ctx, 1); err != nil {
return true, fmt.Errorf("reacquiring job semaphore: %w", err)
return true, args, fmt.Errorf("reacquiring job semaphore: %w", err)
}
}
}

// freezeArgs stores a copy of the argument map for further use in dependent stages
func (b *Executor) freezeArgs(name string, args map[string]string) {
copiedArgs := maps.Clone(args)
b.stagesLock.Lock()
b.argResult[name] = copiedArgs
b.stagesLock.Unlock()
}

// getImageTypeAndHistoryAndDiffIDs returns the manifest type, history, and diff IDs list of imageID.
func (b *Executor) getImageTypeAndHistoryAndDiffIDs(ctx context.Context, imageID string) (string, []v1.History, []digest.Digest, error) {
b.imageInfoLock.Lock()
Expand Down
18 changes: 14 additions & 4 deletions imagebuildah/stage_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"maps"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -512,7 +513,7 @@ func (s *StageExecutor) performCopy(excludes []string, copies ...imagebuilder.Co
}
}
if additionalBuildContext == nil {
if isStage, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil {
if isStage, _, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil {
return err
}
if other, ok := s.executor.stages[from]; ok && other.index < s.index {
Expand Down Expand Up @@ -677,7 +678,7 @@ func (s *StageExecutor) runStageMountPoints(mountList []string) (map[string]inte
// If the source's name corresponds to the
// result of an earlier stage, wait for that
// stage to finish being built.
if isStage, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil {
if isStage, _, err := s.executor.waitForStage(s.ctx, from, s.stages[:s.index]); isStage && err != nil {
return nil, err
}
// If the source's name is a stage, return a
Expand Down Expand Up @@ -1149,9 +1150,15 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// If not, then go on assuming that it's just a regular image that's
// either in local storage, or one that we have to pull from a
// registry, subject to the passed-in pull policy.
if isStage, err := s.executor.waitForStage(ctx, base, s.stages[:s.index]); isStage && err != nil {
// This helper will also return the final resolved argument list from
// the parent stage, if available.
isStage, parentArgs, err := s.executor.waitForStage(ctx, base, s.stages[:s.index])
if isStage && err != nil {
return "", nil, false, err
}
// Update the start args with those from the parent stage
maps.Copy(ib.Args, parentArgs)

pullPolicy := s.executor.pullPolicy
s.executor.stagesLock.Lock()
var preserveBaseImageAnnotationsAtStageStart bool
Expand Down Expand Up @@ -1369,7 +1376,7 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
// If the source's name corresponds to the
// result of an earlier stage, wait for that
// stage to finish being built.
if isStage, err := s.executor.waitForStage(ctx, from, s.stages[:s.index]); isStage && err != nil {
if isStage, _, err := s.executor.waitForStage(ctx, from, s.stages[:s.index]); isStage && err != nil {
return "", nil, false, err
}
if otherStage, ok := s.executor.stages[from]; ok && otherStage.index < s.index {
Expand Down Expand Up @@ -1742,6 +1749,9 @@ func (s *StageExecutor) Execute(ctx context.Context, base string) (imgID string,
}
}

// store the final argument map for further use
s.executor.freezeArgs(s.name, ib.Args)

return imgID, ref, onlyBaseImage, nil
}

Expand Down
11 changes: 11 additions & 0 deletions tests/conformance/conformance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3451,6 +3451,17 @@ var internalTestCases = []testCase{
dockerUseBuildKit: true,
buildArgs: map[string]string{"SOURCE": "e/**/**/*sub/*.txt"},
},

{
name: "argument inheritance",
dockerUseBuildKit: true,
dockerfileContents: strings.Join([]string{
"FROM mirror.gcr.io/busybox AS base",
"ARG NAME=\"joe\"",
"FROM base AS build",
"RUN echo \"hello $NAME!\" > /hello.txt",
}, "\n"),
},
}

func TestCommit(t *testing.T) {
Expand Down
Loading