Skip to content

Commit 2ec1338

Browse files
authored
Merge pull request moby#5091 from jsternberg/lint-redundant-target-platform
checks: add redundant target platform check
2 parents 835e9fd + fe6a7dc commit 2ec1338

File tree

7 files changed

+152
-10
lines changed

7 files changed

+152
-10
lines changed

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,8 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
289289

290290
// set base state for every image
291291
for i, st := range stages {
292-
nameMatch, err := shlex.ProcessWordWithMatches(st.BaseName, metaArgsToEnvs(optMetaArgs))
292+
env := metaArgsToEnvs(optMetaArgs)
293+
nameMatch, err := shlex.ProcessWordWithMatches(st.BaseName, env)
293294
reportUnusedFromArgs(metaArgsKeys(optMetaArgs), nameMatch.Unmatched, st.Location, lint)
294295
used := nameMatch.Matched
295296
if used == nil {
@@ -316,8 +317,10 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
316317
}
317318

318319
if v := st.Platform; v != "" {
319-
platMatch, err := shlex.ProcessWordWithMatches(v, metaArgsToEnvs(optMetaArgs))
320+
platEnv := metaArgsToEnvs(optMetaArgs)
321+
platMatch, err := shlex.ProcessWordWithMatches(v, platEnv)
320322
reportUnusedFromArgs(metaArgsKeys(optMetaArgs), platMatch.Unmatched, st.Location, lint)
323+
reportRedundantTargetPlatform(st.Platform, platMatch, st.Location, platEnv, lint)
321324

322325
if err != nil {
323326
return nil, parser.WithLocation(errors.Wrapf(err, "failed to process arguments for platform %s", platMatch.Result), st.Location)
@@ -2281,6 +2284,26 @@ func reportUnusedFromArgs(values []string, unmatched map[string]struct{}, locati
22812284
}
22822285
}
22832286

2287+
func reportRedundantTargetPlatform(platformVar string, nameMatch shell.ProcessWordResult, location []parser.Range, env shell.EnvGetter, lint *linter.Linter) {
2288+
// Only match this rule if there was only one matched name.
2289+
// It's psosible there were multiple args and that one of them expanded to an empty
2290+
// string and we don't want to report a warning when that happens.
2291+
if len(nameMatch.Matched) == 1 && len(nameMatch.Unmatched) == 0 {
2292+
const targetPlatform = "TARGETPLATFORM"
2293+
// If target platform is the only environment variable that was substituted and the result
2294+
// matches the target platform exactly, we can infer that the input was ${TARGETPLATFORM} or
2295+
// $TARGETPLATFORM.
2296+
if _, ok := nameMatch.Matched[targetPlatform]; !ok {
2297+
return
2298+
}
2299+
2300+
if result, _ := env.Get(targetPlatform); nameMatch.Result == result {
2301+
msg := linter.RuleRedundantTargetPlatform.Format(platformVar)
2302+
lint.Run(&linter.RuleRedundantTargetPlatform, location, msg)
2303+
}
2304+
}
2305+
}
2306+
22842307
type instructionTracker struct {
22852308
Loc []parser.Range
22862309
IsSet bool

frontend/dockerfile/dockerfile_lint_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ var lintTests = integration.TestFuncs(
4040
testLegacyKeyValueFormat,
4141
testBaseImagePlatformMismatch,
4242
testAllTargetUnmarshal,
43+
testRedundantTargetPlatform,
4344
)
4445

4546
func testAllTargetUnmarshal(t *testing.T, sb integration.Sandbox) {
@@ -923,6 +924,42 @@ FROM a AS c
923924
})
924925
}
925926

927+
func testRedundantTargetPlatform(t *testing.T, sb integration.Sandbox) {
928+
dockerfile := []byte(`
929+
FROM --platform=$TARGETPLATFORM scratch
930+
`)
931+
checkLinterWarnings(t, sb, &lintTestParams{
932+
Dockerfile: dockerfile,
933+
Warnings: []expectedLintWarning{
934+
{
935+
RuleName: "RedundantTargetPlatform",
936+
Description: "Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior",
937+
URL: "https://docs.docker.com/go/dockerfile/rule/redundant-target-platform/",
938+
Detail: "Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior",
939+
Line: 2,
940+
Level: 1,
941+
},
942+
},
943+
})
944+
945+
dockerfile = []byte(`
946+
FROM --platform=${TARGETPLATFORM} scratch
947+
`)
948+
checkLinterWarnings(t, sb, &lintTestParams{
949+
Dockerfile: dockerfile,
950+
Warnings: []expectedLintWarning{
951+
{
952+
RuleName: "RedundantTargetPlatform",
953+
Description: "Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior",
954+
URL: "https://docs.docker.com/go/dockerfile/rule/redundant-target-platform/",
955+
Detail: "Setting platform to predefined ${TARGETPLATFORM} in FROM is redundant as this is the default behavior",
956+
Line: 2,
957+
Level: 1,
958+
},
959+
},
960+
})
961+
}
962+
926963
func checkUnmarshal(t *testing.T, sb integration.Sandbox, lintTest *lintTestParams) {
927964
destDir, err := os.MkdirTemp("", "buildkit")
928965
require.NoError(t, err)

frontend/dockerfile/dockerfile_test.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ var allTests = integration.TestFuncs(
122122
testErrorsSourceMap,
123123
testMultiArgs,
124124
testFrontendSubrequests,
125-
testDockefileCheckHostname,
125+
testDockerfileCheckHostname,
126126
testDefaultShellAndPath,
127127
testDockerfileLowercase,
128128
testExportCacheLoop,
@@ -201,8 +201,10 @@ var reproTests = integration.TestFuncs(
201201
testReproSourceDateEpoch,
202202
)
203203

204-
var opts []integration.TestOpt
205-
var securityOpts []integration.TestOpt
204+
var (
205+
opts []integration.TestOpt
206+
securityOpts []integration.TestOpt
207+
)
206208

207209
type frontend interface {
208210
Solve(context.Context, *client.Client, client.SolveOpt, chan *client.SolveStatus) (*client.SolveResponse, error)
@@ -4387,6 +4389,7 @@ COPY --from=base unique /
43874389
require.NoError(t, err)
43884390
require.Equal(t, string(dt), string(dt2))
43894391
}
4392+
43904393
func testCacheImportExport(t *testing.T, sb integration.Sandbox) {
43914394
integration.SkipOnPlatform(t, "windows")
43924395
workers.CheckFeatureCompat(t, sb, workers.FeatureCacheExport, workers.FeatureCacheBackendLocal)
@@ -5285,7 +5288,7 @@ COPY Dockerfile Dockerfile
52855288
}
52865289

52875290
// moby/buildkit#1301
5288-
func testDockefileCheckHostname(t *testing.T, sb integration.Sandbox) {
5291+
func testDockerfileCheckHostname(t *testing.T, sb integration.Sandbox) {
52895292
integration.SkipOnPlatform(t, "windows")
52905293
f := getFrontend(t, sb)
52915294
dockerfile := []byte(`
@@ -7496,6 +7499,7 @@ func (f *clientFrontend) SolveGateway(ctx context.Context, c gateway.Client, req
74967499
func (f *clientFrontend) DFCmdArgs(ctx, dockerfile string) (string, string) {
74977500
return "", ""
74987501
}
7502+
74997503
func (f *clientFrontend) RequiresBuildctl(t *testing.T) {
75007504
t.Skip()
75017505
}
@@ -7556,8 +7560,10 @@ func (*secModeInsecure) UpdateConfigFile(in string) string {
75567560
return in + "\n\ninsecure-entitlements = [\"security.insecure\"]\n"
75577561
}
75587562

7559-
var securityInsecureGranted integration.ConfigUpdater = &secModeInsecure{}
7560-
var securityInsecureDenied integration.ConfigUpdater = &secModeSandbox{}
7563+
var (
7564+
securityInsecureGranted integration.ConfigUpdater = &secModeInsecure{}
7565+
securityInsecureDenied integration.ConfigUpdater = &secModeSandbox{}
7566+
)
75617567

75627568
type networkModeHost struct{}
75637569

@@ -7571,8 +7577,10 @@ func (*networkModeSandbox) UpdateConfigFile(in string) string {
75717577
return in
75727578
}
75737579

7574-
var networkHostGranted integration.ConfigUpdater = &networkModeHost{}
7575-
var networkHostDenied integration.ConfigUpdater = &networkModeSandbox{}
7580+
var (
7581+
networkHostGranted integration.ConfigUpdater = &networkModeHost{}
7582+
networkHostDenied integration.ConfigUpdater = &networkModeSandbox{}
7583+
)
75767584

75777585
func fixedWriteCloser(wc io.WriteCloser) filesync.FileOutputFunc {
75787586
return func(map[string]string) (io.WriteCloser, error) {

frontend/dockerfile/docs/rules/_index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,5 +80,9 @@ $ docker build --check .
8080
<td><a href="./legacy-key-value-format/">LegacyKeyValueFormat</a></td>
8181
<td>Legacy key/value format with whitespace separator should not be used</td>
8282
</tr>
83+
<tr>
84+
<td><a href="./redundant-target-platform/">RedundantTargetPlatform</a></td>
85+
<td>Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior</td>
86+
</tr>
8387
</tbody>
8488
</table>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
title: RedundantTargetPlatform
3+
description: Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior
4+
aliases:
5+
- /go/dockerfile/rule/redundant-target-platform/
6+
---
7+
8+
## Output
9+
10+
```text
11+
Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior
12+
```
13+
14+
## Description
15+
16+
A custom platform can be used for a base image. The default platform is the
17+
same platform as the target output so setting the platform to `$TARGETPLATFORM`
18+
is redundant and unnecessary.
19+
20+
## Examples
21+
22+
❌ Bad: this usage of `--platform` is redundant since `$TARGETPLATFORM` is the default.
23+
24+
```dockerfile
25+
FROM --platform=$TARGETPLATFORM alpine AS builder
26+
RUN apk add --no-cache git
27+
```
28+
29+
✅ Good: omit the `--platform` argument.
30+
31+
```dockerfile
32+
FROM alpine AS builder
33+
RUN apk add --no-cache git
34+
```
35+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
## Output
2+
3+
```text
4+
Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior
5+
```
6+
7+
## Description
8+
9+
A custom platform can be used for a base image. The default platform is the
10+
same platform as the target output so setting the platform to `$TARGETPLATFORM`
11+
is redundant and unnecessary.
12+
13+
## Examples
14+
15+
❌ Bad: this usage of `--platform` is redundant since `$TARGETPLATFORM` is the default.
16+
17+
```dockerfile
18+
FROM --platform=$TARGETPLATFORM alpine AS builder
19+
RUN apk add --no-cache git
20+
```
21+
22+
✅ Good: omit the `--platform` argument.
23+
24+
```dockerfile
25+
FROM alpine AS builder
26+
RUN apk add --no-cache git
27+
```

frontend/dockerfile/linter/ruleset.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,4 +124,12 @@ var (
124124
return fmt.Sprintf("Base image %s was pulled with platform %q, expected %q for current build", image, actual, expected)
125125
},
126126
}
127+
RuleRedundantTargetPlatform = LinterRule[func(string) string]{
128+
Name: "RedundantTargetPlatform",
129+
Description: "Setting platform to predefined $TARGETPLATFORM in FROM is redundant as this is the default behavior",
130+
URL: "https://docs.docker.com/go/dockerfile/rule/redundant-target-platform/",
131+
Format: func(platformVar string) string {
132+
return fmt.Sprintf("Setting platform to predefined %s in FROM is redundant as this is the default behavior", platformVar)
133+
},
134+
}
127135
)

0 commit comments

Comments
 (0)