diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 0d781029ba88b..18b6807c336c3 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -612,7 +612,6 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS llbCaps: opt.LLBCaps, sourceMap: opt.SourceMap, lintWarn: opt.Warn, - argCmdVars: make(map[string]struct{}), } if err = dispatchOnBuildTriggers(d, d.image.Config.OnBuild, opt); err != nil { @@ -749,17 +748,19 @@ type dispatchOpt struct { llbCaps *apicaps.CapSet sourceMap *llb.SourceMap lintWarn linter.LintWarnFunc - argCmdVars map[string]struct{} } func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { - env, err := d.state.Env(context.TODO()) - if err != nil { - return err - } + var err error if ex, ok := cmd.Command.(instructions.SupportsSingleWordExpansion); ok { err := ex.Expand(func(word string) (string, error) { - newword, _, err := opt.shlex.ProcessWord(word, env) + env, err := d.state.Env(context.TODO()) + if err != nil { + return "", err + } + + newword, unmatched, err := opt.shlex.ProcessWord(word, env) + reportUnmatchedVariables(cmd, d.buildArgs, unmatched, &opt) return newword, err }) if err != nil { @@ -768,16 +769,20 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { } if ex, ok := cmd.Command.(instructions.SupportsSingleWordExpansionRaw); ok { err := ex.ExpandRaw(func(word string) (string, error) { + env, err := d.state.Env(context.TODO()) + if err != nil { + return "", err + } lex := shell.NewLex('\\') lex.SkipProcessQuotes = true - newword, _, err := lex.ProcessWord(word, env) + newword, unmatched, err := lex.ProcessWord(word, env) + reportUnmatchedVariables(cmd, d.buildArgs, unmatched, &opt) return newword, err }) if err != nil { return err } } - validateCommandVar(cmd.Command, env, &opt) switch c := cmd.Command.(type) { case *instructions.MaintainerCommand: @@ -837,7 +842,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { case *instructions.ShellCommand: err = dispatchShell(d, c) case *instructions.ArgCommand: - err = dispatchArg(d, c, opt.metaArgs, &opt) + err = dispatchArg(d, c, &opt) case *instructions.CopyCommand: l := opt.buildContext if len(cmd.sources) != 0 { @@ -1546,10 +1551,9 @@ func dispatchShell(d *dispatchState, c *instructions.ShellCommand) error { return commitToHistory(&d.image, fmt.Sprintf("SHELL %v", c.Shell), false, nil, d.epoch) } -func dispatchArg(d *dispatchState, c *instructions.ArgCommand, metaArgs []instructions.KeyValuePairOptional, opt *dispatchOpt) error { +func dispatchArg(d *dispatchState, c *instructions.ArgCommand, opt *dispatchOpt) error { commitStrs := make([]string, 0, len(c.Args)) for _, arg := range c.Args { - opt.argCmdVars[arg.Key] = struct{}{} buildArg := setKVValue(arg, opt.buildArgValues) commitStr := arg.Key @@ -1560,7 +1564,7 @@ func dispatchArg(d *dispatchState, c *instructions.ArgCommand, metaArgs []instru skipArgInfo := false // skip the arg info if the arg is inherited from global scope if buildArg.Value == nil { - for _, ma := range metaArgs { + for _, ma := range opt.metaArgs { if ma.Key == buildArg.Key { buildArg.Value = ma.Value skipArgInfo = true @@ -2046,16 +2050,18 @@ func validateStageNames(stages []instructions.Stage, warn linter.LintWarnFunc) { } } -func validateCommandVar(cmd instructions.Command, env []string, opt *dispatchOpt) { - if cmdstr, ok := cmd.(fmt.Stringer); ok { - _, unmatched, _ := opt.shlex.ProcessWord(cmdstr.String(), env) - for arg := range unmatched { - _, argCmdOk := opt.argCmdVars[arg] - _, nonEnvOk := nonEnvArgs[arg] - if !argCmdOk && !nonEnvOk { - msg := linter.RuleUndefinedVar.Format(arg) - linter.RuleUndefinedVar.Run(opt.warn, cmd.Location(), msg) - } +func reportUnmatchedVariables(cmd instructions.Command, buildArgs []instructions.KeyValuePairOptional, unmatched map[string]struct{}, opt *dispatchOpt) { + if len(unmatched) == 0 { + return + } + for _, buildArg := range buildArgs { + delete(unmatched, buildArg.Key) + } + for cmdVar := range unmatched { + _, nonEnvOk := nonEnvArgs[cmdVar] + if !nonEnvOk { + msg := linter.RuleUndefinedVar.Format(cmdVar) + linter.RuleUndefinedVar.Run(opt.lintWarn, cmd.Location(), msg) } } } diff --git a/frontend/dockerfile/dockerfile_lint_test.go b/frontend/dockerfile/dockerfile_lint_test.go index db4f423af1176..e8a7ff7043c25 100644 --- a/frontend/dockerfile/dockerfile_lint_test.go +++ b/frontend/dockerfile/dockerfile_lint_test.go @@ -32,7 +32,7 @@ var lintTests = integration.TestFuncs( testMaintainerDeprecated, testWarningsBeforeError, testUndeclaredArg, - testUndefinedVars, + testUnmatchedVars, ) func testStageName(t *testing.T, sb integration.Sandbox) { @@ -488,7 +488,7 @@ COPY Dockerfile . }) } -func testUndefinedVars(t *testing.T, sb integration.Sandbox) { +func testUnmatchedVars(t *testing.T, sb integration.Sandbox) { dockerfile := []byte(` FROM scratch ARG foo