Skip to content

Commit

Permalink
Merge pull request #4924 from tonistiigi/baseimage-platform
Browse files Browse the repository at this point in the history
dockerfile: detect base image with wrong platform being used
  • Loading branch information
tonistiigi committed May 31, 2024
2 parents 5893a5b + 88b2f70 commit 1a43db6
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 3 deletions.
10 changes: 10 additions & 0 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
llb.WithCustomName(prefixCommand(d, "FROM "+d.stage.BaseName, opt.MultiPlatformRequested, platform, nil)),
location(opt.SourceMap, d.stage.Location),
)
validateBaseImagePlatform(origName, *platform, d.image.Platform, d.stage.Location, opt.Warn)
}
d.platform = platform
return nil
Expand Down Expand Up @@ -2227,3 +2228,12 @@ func wrapSuggestAny(err error, keys map[string]struct{}, options []string) error
}
return err
}

func validateBaseImagePlatform(name string, expected, actual ocispecs.Platform, location []parser.Range, warn linter.LintWarnFunc) {
if expected.OS != actual.OS || expected.Architecture != actual.Architecture {
expectedStr := platforms.Format(platforms.Normalize(expected))
actualStr := platforms.Format(platforms.Normalize(actual))
msg := linter.RuleInvalidBaseImagePlatform.Format(name, expectedStr, actualStr)
linter.RuleInvalidBaseImagePlatform.Run(warn, location, msg)
}
}
13 changes: 10 additions & 3 deletions frontend/dockerfile/dockerfile_lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var lintTests = integration.TestFuncs(
testUnmatchedVars,
testMultipleInstructionsDisallowed,
testLegacyKeyValueFormat,
testBaseImagePlatformMismatch,
)

func testStageName(t *testing.T, sb integration.Sandbox) {
Expand Down Expand Up @@ -800,10 +801,15 @@ func checkProgressStream(t *testing.T, sb integration.Sandbox, lintTest *lintTes

f := getFrontend(t, sb)

_, err := f.Solve(sb.Context(), lintTest.Client, client.SolveOpt{
FrontendAttrs: map[string]string{
attrs := lintTest.FrontendAttrs
if attrs == nil {
attrs = map[string]string{
"platform": "linux/amd64,linux/arm64",
},
}
}

_, err := f.Solve(sb.Context(), lintTest.Client, client.SolveOpt{
FrontendAttrs: attrs,
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: lintTest.TmpDir,
dockerui.DefaultLocalNameContext: lintTest.TmpDir,
Expand Down Expand Up @@ -915,4 +921,5 @@ type lintTestParams struct {
StreamBuildErr string
UnmarshalBuildErr string
BuildErrLocation int32
FrontendAttrs map[string]string
}
74 changes: 74 additions & 0 deletions frontend/dockerfile/dockerfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7167,6 +7167,80 @@ func testSourcePolicyWithNamedContext(t *testing.T, sb integration.Sandbox) {
require.Equal(t, "foo", string(dt))
}

func testBaseImagePlatformMismatch(t *testing.T, sb integration.Sandbox) {
integration.SkipOnPlatform(t, "windows")
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush)
ctx := sb.Context()

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

registry, err := sb.NewRegistry()
if errors.Is(err, integration.ErrRequirements) {
t.Skip(err.Error())
}
require.NoError(t, err)

f := getFrontend(t, sb)

dockerfile := []byte(`
FROM scratch
COPY foo /foo
`)
dir := integration.Tmpdir(
t,
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo", []byte("test"), 0644),
)

// choose target platform that is different from the current platform
targetPlatform := runtime.GOOS + "/arm64"
if runtime.GOARCH == "arm64" {
targetPlatform = runtime.GOOS + "/amd64"
}

target := registry + "/buildkit/testbaseimageplatform:latest"
_, err = f.Solve(sb.Context(), c, client.SolveOpt{
LocalMounts: map[string]fsutil.FS{
dockerui.DefaultLocalNameDockerfile: dir,
dockerui.DefaultLocalNameContext: dir,
},
FrontendAttrs: map[string]string{
"platform": targetPlatform,
},
Exports: []client.ExportEntry{
{
Type: client.ExporterImage,
Attrs: map[string]string{
"name": target,
"push": "true",
},
},
},
}, nil)
require.NoError(t, err)

dockerfile = []byte(fmt.Sprintf(`
FROM %s
ENV foo=bar
`, target))

checkLinterWarnings(t, sb, &lintTestParams{
Dockerfile: dockerfile,
Warnings: []expectedLintWarning{
{
RuleName: "InvalidBaseImagePlatform",
Description: "Base image platform does not match expected target platform",
Detail: fmt.Sprintf("Base image %s was pulled with platform %q, expected %q for current build", target, targetPlatform, runtime.GOOS+"/"+runtime.GOARCH),
Level: 1,
Line: 2,
},
},
FrontendAttrs: map[string]string{},
})
}

func runShell(dir string, cmds ...string) error {
for _, args := range cmds {
var cmd *exec.Cmd
Expand Down
7 changes: 7 additions & 0 deletions frontend/dockerfile/linter/ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,11 @@ var (
return fmt.Sprintf("\"%s key=value\" should be used instead of legacy \"%s key value\" format", cmdName, cmdName)
},
}
RuleInvalidBaseImagePlatform = LinterRule[func(string, string, string) string]{
Name: "InvalidBaseImagePlatform",
Description: "Base image platform does not match expected target platform",
Format: func(image, expected, actual string) string {
return fmt.Sprintf("Base image %s was pulled with platform %q, expected %q for current build", image, actual, expected)
},
}
)

0 comments on commit 1a43db6

Please sign in to comment.