diff --git a/client/build_test.go b/client/build_test.go index 72442161..d092f4ae 100644 --- a/client/build_test.go +++ b/client/build_test.go @@ -3,6 +3,8 @@ package client import ( "bytes" "context" + "encoding/base64" + "encoding/json" "fmt" "io" "io/ioutil" @@ -55,6 +57,7 @@ func TestClientGatewayIntegration(t *testing.T) { testClientGatewayContainerExtraHosts, testClientGatewayContainerSignal, testWarnings, + testClientGatewayFrontendAttrs, ), integration.WithMirroredImages(integration.OfficialImages("busybox:latest"))) integration.Run(t, integration.TestFuncs( @@ -1994,6 +1997,52 @@ func testClientGatewayContainerSignal(t *testing.T, sb integration.Sandbox) { checkAllReleasable(t, c, sb, true) } +// moby/buildkit#2476 +func testClientGatewayFrontendAttrs(t *testing.T, sb integration.Sandbox) { + requiresLinux(t) + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + attrs := map[string]string{"build-arg:foo": "bar"} + dtattrs, err := json.Marshal(attrs) + require.NoError(t, err) + + b := func(ctx context.Context, c client.Client) (*client.Result, error) { + st := llb.Image("busybox:latest").Run( + llb.ReadonlyRootFS(), + llb.Args([]string{"/bin/sh", "-c", `echo hello`}), + ) + def, err := st.Marshal(sb.Context()) + if err != nil { + return nil, err + } + res, err := c.Solve(ctx, client.SolveRequest{ + Definition: def.ToPB(), + FrontendOpt: attrs, + }) + require.Contains(t, res.Metadata, "buildinfo.attrs") + require.Equal(t, res.Metadata["buildinfo.attrs"], dtattrs) + return res, err + } + + res, err := c.Build(sb.Context(), SolveOpt{}, "", b, nil) + require.NoError(t, err) + + require.Contains(t, res.ExporterResponse, exptypes.ExporterBuildInfo) + decbi, err := base64.StdEncoding.DecodeString(res.ExporterResponse[exptypes.ExporterBuildInfo]) + require.NoError(t, err) + + var bi binfotypes.BuildInfo + err = json.Unmarshal(decbi, &bi) + require.NoError(t, err) + + require.Contains(t, bi.Attrs, "build-arg:foo") + require.Equal(t, "bar", bi.Attrs["build-arg:foo"]) + + checkAllReleasable(t, c, sb, true) +} + type nopCloser struct { io.Writer } diff --git a/client/client_test.go b/client/client_test.go index fd79fe59..54336fe0 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -148,6 +148,7 @@ func TestIntegration(t *testing.T) { testRmSymlink, testMoveParentDir, testBuildExportWithForeignLayer, + testFrontendAttrs, ) tests = append(tests, diffOpTestCases()...) integration.Run(t, tests, mirrors) @@ -5038,6 +5039,40 @@ func testRelativeMountpoint(t *testing.T, sb integration.Sandbox) { require.Equal(t, dt, []byte(id)) } +// moby/buildkit#2476 +func testFrontendAttrs(t *testing.T, sb integration.Sandbox) { + requiresLinux(t) + c, err := New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + attrs := map[string]string{"build-arg:foo": "bar"} + dtattrs, err := json.Marshal(attrs) + require.NoError(t, err) + + frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) { + st := llb.Image("busybox:latest").Run( + llb.Args([]string{"/bin/sh", "-c", `echo hello`}), + ) + def, err := st.Marshal(sb.Context()) + if err != nil { + return nil, err + } + res, err := c.Solve(ctx, gateway.SolveRequest{ + Definition: def.ToPB(), + FrontendOpt: attrs, + }) + require.Contains(t, res.Metadata, "buildinfo.attrs") + require.Equal(t, res.Metadata["buildinfo.attrs"], dtattrs) + return res, err + } + + _, err = c.Build(sb.Context(), SolveOpt{}, "", frontend, nil) + require.NoError(t, err) + + checkAllReleasable(t, c, sb, true) +} + func tmpdir(appliers ...fstest.Applier) (string, error) { tmpdir, err := ioutil.TempDir("", "buildkit-client") if err != nil { diff --git a/exporter/containerimage/exptypes/types.go b/exporter/containerimage/exptypes/types.go index 94f91e85..7d3f9fad 100644 --- a/exporter/containerimage/exptypes/types.go +++ b/exporter/containerimage/exptypes/types.go @@ -13,6 +13,7 @@ const ( ExporterImageDescriptorKey = "containerimage.descriptor" ExporterInlineCache = "containerimage.inlinecache" ExporterBuildInfo = "containerimage.buildinfo" + ExporterBuildInfoAttrs = "buildinfo.attrs" ExporterPlatformsKey = "refs.platforms" ) diff --git a/solver/llbsolver/bridge.go b/solver/llbsolver/bridge.go index 4a5c337d..80b6df62 100644 --- a/solver/llbsolver/bridge.go +++ b/solver/llbsolver/bridge.go @@ -2,6 +2,7 @@ package llbsolver import ( "context" + "encoding/json" "fmt" "strings" "sync" @@ -12,6 +13,7 @@ import ( "github.com/moby/buildkit/cache/remotecache" "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend" gw "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/identity" @@ -142,8 +144,7 @@ func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest, sid st if req.Definition != nil && req.Definition.Def != nil { res = &frontend.Result{Ref: newResultProxy(b, req)} if req.Evaluate { - _, err := res.Ref.Result(ctx) - return res, err + _, err = res.Ref.Result(ctx) } } else if req.Frontend != "" { f, ok := b.frontends[req.Frontend] @@ -158,6 +159,16 @@ func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest, sid st return &frontend.Result{}, nil } + if res.Metadata == nil { + res.Metadata = make(map[string][]byte) + } + + attrs, errm := json.Marshal(req.FrontendOpt) + if errm != nil { + return nil, errm + } + res.Metadata[exptypes.ExporterBuildInfoAttrs] = attrs + return } diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index 8aa037c5..4758aa42 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -175,8 +175,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro return nil, errors.Errorf("invalid reference: %T", r.Sys()) } inp.Ref = workerRef.ImmutableRef - - dtbi, err := buildinfo.Encode(ctx, req, res.BuildSources(), inp.Metadata[exptypes.ExporterImageConfigKey]) + dtbi, err := buildinfo.Encode(ctx, req.Frontend, inp.Metadata[exptypes.ExporterBuildInfoAttrs], res.BuildSources(), inp.Metadata[exptypes.ExporterImageConfigKey]) if err != nil { return nil, err } @@ -207,8 +206,7 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro return nil, errors.Errorf("invalid reference: %T", r.Sys()) } m[k] = workerRef.ImmutableRef - - dtbi, err := buildinfo.Encode(ctx, req, res.BuildSources(), inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, k)]) + dtbi, err := buildinfo.Encode(ctx, req.Frontend, inp.Metadata[exptypes.ExporterBuildInfoAttrs], res.BuildSources(), inp.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterImageConfigKey, k)]) if err != nil { return nil, err } diff --git a/util/buildinfo/buildinfo.go b/util/buildinfo/buildinfo.go index 73f83256..d4979dcd 100644 --- a/util/buildinfo/buildinfo.go +++ b/util/buildinfo/buildinfo.go @@ -8,7 +8,6 @@ import ( "strings" "github.com/docker/distribution/reference" - "github.com/moby/buildkit/frontend" "github.com/moby/buildkit/source" binfotypes "github.com/moby/buildkit/util/buildinfo/types" "github.com/moby/buildkit/util/urlutil" @@ -26,7 +25,7 @@ func Decode(enc string) (bi binfotypes.BuildInfo, _ error) { } // Encode encodes build info. -func Encode(ctx context.Context, req frontend.SolveRequest, buildSources map[string]string, imageConfig []byte) ([]byte, error) { +func Encode(ctx context.Context, frontend string, frontendAttrs []byte, buildSources map[string]string, imageConfig []byte) ([]byte, error) { icbi, err := FromImageConfig(imageConfig) if err != nil { return nil, err @@ -35,9 +34,15 @@ func Encode(ctx context.Context, req frontend.SolveRequest, buildSources map[str if err != nil { return nil, err } + attrs := make(map[string]string) + if frontendAttrs != nil { + if err := json.Unmarshal(frontendAttrs, &attrs); err != nil { + return nil, err + } + } return json.Marshal(binfotypes.BuildInfo{ - Frontend: req.Frontend, - Attrs: filterAttrs(req.FrontendOpt), + Frontend: frontend, + Attrs: filterAttrs(attrs), Sources: srcs, }) }