Skip to content

Commit 94d3dae

Browse files
authored
Merge pull request moby#5243 from daghack/heredoc-case-consistent
frontend: command casing rule check after parsing the ast
2 parents 9b4378f + e7779a5 commit 94d3dae

File tree

4 files changed

+42
-16
lines changed

4 files changed

+42
-16
lines changed

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,6 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
251251
}
252252
}
253253

254-
validateCommandCasing(dockerfile, lint)
255-
256254
proxyEnv := proxyEnvFromBuildArgs(opt.BuildArgs)
257255

258256
stages, metaArgs, err := instructions.Parse(dockerfile.AST, lint)
@@ -263,6 +261,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
263261
return nil, errors.New("dockerfile contains no stages to build")
264262
}
265263
validateStageNames(stages, lint)
264+
validateCommandCasing(stages, lint)
266265

267266
shlex := shell.NewLex(dockerfile.EscapeToken)
268267
outline := newOutlineCapture()
@@ -2199,32 +2198,49 @@ func isSelfConsistentCasing(s string) bool {
21992198
return s == strings.ToLower(s) || s == strings.ToUpper(s)
22002199
}
22012200

2202-
func validateCommandCasing(dockerfile *parser.Result, lint *linter.Linter) {
2201+
func validateCaseMatch(name string, isMajorityLower bool, location []parser.Range, lint *linter.Linter) {
2202+
var correctCasing string
2203+
if isMajorityLower && strings.ToLower(name) != name {
2204+
correctCasing = "lowercase"
2205+
} else if !isMajorityLower && strings.ToUpper(name) != name {
2206+
correctCasing = "uppercase"
2207+
}
2208+
if correctCasing != "" {
2209+
msg := linter.RuleConsistentInstructionCasing.Format(name, correctCasing)
2210+
lint.Run(&linter.RuleConsistentInstructionCasing, location, msg)
2211+
}
2212+
}
2213+
2214+
func validateCommandCasing(stages []instructions.Stage, lint *linter.Linter) {
22032215
var lowerCount, upperCount int
2204-
for _, node := range dockerfile.AST.Children {
2205-
if isSelfConsistentCasing(node.Value) {
2206-
if strings.ToLower(node.Value) == node.Value {
2216+
for _, stage := range stages {
2217+
if isSelfConsistentCasing(stage.OrigCmd) {
2218+
if strings.ToLower(stage.OrigCmd) == stage.OrigCmd {
22072219
lowerCount++
22082220
} else {
22092221
upperCount++
22102222
}
22112223
}
2224+
for _, cmd := range stage.Commands {
2225+
cmdName := cmd.Name()
2226+
if isSelfConsistentCasing(cmdName) {
2227+
if strings.ToLower(cmdName) == cmdName {
2228+
lowerCount++
2229+
} else {
2230+
upperCount++
2231+
}
2232+
}
2233+
}
22122234
}
22132235

22142236
isMajorityLower := lowerCount > upperCount
2215-
for _, node := range dockerfile.AST.Children {
2237+
for _, stage := range stages {
22162238
// Here, we check both if the command is consistent per command (ie, "CMD" or "cmd", not "Cmd")
22172239
// as well as ensuring that the casing is consistent throughout the dockerfile by comparing the
22182240
// command to the casing of the majority of commands.
2219-
var correctCasing string
2220-
if isMajorityLower && strings.ToLower(node.Value) != node.Value {
2221-
correctCasing = "lowercase"
2222-
} else if !isMajorityLower && strings.ToUpper(node.Value) != node.Value {
2223-
correctCasing = "uppercase"
2224-
}
2225-
if correctCasing != "" {
2226-
msg := linter.RuleConsistentInstructionCasing.Format(node.Value, correctCasing)
2227-
lint.Run(&linter.RuleConsistentInstructionCasing, node.Location(), msg)
2241+
validateCaseMatch(stage.OrigCmd, isMajorityLower, stage.Location, lint)
2242+
for _, cmd := range stage.Commands {
2243+
validateCaseMatch(cmd.Name(), isMajorityLower, cmd.Location(), lint)
22282244
}
22292245
}
22302246
}

frontend/dockerfile/dockerfile_lint_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,14 @@ copy Dockerfile /bar
578578
FROM scratch
579579
COPY Dockerfile /foo
580580
COPY Dockerfile /bar
581+
`)
582+
checkLinterWarnings(t, sb, &lintTestParams{Dockerfile: dockerfile})
583+
584+
dockerfile = []byte(`
585+
FROM alpine
586+
RUN <<'EOT'
587+
env
588+
EOT
581589
`)
582590
checkLinterWarnings(t, sb, &lintTestParams{Dockerfile: dockerfile})
583591
}

frontend/dockerfile/instructions/commands.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@ type ShellCommand struct {
506506
type Stage struct {
507507
Name string // name of the stage
508508
Commands []Command // commands contained within the stage
509+
OrigCmd string // original FROM command, used for rule checks
509510
BaseName string // name of the base stage or source
510511
Platform string // platform of base source to use
511512

frontend/dockerfile/instructions/parse.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ func parseFrom(req parseRequest) (*Stage, error) {
398398
code := strings.TrimSpace(req.original)
399399
return &Stage{
400400
BaseName: req.args[0],
401+
OrigCmd: req.command,
401402
Name: stageName,
402403
SourceCode: code,
403404
Commands: []Command{},

0 commit comments

Comments
 (0)