Skip to content

Commit

Permalink
dockerfile: avoid evaluating ARG default if unused
Browse files Browse the repository at this point in the history
Signed-off-by: Tonis Tiigi <[email protected]>
  • Loading branch information
tonistiigi committed May 17, 2024
1 parent 32bce82 commit 47a52a1
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 16 deletions.
52 changes: 36 additions & 16 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,9 @@ type dispatchOpt struct {

func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error {
var err error
if ex, ok := cmd.Command.(instructions.SupportsSingleWordExpansion); ok {
// ARG command value could be ignored, so defer handling the expansion error
_, isArg := cmd.Command.(*instructions.ArgCommand)
if ex, ok := cmd.Command.(instructions.SupportsSingleWordExpansion); ok && !isArg {
err := ex.Expand(func(word string) (string, error) {
env, err := d.state.Env(context.TODO())
if err != nil {
Expand Down Expand Up @@ -1593,39 +1595,57 @@ func dispatchShell(d *dispatchState, c *instructions.ShellCommand) error {
func dispatchArg(d *dispatchState, c *instructions.ArgCommand, opt *dispatchOpt) error {
commitStrs := make([]string, 0, len(c.Args))
for _, arg := range c.Args {
buildArg := setKVValue(arg, opt.buildArgValues)

commitStr := arg.Key
if arg.Value != nil {
commitStr += "=" + *arg.Value
}
commitStrs = append(commitStrs, commitStr)
_, hasValue := opt.buildArgValues[arg.Key]
hasDefault := arg.Value != nil

skipArgInfo := false // skip the arg info if the arg is inherited from global scope
if buildArg.Value == nil {
if !hasDefault && !hasValue {
for _, ma := range opt.metaArgs {
if ma.Key == buildArg.Key {
buildArg.Value = ma.Value
if ma.Key == arg.Key {
arg.Value = ma.Value
skipArgInfo = true
hasDefault = false
}
}
}

if hasValue {
v := opt.buildArgValues[arg.Key]
arg.Value = &v
} else if hasDefault {
env, err := d.state.Env(context.TODO())
if err != nil {
return err
}
v, unmatched, err := opt.shlex.ProcessWord(*arg.Value, env)
reportUnmatchedVariables(c, d.buildArgs, unmatched, opt)
if err != nil {
return err
}
arg.Value = &v
}

ai := argInfo{definition: arg, location: c.Location()}

if buildArg.Value != nil {
if _, ok := nonEnvArgs[buildArg.Key]; !ok {
d.state = d.state.AddEnv(buildArg.Key, *buildArg.Value)
if arg.Value != nil {
if _, ok := nonEnvArgs[arg.Key]; !ok {
d.state = d.state.AddEnv(arg.Key, *arg.Value)
}
ai.value = *buildArg.Value
ai.value = *arg.Value
}

if !skipArgInfo {
d.outline.allArgs[arg.Key] = ai
}
d.outline.usedArgs[arg.Key] = struct{}{}

d.buildArgs = append(d.buildArgs, buildArg)
d.buildArgs = append(d.buildArgs, arg)

commitStr := arg.Key
if arg.Value != nil {
commitStr += "=" + *arg.Value
}
commitStrs = append(commitStrs, commitStr)
}
return commitToHistory(&d.image, "ARG "+strings.Join(commitStrs, " "), false, nil, d.epoch)
}
Expand Down
53 changes: 53 additions & 0 deletions frontend/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ var allTests = integration.TestFuncs(
testExportMultiPlatform,
testQuotedMetaArgs,
testGlobalArgErrors,
testArgDefaultExpansion,
testIgnoreEntrypoint,
testSymlinkedDockerfile,
testEmptyWildcard,
Expand Down Expand Up @@ -1333,6 +1334,58 @@ FROM busybox
require.NoError(t, err)
}

func testArgDefaultExpansion(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
f := getFrontend(t, sb)

dockerfile := []byte(`
FROM scratch
ARG FOO
ARG BAR=${FOO:?"foo missing"}
`)

dir := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
)

c, err := client.New(sb.Context(), sb.Address())
require.NoError(t, err)
defer c.Close()

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
}, nil)
require.Error(t, err)

require.Contains(t, err.Error(), "FOO: foo missing")

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
FrontendAttrs: map[string]string{
"build-arg:FOO": "123",
},
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
}, nil)
require.NoError(t, err)

_, err = f.Solve(sb.Context(), c, client.SolveOpt{
FrontendAttrs: map[string]string{
"build-arg:BAR": "123",
},
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
}, nil)
require.NoError(t, err)
}

func testMultiArgs(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
f := getFrontend(t, sb)
Expand Down

0 comments on commit 47a52a1

Please sign in to comment.