package client import ( "context" "encoding/json" "io" "testing" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend/gateway/client" sppb "github.com/moby/buildkit/sourcepolicy/pb" "github.com/moby/buildkit/util/iohelper" "github.com/moby/buildkit/util/testutil/integration" "github.com/moby/buildkit/util/testutil/workers" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" ) var validationTests = []func(t *testing.T, sb integration.Sandbox){ testValidateNullConfig, testValidateInvalidConfig, testValidatePlatformsEmpty, testValidatePlatformsInvalid, testValidateSourcePolicy, } func testValidateNullConfig(t *testing.T, sb integration.Sandbox) { requiresLinux(t) workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter) 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("containerimage.config", []byte("null")) return res, nil } _, err = c.Build(ctx, SolveOpt{ Exports: []ExportEntry{ { Type: ExporterOCI, Output: fixedWriteCloser(&iohelper.NopWriteCloser{Writer: io.Discard}), }, }, }, "", b, nil) require.Error(t, err) require.Contains(t, err.Error(), "invalid null image config for export") } func testValidateInvalidConfig(t *testing.T, sb integration.Sandbox) { requiresLinux(t) workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter) 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 } var img ocispecs.Image img.Platform = ocispecs.Platform{ Architecture: "amd64", } dt, err := json.Marshal(img) if err != nil { return nil, err } res.AddMeta("containerimage.config", dt) return res, nil } _, err = c.Build(ctx, SolveOpt{ Exports: []ExportEntry{ { Type: ExporterOCI, Output: fixedWriteCloser(&iohelper.NopWriteCloser{Writer: io.Discard}), }, }, }, "", b, nil) require.Error(t, err) require.Contains(t, err.Error(), "invalid image config: os and architecture must be specified together") } func testValidatePlatformsEmpty(t *testing.T, sb integration.Sandbox) { requiresLinux(t) workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter) 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(&iohelper.NopWriteCloser{Writer: 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) workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter) 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(&iohelper.NopWriteCloser{Writer: io.Discard}), }, }, }, "", b, nil) require.Error(t, err) require.Contains(t, err.Error(), tc.exp) }) } } func testValidateSourcePolicy(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 *sppb.Policy exp string }{ // this condition fails on marshaling atm // { // name: "nilrule", // value: &sppb.Policy{ // Rules: []*sppb.Rule{nil}, // }, // exp: "", // }, { name: "nilselector", value: &sppb.Policy{ Rules: []*sppb.Rule{ { Action: sppb.PolicyAction_CONVERT, }, }, }, exp: "invalid nil selector in policy", }, { name: "emptyaction", value: &sppb.Policy{ Rules: []*sppb.Rule{ { Action: sppb.PolicyAction(9000), Selector: &sppb.Selector{ Identifier: "docker-image://docker.io/library/alpine:latest", }, }, }, }, exp: "unknown type", }, { name: "nilupdates", value: &sppb.Policy{ Rules: []*sppb.Rule{ { Action: sppb.PolicyAction_CONVERT, Selector: &sppb.Selector{ Identifier: "docker-image://docker.io/library/alpine:latest", }, }, }, }, exp: "missing destination for convert rule", }, } for _, tc := range tcases { t.Run(tc.name, func(t *testing.T) { var viaFrontend bool b := func(ctx context.Context, c client.Client) (*client.Result, error) { def, err := llb.Image("alpine").Marshal(ctx) if err != nil { return nil, err } req := client.SolveRequest{ Evaluate: true, Definition: def.ToPB(), } if viaFrontend { req.SourcePolicies = []*sppb.Policy{ tc.value, } } return c.Solve(ctx, req) } _, err = c.Build(ctx, SolveOpt{ SourcePolicy: tc.value, }, "", b, nil) require.Error(t, err) require.Contains(t, err.Error(), tc.exp) viaFrontend = true _, err = c.Build(ctx, SolveOpt{}, "", b, nil) require.Error(t, err) require.Contains(t, err.Error(), tc.exp) }) } }