From c8429c01dd6b1a195b894a1069c8dd714ad24bb0 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Sat, 19 Feb 2022 22:47:36 +0100 Subject: [PATCH] buildinfo: handle deps Signed-off-by: CrazyMax --- client/client_test.go | 7 +- docs/build-repro.md | 29 +- frontend/dockerfile/dockerfile2llb/convert.go | 14 +- .../dockerfile/dockerfile_buildinfo_test.go | 287 +++++++++++++++++- util/buildinfo/buildinfo.go | 90 +++++- util/buildinfo/buildinfo_test.go | 30 +- util/buildinfo/types/types.go | 4 +- 7 files changed, 423 insertions(+), 38 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 4ea9da48dc349..7cf6582920ae1 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -5194,12 +5194,13 @@ func testBuildInfoInline(t *testing.T, sb integration.Sandbox) { require.NoError(t, err) var config binfotypes.ImageConfig - err = json.Unmarshal(dt, &config) + require.NoError(t, json.Unmarshal(dt, &config)) + + dec, err := base64.StdEncoding.DecodeString(config.BuildInfo) require.NoError(t, err) var bi binfotypes.BuildInfo - err = json.Unmarshal(config.BuildInfo, &bi) - require.NoError(t, err) + require.NoError(t, json.Unmarshal(dec, &bi)) if tt.buildAttrs { attrval := "bar" diff --git a/docs/build-repro.md b/docs/build-repro.md index f8356d75eaa5d..40a84f935b82e 100644 --- a/docs/build-repro.md +++ b/docs/build-repro.md @@ -6,18 +6,6 @@ Build dependencies are generated when your image has been built. These dependencies include versions of used images, git repositories and HTTP URLs used by LLB `Source` operation as well as build request attributes. -By default, the build dependencies are inlined in the image configuration. You -can disable this behavior with the [`buildinfo` attribute](../README.md#imageregistry). - -### Image config - -A new field similar to the one for inline cache has been added to the image -configuration to embed build dependencies: - -```text -"moby.buildkit.buildinfo.v1": -``` - The structure is base64 encoded and has the following format when decoded: ```json @@ -57,10 +45,25 @@ The structure is base64 encoded and has the following format when decoded: * `frontend` defines the frontend used to build. * `attrs` defines build request attributes. -* `sources` defines build dependencies. +* `sources` defines build sources. * `type` defines the source type (`docker-image`, `git` or `http`). * `ref` is the reference of the source. * `pin` is the source digest. +* `deps` defines build dependencies from named contexts. + +### Image config + +A new field similar to the one for inline cache has been added to the image +configuration to embed build dependencies: + +```json +{ + "moby.buildkit.buildinfo.v0": "" +} +``` + +By default, the build dependencies are inlined in the image configuration. You +can disable this behavior with the [`buildinfo` attribute](../README.md#imageregistry). ### Exporter response (metadata) diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 33bc1bfb26643..69e1b7ed4366d 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -382,16 +382,16 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, buildContext := &mutableOutput{} ctxPaths := map[string]struct{}{} - buildinfo := &binfotypes.BuildInfo{} + buildInfo := &binfotypes.BuildInfo{} for _, d := range allDispatchStates.states { if !isReachable(target, d) { continue } - // collect build dependencies + // collect build sources and dependencies if d.buildSource != nil { - buildinfo.Sources = append(buildinfo.Sources, *d.buildSource) + buildInfo.Sources = append(buildInfo.Sources, *d.buildSource) } if d.base != nil { @@ -469,9 +469,9 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, } // sort build sources - if len(buildinfo.Sources) > 0 { - sort.Slice(buildinfo.Sources, func(i, j int) bool { - return buildinfo.Sources[i].Ref < buildinfo.Sources[j].Ref + if len(buildInfo.Sources) > 0 { + sort.Slice(buildInfo.Sources, func(i, j int) bool { + return buildInfo.Sources[i].Ref < buildInfo.Sources[j].Ref }) } @@ -512,7 +512,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, target.image.Variant = platformOpt.targetPlatform.Variant } - return &st, &target.image, buildinfo, nil + return &st, &target.image, buildInfo, nil } func metaArgsToMap(metaArgs []instructions.KeyValuePairOptional) map[string]string { diff --git a/frontend/dockerfile/dockerfile_buildinfo_test.go b/frontend/dockerfile/dockerfile_buildinfo_test.go index f564831bd002a..9383a1625aaf4 100644 --- a/frontend/dockerfile/dockerfile_buildinfo_test.go +++ b/frontend/dockerfile/dockerfile_buildinfo_test.go @@ -1,6 +1,7 @@ package dockerfile import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -16,16 +17,21 @@ import ( "github.com/moby/buildkit/client" "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/frontend/dockerfile/builder" + gateway "github.com/moby/buildkit/frontend/gateway/client" + "github.com/moby/buildkit/solver/pb" binfotypes "github.com/moby/buildkit/util/buildinfo/types" "github.com/moby/buildkit/util/testutil/integration" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var buildinfoTests = integration.TestFuncs( - testBuildSources, - testBuildAttrs, + testBuildInfoSources, + testBuildInfoAttrs, testBuildInfoMultiPlatform, + testBuildInfoDeps, + testBuildInfoDepsMultiPlatform, ) func init() { @@ -33,7 +39,7 @@ func init() { } // moby/buildkit#2311 -func testBuildSources(t *testing.T, sb integration.Sandbox) { +func testBuildInfoSources(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) gitDir, err := ioutil.TempDir("", "buildkit") @@ -114,7 +120,7 @@ COPY --from=alpine /bin/busybox /alpine-busybox } // moby/buildkit#2476 -func testBuildAttrs(t *testing.T, sb integration.Sandbox) { +func testBuildInfoAttrs(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) f.RequiresBuildctl(t) @@ -245,3 +251,276 @@ ADD https://raw.githubusercontent.com/moby/moby/master/README.md / assert.Equal(t, "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", sources[1].Pin) } } + +func testBuildInfoDeps(t *testing.T, sb integration.Sandbox) { + ctx := sb.Context() + f := getFrontend(t, sb) + f.RequiresBuildctl(t) + + c, err := client.New(ctx, sb.Address()) + require.NoError(t, err) + defer c.Close() + + dockerfile := []byte(` +FROM alpine +ENV FOO=bar +RUN echo first > /out +`) + + dir, err := tmpdir( + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + dockerfile2 := []byte(` +FROM base AS build +RUN echo "foo is $FOO" > /foo +FROM busybox +COPY --from=build /foo /out / +`) + + dir2, err := tmpdir( + fstest.CreateFile("Dockerfile", dockerfile2, 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + b := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) { + res, err := f.SolveGateway(ctx, c, gateway.SolveRequest{}) + if err != nil { + return nil, err + } + ref, err := res.SingleRef() + if err != nil { + return nil, err + } + st, err := ref.ToState() + if err != nil { + return nil, err + } + + def, err := st.Marshal(ctx) + if err != nil { + return nil, err + } + + dtic, ok := res.Metadata[exptypes.ExporterImageConfigKey] + if !ok { + return nil, errors.Errorf("no containerimage.config in metadata") + } + + dtbi, ok := res.Metadata[exptypes.ExporterBuildInfo] + if !ok { + return nil, errors.Errorf("no containerimage.buildinfo in metadata") + } + + dt, err := json.Marshal(map[string][]byte{ + exptypes.ExporterImageConfigKey: dtic, + exptypes.ExporterBuildInfo: dtbi, + }) + if err != nil { + return nil, err + } + + res, err = f.SolveGateway(ctx, c, gateway.SolveRequest{ + FrontendOpt: map[string]string{ + "dockerfilekey": builder.DefaultLocalNameDockerfile + "2", + "context:base": "input:base", + "input-metadata:base": string(dt), + }, + FrontendInputs: map[string]*pb.Definition{ + "base": def.ToPB(), + }, + }) + if err != nil { + return nil, err + } + return res, nil + } + + destDir, err := ioutil.TempDir("", "buildkit") + require.NoError(t, err) + defer os.RemoveAll(destDir) + + res, err := c.Build(ctx, client.SolveOpt{ + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + builder.DefaultLocalNameDockerfile + "2": dir2, + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterLocal, + OutputDir: destDir, + }, + }, + }, "", b, nil) + require.NoError(t, err) + + require.Contains(t, res.ExporterResponse, exptypes.ExporterBuildInfo) + dtbi, err := base64.StdEncoding.DecodeString(res.ExporterResponse[exptypes.ExporterBuildInfo]) + require.NoError(t, err) + + var bi binfotypes.BuildInfo + err = json.Unmarshal(dtbi, &bi) + require.NoError(t, err) + + require.Equal(t, 2, len(bi.Sources)) + assert.Equal(t, binfotypes.SourceTypeDockerImage, bi.Sources[0].Type) + assert.True(t, strings.HasPrefix(bi.Sources[0].Ref, "docker.io/library/alpine")) + assert.NotEmpty(t, bi.Sources[0].Pin) + assert.Equal(t, binfotypes.SourceTypeDockerImage, bi.Sources[1].Type) + assert.Equal(t, "docker.io/library/busybox:latest", bi.Sources[1].Ref) + assert.NotEmpty(t, bi.Sources[1].Pin) + + require.Contains(t, bi.Deps, "base") + depsrc := bi.Deps["base"].Sources + require.Equal(t, 1, len(depsrc)) + assert.Equal(t, binfotypes.SourceTypeDockerImage, depsrc[0].Type) + assert.Equal(t, "alpine", depsrc[0].Ref) + assert.NotEmpty(t, depsrc[0].Pin) +} + +func testBuildInfoDepsMultiPlatform(t *testing.T, sb integration.Sandbox) { + ctx := sb.Context() + f := getFrontend(t, sb) + f.RequiresBuildctl(t) + + platforms := []string{"linux/amd64", "linux/arm64"} + + c, err := client.New(ctx, sb.Address()) + require.NoError(t, err) + defer c.Close() + + dockerfile := []byte(` +FROM --platform=$BUILDPLATFORM alpine +ARG TARGETARCH +ENV FOO=bar-$TARGETARCH +RUN echo "foo $TARGETARCH" > /out +`) + + dir, err := tmpdir( + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + dockerfile2 := []byte(` +FROM base AS build +RUN echo "foo is $FOO" > /foo +FROM busybox +COPY --from=build /foo /out / +`) + + dir2, err := tmpdir( + fstest.CreateFile("Dockerfile", dockerfile2, 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + b := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) { + res, err := f.SolveGateway(ctx, c, gateway.SolveRequest{ + FrontendOpt: map[string]string{ + "platform": strings.Join(platforms, ","), + }, + }) + if err != nil { + return nil, err + } + + if len(res.Refs) != 2 { + return nil, errors.Errorf("expected 2 refs, got %d", len(res.Refs)) + } + + frontendOpt := map[string]string{ + "dockerfilekey": builder.DefaultLocalNameDockerfile + "2", + "platform": strings.Join(platforms, ","), + } + + inputs := map[string]*pb.Definition{} + for _, platform := range platforms { + frontendOpt["context:base::"+platform] = "input:base::" + platform + + st, err := res.Refs[platform].ToState() + if err != nil { + return nil, err + } + def, err := st.Marshal(ctx) + if err != nil { + return nil, err + } + inputs["base::"+platform] = def.ToPB() + + dtic, ok := res.Metadata[exptypes.ExporterImageConfigKey+"/"+platform] + if !ok { + return nil, errors.Errorf("no containerimage.config/" + platform + " in metadata") + } + dtbi, ok := res.Metadata[exptypes.ExporterBuildInfo+"/"+platform] + if !ok { + return nil, errors.Errorf("no containerimage.buildinfo/" + platform + " in metadata") + } + dt, err := json.Marshal(map[string][]byte{ + exptypes.ExporterImageConfigKey: dtic, + exptypes.ExporterBuildInfo: dtbi, + }) + if err != nil { + return nil, err + } + frontendOpt["input-metadata:base:"+platform] = string(dt) + } + + res, err = f.SolveGateway(ctx, c, gateway.SolveRequest{ + FrontendOpt: frontendOpt, + FrontendInputs: inputs, + }) + if err != nil { + return nil, err + } + return res, nil + } + + destDir, err := ioutil.TempDir("", "buildkit") + require.NoError(t, err) + defer os.RemoveAll(destDir) + + res, err := c.Build(ctx, client.SolveOpt{ + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + builder.DefaultLocalNameDockerfile + "2": dir2, + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterLocal, + OutputDir: destDir, + }, + }, + }, "", b, nil) + require.NoError(t, err) + + for _, platform := range platforms { + require.Contains(t, res.ExporterResponse, fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, platform)) + dtbi, err := base64.StdEncoding.DecodeString(res.ExporterResponse[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, platform)]) + require.NoError(t, err) + + var bi binfotypes.BuildInfo + err = json.Unmarshal(dtbi, &bi) + require.NoError(t, err) + + require.Equal(t, 2, len(bi.Sources)) + assert.Equal(t, binfotypes.SourceTypeDockerImage, bi.Sources[0].Type) + assert.True(t, strings.HasPrefix(bi.Sources[0].Ref, "docker.io/library/alpine")) + assert.NotEmpty(t, bi.Sources[0].Pin) + assert.Equal(t, binfotypes.SourceTypeDockerImage, bi.Sources[1].Type) + assert.Equal(t, "docker.io/library/busybox:latest", bi.Sources[1].Ref) + assert.NotEmpty(t, bi.Sources[1].Pin) + + require.Contains(t, bi.Deps, "base:"+platform) + depsrc := bi.Deps["base:"+platform].Sources + require.Equal(t, 1, len(depsrc)) + assert.Equal(t, binfotypes.SourceTypeDockerImage, depsrc[0].Type) + assert.Equal(t, "alpine", depsrc[0].Ref) + assert.NotEmpty(t, depsrc[0].Pin) + } +} diff --git a/util/buildinfo/buildinfo.go b/util/buildinfo/buildinfo.go index a3186affb3ecb..965b6cae3ddd9 100644 --- a/util/buildinfo/buildinfo.go +++ b/util/buildinfo/buildinfo.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/docker/distribution/reference" + "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/source" binfotypes "github.com/moby/buildkit/util/buildinfo/types" "github.com/moby/buildkit/util/urlutil" @@ -32,15 +33,18 @@ func Encode(ctx context.Context, buildInfo []byte, buildSources map[string]strin return nil, err } } - msources, err := mergeSources(ctx, buildSources, bi.Sources) - if err != nil { + if deps, err := decodeDeps(bi.Attrs); err == nil { + bi.Deps = reduceMapBuildInfo(deps, bi.Deps) + } else { return nil, err } - return json.Marshal(binfotypes.BuildInfo{ - Frontend: bi.Frontend, - Attrs: filterAttrs(bi.Attrs), - Sources: msources, - }) + if sources, err := mergeSources(ctx, buildSources, bi.Sources); err == nil { + bi.Sources = sources + } else { + return nil, err + } + bi.Attrs = filterAttrs(bi.Attrs) + return json.Marshal(bi) } // mergeSources combines and fixes build sources from frontend sources. @@ -136,6 +140,32 @@ func mergeSources(ctx context.Context, buildSources map[string]string, frontendS return srcs, nil } +func decodeDeps(attrs map[string]*string) (map[string]binfotypes.BuildInfo, error) { + res := make(map[string]binfotypes.BuildInfo) + for k, v := range attrs { + if v == nil || !strings.HasPrefix(k, "input-metadata:") { + continue + } + var inputresp map[string]string + if err := json.Unmarshal([]byte(*v), &inputresp); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal input-metadata") + } + if _, ok := inputresp[exptypes.ExporterBuildInfo]; !ok { + continue + } + bi, err := Decode(inputresp[exptypes.ExporterBuildInfo]) + if err != nil { + return nil, errors.Wrap(err, "failed to decode build info from input-metadata") + } + kl := strings.SplitN(k, ":", 2) + res[kl[1]] = bi + } + if len(res) == 0 { + return nil, nil + } + return res, nil +} + // FormatOpts holds build info format options. type FormatOpts struct { RemoveAttrs bool @@ -239,15 +269,25 @@ func GetMetadata(metadata map[string][]byte, key string, reqFrontend string, req if reqFrontend != "" { mbi.Frontend = reqFrontend } - mbi.Attrs = filterAttrs(convertMap(reduceMap(reqAttrs, mbi.Attrs))) + if deps, err := decodeDeps(convertMap(reduceMapString(reqAttrs, mbi.Attrs))); err == nil { + mbi.Deps = reduceMapBuildInfo(deps, mbi.Deps) + } else { + return nil, err + } + mbi.Attrs = filterAttrs(convertMap(reduceMapString(reqAttrs, mbi.Attrs))) dtbi, err = json.Marshal(mbi) if err != nil { return nil, errors.Wrapf(err, "failed to marshal build info for %q", key) } } else { + deps, err := decodeDeps(convertMap(reqAttrs)) + if err != nil { + return nil, err + } dtbi, err = json.Marshal(binfotypes.BuildInfo{ Frontend: reqFrontend, Attrs: filterAttrs(convertMap(reqAttrs)), + Deps: deps, }) if err != nil { return nil, errors.Wrapf(err, "failed to marshal build info for %q", key) @@ -256,7 +296,26 @@ func GetMetadata(metadata map[string][]byte, key string, reqFrontend string, req return dtbi, nil } -func reduceMap(m1 map[string]string, m2 map[string]*string) map[string]string { +// FromImageConfig returns build info from image config. +func FromImageConfig(dt []byte) (*binfotypes.BuildInfo, error) { + if len(dt) == 0 { + return nil, nil + } + var config binfotypes.ImageConfig + if err := json.Unmarshal(dt, &config); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal image config") + } + if len(config.BuildInfo) == 0 { + return nil, nil + } + bi, err := Decode(config.BuildInfo) + if err != nil { + return nil, errors.Wrap(err, "failed to decode build info from image config") + } + return &bi, nil +} + +func reduceMapString(m1 map[string]string, m2 map[string]*string) map[string]string { if m1 == nil && m2 == nil { return nil } @@ -271,6 +330,19 @@ func reduceMap(m1 map[string]string, m2 map[string]*string) map[string]string { return m1 } +func reduceMapBuildInfo(m1 map[string]binfotypes.BuildInfo, m2 map[string]binfotypes.BuildInfo) map[string]binfotypes.BuildInfo { + if m1 == nil && m2 == nil { + return nil + } + if m1 == nil { + m1 = map[string]binfotypes.BuildInfo{} + } + for k, v := range m2 { + m1[k] = v + } + return m1 +} + func convertMap(m map[string]string) map[string]*string { res := make(map[string]*string) for k, v := range m { diff --git a/util/buildinfo/buildinfo_test.go b/util/buildinfo/buildinfo_test.go index bdfb0a95783bc..76b09722e7647 100644 --- a/util/buildinfo/buildinfo_test.go +++ b/util/buildinfo/buildinfo_test.go @@ -91,6 +91,34 @@ func TestMergeSources(t *testing.T) { }, srcs) } +func TestDecodeDeps(t *testing.T) { + deps, err := decodeDeps(map[string]*string{ + "build-arg:bar": stringPtr("foo"), + "build-arg:foo": stringPtr("bar"), + "context:baseapp": stringPtr("input:0-base"), + "filename": stringPtr("Dockerfile"), + "input-metadata:0-base": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJhdHRycyI6eyJidWlsZC1hcmc6YmFyIjoiZm9vIiwiYnVpbGQtYXJnOmZvbyI6ImJhciIsImZpbGVuYW1lIjoiYmFzZWFwcC5Eb2NrZXJmaWxlIn0sInNvdXJjZXMiOlt7InR5cGUiOiJkb2NrZXItaW1hZ2UiLCJyZWYiOiJidXN5Ym94IiwiYWxpYXMiOiJkb2NrZXIuaW8vbGlicmFyeS9idXN5Ym94QHNoYTI1NjphZmNjN2YxYWMxYjQ5ZGIzMTdhNzE5NmM5MDJlNjFjNmMzYzQ2MDdkNjM1OTllZTFhODJkNzAyZDI0OWEwY2NiIiwicGluIjoic2hhMjU2OmFmY2M3ZjFhYzFiNDlkYjMxN2E3MTk2YzkwMmU2MWM2YzNjNDYwN2Q2MzU5OWVlMWE4MmQ3MDJkMjQ5YTBjY2IifV19\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1NjpkMzE1MDVmZDUwNTBmNmI5NmNhMzI2OGQxZGI1OGZjOTFhZTU2MWRkZjE0ZWFhYmM0MWQ2M2VhMmVmOGMxYzZkIl19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMi0wMi0wNFQyMToyMDoxMi4zMTg5MTc4MjJaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjFjODUwN2UzZTliMjJiOTc3OGYyZWRiYjk1MDA2MWUwNmJkZTZhMWY1M2I2OWUxYzYxMDI1MDAyOWMzNzNiNzIgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIyLTAyLTA0VDIxOjIwOjEyLjQ5Nzc5NDgwOVoiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCJzaFwiXSIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJXT1JLRElSIC9zcmMiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9XSwiY29uZmlnIjp7IkVudiI6WyJQQVRIPS91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2JpbiJdLCJDbWQiOlsic2giXSwiV29ya2luZ0RpciI6Ii9zcmMiLCJPbkJ1aWxkIjpudWxsfX0=\"}"), + }) + require.NoError(t, err) + require.Contains(t, deps, "0-base") + require.Equal(t, binfotypes.BuildInfo{ + Frontend: "dockerfile.v0", + Attrs: map[string]*string{ + "build-arg:bar": stringPtr("foo"), + "build-arg:foo": stringPtr("bar"), + "filename": stringPtr("baseapp.Dockerfile"), + }, + Sources: []binfotypes.Source{ + { + Type: binfotypes.SourceTypeDockerImage, + Ref: "busybox", + Alias: "docker.io/library/busybox@sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb", + Pin: "sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb", + }, + }, + }, deps["0-base"]) +} + func TestFormat(t *testing.T) { bi := binfotypes.BuildInfo{ Frontend: "dockerfile.v0", @@ -193,7 +221,7 @@ func TestReduceMap(t *testing.T) { for _, tt := range cases { tt := tt t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.expected, reduceMap(tt.m2, tt.m1)) + require.Equal(t, tt.expected, reduceMapString(tt.m2, tt.m1)) }) } } diff --git a/util/buildinfo/types/types.go b/util/buildinfo/types/types.go index 74b1e393d5559..93abcd1b4f123 100644 --- a/util/buildinfo/types/types.go +++ b/util/buildinfo/types/types.go @@ -10,7 +10,7 @@ const ImageConfigField = "moby.buildkit.buildinfo.v1" // ImageConfig defines the structure of build dependencies // inside image config. type ImageConfig struct { - BuildInfo []byte `json:"moby.buildkit.buildinfo.v1,omitempty"` + BuildInfo string `json:"moby.buildkit.buildinfo.v1,omitempty"` } // BuildInfo defines the main structure added to image config as @@ -23,6 +23,8 @@ type BuildInfo struct { Attrs map[string]*string `json:"attrs,omitempty"` // Sources defines build dependencies. Sources []Source `json:"sources,omitempty"` + // Deps defines context dependencies. + Deps map[string]BuildInfo `json:"deps,omitempty"` } // Source defines a build dependency.