diff --git a/client/client_test.go b/client/client_test.go index a95965d66d3f..13b554816645 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -208,6 +208,8 @@ func TestIntegration(t *testing.T) { testExportLocalNoPlatformSplitOverwrite, testValidateNullConfig, testValidateInvalidConfig, + testValidatePlatformsEmpty, + testValidatePlatformsInvalid, ) } diff --git a/client/validation_test.go b/client/validation_test.go index 41886443e94a..5dd5df6cf2ce 100644 --- a/client/validation_test.go +++ b/client/validation_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/util/testutil/integration" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" @@ -96,3 +97,110 @@ func testValidateInvalidConfig(t *testing.T, sb integration.Sandbox) { require.Error(t, err) require.Contains(t, err.Error(), "invalid image config for export: missing os") } + +func testValidatePlatformsEmpty(t *testing.T, sb integration.Sandbox) { + requiresLinux(t) + + ctx := sb.Context() + + c, err := New(ctx, sb.Address()) + require.NoError(t, err) + defer c.Close() + + b := func(ctx context.Context, c client.Client) (*client.Result, error) { + def, err := llb.Scratch().Marshal(ctx) + if err != nil { + return nil, err + } + + res, err := c.Solve(ctx, client.SolveRequest{ + Evaluate: true, + Definition: def.ToPB(), + }) + if err != nil { + return nil, err + } + res.AddMeta("refs.platforms", []byte("null")) + return res, nil + } + + _, err = c.Build(ctx, SolveOpt{ + Exports: []ExportEntry{ + { + Type: ExporterOCI, + Output: fixedWriteCloser(nopWriteCloser{io.Discard}), + }, + }, + }, "", b, nil) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid empty platforms index for exporter") +} + +func testValidatePlatformsInvalid(t *testing.T, sb integration.Sandbox) { + requiresLinux(t) + + ctx := sb.Context() + + c, err := New(ctx, sb.Address()) + require.NoError(t, err) + defer c.Close() + + tcases := []struct { + name string + value []exptypes.Platform + exp string + }{ + { + name: "emptyID", + value: []exptypes.Platform{{}}, + exp: "invalid empty platform key for exporter", + }, + { + name: "missingOS", + value: []exptypes.Platform{ + { + ID: "foo", + }, + }, + exp: "invalid platform value", + }, + } + + for _, tc := range tcases { + t.Run(tc.name, func(t *testing.T) { + b := func(ctx context.Context, c client.Client) (*client.Result, error) { + def, err := llb.Scratch().Marshal(ctx) + if err != nil { + return nil, err + } + + res, err := c.Solve(ctx, client.SolveRequest{ + Evaluate: true, + Definition: def.ToPB(), + }) + if err != nil { + return nil, err + } + + dt, err := json.Marshal(exptypes.Platforms{Platforms: tc.value}) + if err != nil { + return nil, err + } + + res.AddMeta("refs.platforms", dt) + return res, nil + } + + _, err = c.Build(ctx, SolveOpt{ + Exports: []ExportEntry{ + { + Type: ExporterOCI, + Output: fixedWriteCloser(nopWriteCloser{io.Discard}), + }, + }, + }, "", b, nil) + require.Error(t, err) + require.Contains(t, err.Error(), tc.exp) + }) + } +} diff --git a/exporter/containerimage/exptypes/parse.go b/exporter/containerimage/exptypes/parse.go index f77cd3f52565..6d01dc0f6e33 100644 --- a/exporter/containerimage/exptypes/parse.go +++ b/exporter/containerimage/exptypes/parse.go @@ -17,6 +17,18 @@ func ParsePlatforms(meta map[string][]byte) (Platforms, error) { return Platforms{}, errors.Wrapf(err, "failed to parse platforms passed to provenance processor") } } + if len(ps.Platforms) == 0 { + return Platforms{}, errors.Errorf("invalid empty platforms index for exporter") + } + for i, p := range ps.Platforms { + if p.ID == "" { + return Platforms{}, errors.Errorf("invalid empty platform key for exporter") + } + if p.Platform.OS == "" || p.Platform.Architecture == "" { + return Platforms{}, errors.Errorf("invalid platform value %v for exporter", p.Platform) + } + ps.Platforms[i].Platform = platforms.Normalize(p.Platform) + } return ps, nil } @@ -36,6 +48,8 @@ func ParsePlatforms(meta map[string][]byte) (Platforms, error) { OSFeatures: img.OSFeatures, Variant: img.Variant, } + } else if img.OS != "" || img.Architecture != "" { + return Platforms{}, errors.Errorf("invalid image config: os and architecture must be specified together") } } p = platforms.Normalize(p)