diff --git a/frontend/dockerfile/builder/build.go b/frontend/dockerfile/builder/build.go index 74e60c6e0d79..e0a1806901bc 100644 --- a/frontend/dockerfile/builder/build.go +++ b/frontend/dockerfile/builder/build.go @@ -27,6 +27,7 @@ import ( "github.com/moby/buildkit/solver/errdefs" "github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/util/apicaps" + binfotypes "github.com/moby/buildkit/util/buildinfo/types" ocispecs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/sync/errgroup" @@ -811,11 +812,11 @@ func warnOpts(sm *llb.SourceMap, r *parser.Range, detail [][]byte, url string) c return opts } -func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Context, string) (*llb.State, *dockerfile2llb.Image, error) { - return func(ctx context.Context, name string) (*llb.State, *dockerfile2llb.Image, error) { +func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Context, string) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { + return func(ctx context.Context, name string) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { named, err := reference.ParseNormalizedNamed(name) if err != nil { - return nil, nil, errors.Wrapf(err, "invalid context name %s", name) + return nil, nil, nil, errors.Wrapf(err, "invalid context name %s", name) } name = strings.TrimSuffix(reference.FamiliarString(named), ":latest") @@ -825,28 +826,28 @@ func contextByNameFunc(c client.Client, p *ocispecs.Platform) func(context.Conte } if p != nil { name := name + "::" + platforms.Format(platforms.Normalize(*p)) - st, img, err := contextByName(ctx, c, name, p) + st, img, bi, err := contextByName(ctx, c, name, p) if err != nil { - return nil, nil, err + return nil, nil, nil, err } if st != nil { - return st, img, nil + return st, img, bi, nil } } return contextByName(ctx, c, name, p) } } -func contextByName(ctx context.Context, c client.Client, name string, platform *ocispecs.Platform) (*llb.State, *dockerfile2llb.Image, error) { +func contextByName(ctx context.Context, c client.Client, name string, platform *ocispecs.Platform) (*llb.State, *dockerfile2llb.Image, *binfotypes.BuildInfo, error) { opts := c.BuildOpts().Opts v, ok := opts["context:"+name] if !ok { - return nil, nil, nil + return nil, nil, nil, nil } vv := strings.SplitN(v, ":", 2) if len(vv) != 2 { - return nil, nil, errors.Errorf("invalid context specifier %s for %s", v, name) + return nil, nil, nil, errors.Errorf("invalid context specifier %s for %s", v, name) } switch vv[0] { case "docker-image": @@ -859,20 +860,20 @@ func contextByName(ctx context.Context, c client.Client, name string, platform * imgOpt = append(imgOpt, llb.Platform(*platform)) } st := llb.Image(ref, imgOpt...) - return &st, nil, nil + return &st, nil, nil, nil case "git": st, ok := detectGitContext(v, "1") if !ok { - return nil, nil, errors.Errorf("invalid git context %s", v) + return nil, nil, nil, errors.Errorf("invalid git context %s", v) } - return st, nil, nil + return st, nil, nil, nil case "http", "https": st, ok := detectGitContext(v, "1") if !ok { httpst := llb.HTTP(v, llb.WithCustomName("[context "+name+"] "+v)) st = &httpst } - return st, nil, nil + return st, nil, nil, nil case "local": st := llb.Local(vv[1], llb.SessionID(c.BuildOpts().SessionID), @@ -883,18 +884,18 @@ func contextByName(ctx context.Context, c client.Client, name string, platform * ) def, err := st.Marshal(ctx) if err != nil { - return nil, nil, err + return nil, nil, nil, err } res, err := c.Solve(ctx, client.SolveRequest{ Evaluate: true, Definition: def.ToPB(), }) if err != nil { - return nil, nil, err + return nil, nil, nil, err } ref, err := res.SingleRef() if err != nil { - return nil, nil, err + return nil, nil, nil, err } dt, _ := ref.ReadFile(ctx, client.ReadRequest{ Filename: dockerignoreFilename, @@ -903,7 +904,7 @@ func contextByName(ctx context.Context, c client.Client, name string, platform * if len(dt) != 0 { excludes, err = dockerignore.ReadAll(bytes.NewBuffer(dt)) if err != nil { - return nil, nil, err + return nil, nil, nil, err } } st = llb.Local(vv[1], @@ -912,38 +913,49 @@ func contextByName(ctx context.Context, c client.Client, name string, platform * llb.SharedKeyHint("context:"+name), llb.ExcludePatterns(excludes), ) - return &st, nil, nil + return &st, nil, nil, nil case "input": inputs, err := c.Inputs(ctx) if err != nil { - return nil, nil, err + return nil, nil, nil, err } st, ok := inputs[vv[1]] if !ok { - return nil, nil, errors.Errorf("invalid input %s for %s", vv[1], name) + return nil, nil, nil, errors.Errorf("invalid input %s for %s", vv[1], name) } md, ok := opts["input-metadata:"+vv[1]] if ok { m := make(map[string][]byte) if err := json.Unmarshal([]byte(md), &m); err != nil { - return nil, nil, errors.Wrapf(err, "failed to parse input metadata %s", md) + return nil, nil, nil, errors.Wrapf(err, "failed to parse input metadata %s", md) } - dt, ok := m["containerimage.config"] - if ok { - st, err = st.WithImageConfig([]byte(dt)) + var bi *binfotypes.BuildInfo + if dtbi, ok := m[exptypes.ExporterBuildInfo]; ok { + var depbi binfotypes.BuildInfo + if err := json.Unmarshal(dtbi, &depbi); err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to parse buildinfo for %s", name) + } + bi = &binfotypes.BuildInfo{ + Deps: map[string]binfotypes.BuildInfo{ + strings.SplitN(vv[1], "::", 2)[0]: depbi, + }, + } + } + var img *dockerfile2llb.Image + if dtic, ok := m[exptypes.ExporterImageConfigKey]; ok { + st, err = st.WithImageConfig(dtic) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - var img dockerfile2llb.Image - if err := json.Unmarshal(dt, &img); err != nil { - return nil, nil, errors.Wrapf(err, "failed to parse image config for %s", name) + if err := json.Unmarshal(dtic, &img); err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to parse image config for %s", name) } - return &st, &img, nil } + return &st, img, bi, nil } - return &st, nil, nil + return &st, nil, nil, nil default: - return nil, nil, errors.Errorf("unsupported context source %s for %s", vv[0], name) + return nil, nil, nil, errors.Errorf("unsupported context source %s for %s", vv[0], name) } } diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index 27a0d931747f..7ac6b9bdd774 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -70,18 +70,31 @@ type ConvertOpt struct { SourceMap *llb.SourceMap Hostname string Warn func(short, url string, detail [][]byte, location *parser.Range) - ContextByName func(context.Context, string) (*llb.State, *Image, error) + ContextByName func(context.Context, string) (*llb.State, *Image, *binfotypes.BuildInfo, error) } func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, *binfotypes.BuildInfo, error) { + buildInfo := &binfotypes.BuildInfo{} contextByName := opt.ContextByName - opt.ContextByName = func(ctx context.Context, name string) (*llb.State, *Image, error) { + opt.ContextByName = func(ctx context.Context, name string) (*llb.State, *Image, *binfotypes.BuildInfo, error) { if !strings.EqualFold(name, "scratch") && !strings.EqualFold(name, "context") { if contextByName != nil { - return contextByName(ctx, name) + st, img, bi, err := contextByName(ctx, name) + if err != nil { + return nil, nil, nil, err + } + if bi != nil && bi.Deps != nil { + for k := range bi.Deps { + if buildInfo.Deps == nil { + buildInfo.Deps = make(map[string]binfotypes.BuildInfo) + } + buildInfo.Deps[k] = bi.Deps[k] + } + } + return st, img, bi, nil } } - return nil, nil, nil + return nil, nil, nil, nil } if len(dt) == 0 { @@ -152,7 +165,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, } if st.Name != "" { - s, img, err := opt.ContextByName(ctx, st.Name) + s, img, bi, err := opt.ContextByName(ctx, st.Name) if err != nil { return nil, nil, nil, err } @@ -162,6 +175,9 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, if img != nil { ds.image = *img } + if bi != nil { + ds.buildInfo = *bi + } allDispatchStates.addState(ds) continue } @@ -291,7 +307,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, d.stage.BaseName = reference.TagNameOnly(ref).String() var isScratch bool - st, img, err := opt.ContextByName(ctx, d.stage.BaseName) + st, img, bi, err := opt.ContextByName(ctx, d.stage.BaseName) if err != nil { return err } @@ -301,6 +317,9 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, } else { d.image = emptyImage(platformOpt.targetPlatform) } + if bi != nil { + d.buildInfo = *bi + } d.state = *st d.platform = platform return nil @@ -349,12 +368,12 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, if !isScratch { // if image not scratch set original image name as ref // and actual reference as alias in binfotypes.Source - d.buildSource = &binfotypes.Source{ + d.buildInfo.Sources = append(d.buildInfo.Sources, binfotypes.Source{ Type: binfotypes.SourceTypeDockerImage, Ref: origName, Alias: ref.String(), Pin: dgst.String(), - } + }) } d.image = img } @@ -382,7 +401,6 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, buildContext := &mutableOutput{} ctxPaths := map[string]struct{}{} - buildInfo := &binfotypes.BuildInfo{} for _, d := range allDispatchStates.states { if !isReachable(target, d) { @@ -390,8 +408,16 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, } // collect build sources and dependencies - if d.buildSource != nil { - buildInfo.Sources = append(buildInfo.Sources, *d.buildSource) + if len(d.buildInfo.Sources) > 0 { + buildInfo.Sources = append(buildInfo.Sources, d.buildInfo.Sources...) + } + if d.buildInfo.Deps != nil { + for name, bi := range d.buildInfo.Deps { + if buildInfo.Deps == nil { + buildInfo.Deps = make(map[string]binfotypes.BuildInfo) + } + buildInfo.Deps[name] = bi + } } if d.base != nil { @@ -701,7 +727,7 @@ type dispatchState struct { cmdIndex int cmdTotal int prefixPlatform bool - buildSource *binfotypes.Source + buildInfo binfotypes.BuildInfo } type dispatchStates struct { diff --git a/frontend/dockerfile/dockerfile_buildinfo_test.go b/frontend/dockerfile/dockerfile_buildinfo_test.go index 5ee8f60b7589..84e0c24622d5 100644 --- a/frontend/dockerfile/dockerfile_buildinfo_test.go +++ b/frontend/dockerfile/dockerfile_buildinfo_test.go @@ -28,10 +28,14 @@ import ( var buildinfoTests = integration.TestFuncs( testBuildInfoSources, + testBuildInfoSourcesNoop, testBuildInfoAttrs, testBuildInfoMultiPlatform, + testBuildInfoImageContext, + testBuildInfoLocalContext, testBuildInfoDeps, testBuildInfoDepsMultiPlatform, + testBuildInfoDepsMainNoSource, ) func init() { @@ -41,6 +45,7 @@ func init() { // moby/buildkit#2311 func testBuildInfoSources(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) + f.RequiresBuildctl(t) gitDir, err := ioutil.TempDir("", "buildkit") require.NoError(t, err) @@ -103,6 +108,9 @@ COPY --from=alpine /bin/busybox /alpine-busybox err = json.Unmarshal(dtbi, &bi) require.NoError(t, err) + require.Contains(t, bi.Attrs, "context") + require.Equal(t, server.URL+"/.git#buildinfo", *bi.Attrs["context"]) + sources := bi.Sources require.Equal(t, 3, len(sources)) @@ -119,6 +127,62 @@ COPY --from=alpine /bin/busybox /alpine-busybox assert.Equal(t, "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", sources[2].Pin) } +func testBuildInfoSourcesNoop(t *testing.T, sb integration.Sandbox) { + f := getFrontend(t, sb) + f.RequiresBuildctl(t) + + dockerfile := ` +FROM busybox:latest +` + + dir, err := tmpdir( + fstest.CreateFile("Dockerfile", []byte(dockerfile), 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + c, err := client.New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + destDir, err := ioutil.TempDir("", "buildkit") + require.NoError(t, err) + defer os.RemoveAll(destDir) + + out := filepath.Join(destDir, "out.tar") + outW, err := os.Create(out) + require.NoError(t, err) + + res, err := f.Solve(sb.Context(), c, client.SolveOpt{ + Exports: []client.ExportEntry{ + { + Type: client.ExporterOCI, + Output: fixedWriteCloser(outW), + }, + }, + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + }, + }, 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) + + sources := bi.Sources + require.Equal(t, 1, len(sources)) + + assert.Equal(t, binfotypes.SourceTypeDockerImage, sources[0].Type) + assert.Equal(t, "docker.io/library/busybox:latest", sources[0].Ref) + assert.NotEmpty(t, sources[0].Pin) +} + // moby/buildkit#2476 func testBuildInfoAttrs(t *testing.T, sb integration.Sandbox) { f := getFrontend(t, sb) @@ -252,6 +316,147 @@ ADD https://raw.githubusercontent.com/moby/moby/master/README.md / } } +func testBuildInfoImageContext(t *testing.T, sb integration.Sandbox) { + f := getFrontend(t, sb) + f.RequiresBuildctl(t) + + dockerfile := ` +FROM busybox AS base +RUN cat /etc/alpine-release > /out +FROM scratch +COPY --from=base /out / +` + + dir, err := tmpdir( + fstest.CreateFile("Dockerfile", []byte(dockerfile), 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + c, err := client.New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + destDir, err := ioutil.TempDir("", "buildkit") + require.NoError(t, err) + defer os.RemoveAll(destDir) + + out := filepath.Join(destDir, "out.tar") + outW, err := os.Create(out) + require.NoError(t, err) + + res, err := f.Solve(sb.Context(), c, client.SolveOpt{ + FrontendAttrs: map[string]string{ + "build-arg:foo": "bar", + "context:busybox": "docker-image://alpine", + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterOCI, + Output: fixedWriteCloser(outW), + }, + }, + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + }, + }, 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.Contains(t, bi.Attrs, "context:busybox") + require.Equal(t, "docker-image://alpine", *bi.Attrs["context:busybox"]) + require.Contains(t, bi.Attrs, "build-arg:foo") + require.Equal(t, "bar", *bi.Attrs["build-arg:foo"]) + + sources := bi.Sources + require.Equal(t, 1, len(sources)) + assert.Equal(t, binfotypes.SourceTypeDockerImage, sources[0].Type) + assert.Equal(t, "docker.io/library/alpine:latest", sources[0].Ref) + assert.NotEmpty(t, sources[0].Pin) +} + +func testBuildInfoLocalContext(t *testing.T, sb integration.Sandbox) { + f := getFrontend(t, sb) + f.RequiresBuildctl(t) + + dockerfile := ` +FROM busybox AS base +RUN cat /etc/alpine-release > /out +FROM scratch +COPY --from=base /o* / +` + + dir, err := tmpdir( + fstest.CreateFile("Dockerfile", []byte(dockerfile), 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir) + + c, err := client.New(sb.Context(), sb.Address()) + require.NoError(t, err) + defer c.Close() + + destDir, err := ioutil.TempDir("", "buildkit") + require.NoError(t, err) + defer os.RemoveAll(destDir) + + out := filepath.Join(destDir, "out.tar") + outW, err := os.Create(out) + require.NoError(t, err) + + outf := []byte(`dummy-result`) + + dir2, err := tmpdir( + fstest.CreateFile("out", outf, 0600), + fstest.CreateFile("out2", outf, 0600), + fstest.CreateFile(".dockerignore", []byte("out2\n"), 0600), + ) + require.NoError(t, err) + defer os.RemoveAll(dir2) + + res, err := f.Solve(sb.Context(), c, client.SolveOpt{ + FrontendAttrs: map[string]string{ + "build-arg:foo": "bar", + "context:base": "local:basedir", + }, + Exports: []client.ExportEntry{ + { + Type: client.ExporterOCI, + Output: fixedWriteCloser(outW), + }, + }, + LocalDirs: map[string]string{ + builder.DefaultLocalNameDockerfile: dir, + builder.DefaultLocalNameContext: dir, + "basedir": dir2, + }, + }, 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.Contains(t, bi.Attrs, "context:base") + require.Equal(t, "local:basedir", *bi.Attrs["context:base"]) + require.Contains(t, bi.Attrs, "build-arg:foo") + require.Equal(t, "bar", *bi.Attrs["build-arg:foo"]) + + require.Equal(t, 0, len(bi.Sources)) +} + func testBuildInfoDeps(t *testing.T, sb integration.Sandbox) { ctx := sb.Context() f := getFrontend(t, sb) @@ -264,6 +469,7 @@ func testBuildInfoDeps(t *testing.T, sb integration.Sandbox) { dockerfile := []byte(` FROM alpine ENV FOO=bar +ADD https://raw.githubusercontent.com/moby/moby/master/README.md / RUN echo first > /out `) @@ -366,19 +572,24 @@ COPY --from=build /foo /out / err = json.Unmarshal(dtbi, &bi) require.NoError(t, err) + require.Contains(t, bi.Attrs, "context:base") + require.Equal(t, "input:base", *bi.Attrs["context:base"]) + 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.Equal(t, "docker.io/library/busybox:latest", bi.Sources[0].Ref) 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) + + assert.Equal(t, binfotypes.SourceTypeHTTP, bi.Sources[1].Type) + assert.Equal(t, "https://raw.githubusercontent.com/moby/moby/master/README.md", bi.Sources[1].Ref) + assert.Equal(t, "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", 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.Equal(t, "docker.io/library/alpine:latest", depsrc[0].Ref) assert.NotEmpty(t, depsrc[0].Pin) } @@ -508,19 +719,149 @@ COPY --from=build /foo /out / err = json.Unmarshal(dtbi, &bi) require.NoError(t, err) - require.Equal(t, 2, len(bi.Sources)) + require.Contains(t, bi.Attrs, "context:base") + require.Equal(t, "input:base", *bi.Attrs["context:base"]) + + require.Equal(t, 1, 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.Equal(t, "docker.io/library/busybox:latest", bi.Sources[0].Ref) 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.Equal(t, "docker.io/library/alpine:latest", depsrc[0].Ref) assert.NotEmpty(t, depsrc[0].Pin) } } + +func testBuildInfoDepsMainNoSource(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 +ADD https://raw.githubusercontent.com/moby/moby/master/README.md / +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 +`) + + 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.Contains(t, bi.Attrs, "context:base") + require.Equal(t, "input:base", *bi.Attrs["context:base"]) + + require.Equal(t, 1, len(bi.Sources)) + + assert.Equal(t, binfotypes.SourceTypeHTTP, bi.Sources[0].Type) + assert.Equal(t, "https://raw.githubusercontent.com/moby/moby/master/README.md", bi.Sources[0].Ref) + assert.Equal(t, "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", bi.Sources[0].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, "docker.io/library/alpine:latest", depsrc[0].Ref) + assert.NotEmpty(t, depsrc[0].Pin) +} diff --git a/frontend/gateway/gateway.go b/frontend/gateway/gateway.go index 1f8070f7390f..842e3252f377 100644 --- a/frontend/gateway/gateway.go +++ b/frontend/gateway/gateway.go @@ -39,6 +39,7 @@ import ( opspb "github.com/moby/buildkit/solver/pb" "github.com/moby/buildkit/util/apicaps" "github.com/moby/buildkit/util/bklog" + "github.com/moby/buildkit/util/buildinfo" "github.com/moby/buildkit/util/grpcerrors" "github.com/moby/buildkit/util/stack" "github.com/moby/buildkit/util/tracing" @@ -663,6 +664,16 @@ func (lbf *llbBridgeForwarder) Solve(ctx context.Context, req *pb.SolveRequest) if ref == nil { id = "" } else { + dtbi, err := buildinfo.Encode(ctx, pbRes.Metadata, fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, k), ref.BuildSources()) + if err != nil { + return nil, err + } + if dtbi != nil && len(dtbi) > 0 { + if pbRes.Metadata == nil { + pbRes.Metadata = make(map[string][]byte) + } + pbRes.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, k)] = dtbi + } lbf.refs[id] = ref } ids[k] = id @@ -686,6 +697,16 @@ func (lbf *llbBridgeForwarder) Solve(ctx context.Context, req *pb.SolveRequest) if ref == nil { id = "" } else { + dtbi, err := buildinfo.Encode(ctx, pbRes.Metadata, exptypes.ExporterBuildInfo, ref.BuildSources()) + if err != nil { + return nil, err + } + if dtbi != nil && len(dtbi) > 0 { + if pbRes.Metadata == nil { + pbRes.Metadata = make(map[string][]byte) + } + pbRes.Metadata[exptypes.ExporterBuildInfo] = dtbi + } def = ref.Definition() lbf.refs[id] = ref } diff --git a/solver/llbsolver/bridge.go b/solver/llbsolver/bridge.go index 7cba1a5b15fc..bd31bbfdc68b 100644 --- a/solver/llbsolver/bridge.go +++ b/solver/llbsolver/bridge.go @@ -159,24 +159,30 @@ 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) - } - if len(res.Refs) > 0 { for p := range res.Refs { dtbi, err := buildinfo.GetMetadata(res.Metadata, fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, p), req.Frontend, req.FrontendOpt) if err != nil { return nil, err } - res.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, p)] = dtbi + if dtbi != nil && len(dtbi) > 0 { + if res.Metadata == nil { + res.Metadata = make(map[string][]byte) + } + res.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, p)] = dtbi + } } } else { dtbi, err := buildinfo.GetMetadata(res.Metadata, exptypes.ExporterBuildInfo, req.Frontend, req.FrontendOpt) if err != nil { return nil, err } - res.Metadata[exptypes.ExporterBuildInfo] = dtbi + if dtbi != nil && len(dtbi) > 0 { + if res.Metadata == nil { + res.Metadata = make(map[string][]byte) + } + res.Metadata[exptypes.ExporterBuildInfo] = dtbi + } } return diff --git a/solver/llbsolver/solver.go b/solver/llbsolver/solver.go index 90650a3e49d1..ee06233da5ad 100644 --- a/solver/llbsolver/solver.go +++ b/solver/llbsolver/solver.go @@ -158,15 +158,15 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro return nil, err } - if res.Metadata == nil { - res.Metadata = make(map[string][]byte) - } if r := res.Ref; r != nil { dtbi, err := buildinfo.Encode(ctx, res.Metadata, exptypes.ExporterBuildInfo, r.BuildSources()) if err != nil { return nil, err } if dtbi != nil && len(dtbi) > 0 { + if res.Metadata == nil { + res.Metadata = make(map[string][]byte) + } res.Metadata[exptypes.ExporterBuildInfo] = dtbi } } @@ -180,6 +180,9 @@ func (s *Solver) Solve(ctx context.Context, id string, sessionID string, req fro return nil, err } if dtbi != nil && len(dtbi) > 0 { + if res.Metadata == nil { + res.Metadata = make(map[string][]byte) + } res.Metadata[fmt.Sprintf("%s/%s", exptypes.ExporterBuildInfo, k)] = dtbi } } diff --git a/util/buildinfo/buildinfo.go b/util/buildinfo/buildinfo.go index e419cf24c1b0..5adaa9b36f75 100644 --- a/util/buildinfo/buildinfo.go +++ b/util/buildinfo/buildinfo.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + ctnref "github.com/containerd/containerd/reference" "github.com/docker/distribution/reference" "github.com/moby/buildkit/exporter/containerimage/exptypes" "github.com/moby/buildkit/source" @@ -26,7 +27,7 @@ func Decode(enc string) (bi binfotypes.BuildInfo, _ error) { } // Encode encodes build info. -func Encode(ctx context.Context, metadata map[string][]byte, key string, buildSources map[string]string) ([]byte, error) { +func Encode(ctx context.Context, metadata map[string][]byte, key string, llbSources map[string]string) ([]byte, error) { var bi binfotypes.BuildInfo if metadata == nil { metadata = make(map[string][]byte) @@ -36,46 +37,48 @@ func Encode(ctx context.Context, metadata map[string][]byte, key string, buildSo return nil, err } } - if deps, err := decodeDeps(key, bi.Attrs); err == nil { - bi.Deps = reduceMapBuildInfo(deps, bi.Deps) - } else { - return nil, err - } - if sources, err := mergeSources(ctx, buildSources, bi.Sources); err == nil { + if sources, err := mergeSources(llbSources, bi.Sources); err == nil { bi.Sources = sources } else { return nil, err } - bi.Attrs = filterAttrs(key, bi.Attrs) + bi.Sources = dedupSources(bi, allDepsSources(bi, nil)) return json.Marshal(bi) } // mergeSources combines and fixes build sources from frontend sources. -func mergeSources(ctx context.Context, buildSources map[string]string, frontendSources []binfotypes.Source) ([]binfotypes.Source, error) { - // Iterate and combine build sources +func mergeSources(llbSources map[string]string, frontendSources []binfotypes.Source) ([]binfotypes.Source, error) { + if llbSources == nil { + llbSources = make(map[string]string) + } + // iterate and combine build sources mbs := map[string]binfotypes.Source{} - for buildSource, pin := range buildSources { - src, err := source.FromString(buildSource) + for llbSource, pin := range llbSources { + src, err := source.FromString(llbSource) if err != nil { return nil, err } switch sourceID := src.(type) { case *source.ImageIdentifier: for i, fsrc := range frontendSources { + if fsrc.Type != binfotypes.SourceTypeDockerImage { + continue + } // use original user input from frontend sources - if fsrc.Type == binfotypes.SourceTypeDockerImage && fsrc.Alias == sourceID.Reference.String() { - if _, ok := mbs[fsrc.Alias]; !ok { - parsed, err := reference.ParseNormalizedNamed(fsrc.Ref) - if err != nil { - return nil, errors.Wrapf(err, "failed to parse %s", fsrc.Ref) - } - mbs[fsrc.Alias] = binfotypes.Source{ - Type: binfotypes.SourceTypeDockerImage, - Ref: reference.TagNameOnly(parsed).String(), - Pin: pin, - } - frontendSources = append(frontendSources[:i], frontendSources[i+1:]...) + if fsrc.Alias == sourceID.Reference.String() || fsrc.Pin == pin { + if fsrc.Alias == "" { + fsrc.Alias = sourceID.Reference.String() } + parsed, err := reference.ParseNormalizedNamed(fsrc.Ref) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse %s", fsrc.Ref) + } + mbs[fsrc.Alias] = binfotypes.Source{ + Type: binfotypes.SourceTypeDockerImage, + Ref: reference.TagNameOnly(parsed).String(), + Pin: pin, + } + frontendSources = append(frontendSources[:i], frontendSources[i+1:]...) break } } @@ -147,8 +150,7 @@ func mergeSources(ctx context.Context, buildSources map[string]string, frontendS func decodeDeps(key string, attrs map[string]*string) (map[string]binfotypes.BuildInfo, error) { var platform string // extract platform from metadata key - skey := strings.SplitN(key, "/", 2) - if len(skey) == 2 { + if skey := strings.SplitN(key, "/", 2); len(skey) == 2 { platform = skey[1] } @@ -159,8 +161,10 @@ func decodeDeps(key string, attrs map[string]*string) (map[string]binfotypes.Bui continue } - // if platform is defined, only decode dependencies for that platform - if platform != "" && !strings.HasSuffix(k, "::"+platform) { + // if platform is defined in the key, only decode dependencies + // for that platform and vice versa + hasPlatform := len(strings.SplitN(k, "::", 2)) == 2 + if (platform != "" && !hasPlatform) || (platform == "" && hasPlatform) { continue } @@ -184,7 +188,10 @@ func decodeDeps(key string, attrs map[string]*string) (map[string]binfotypes.Bui // set dep key var depkey string kl := strings.SplitN(k, ":", 2) - depkey = kl[1] + if len(kl) != 2 { + continue + } + depkey = strings.SplitN(kl[1], "::", 2)[0] if platform != "" { depkey = strings.TrimSuffix(depkey, "::"+platform) } @@ -197,6 +204,58 @@ func decodeDeps(key string, attrs map[string]*string) (map[string]binfotypes.Bui return res, nil } +// dedupSources deduplicates regular sources from dependencies ones. +func dedupSources(bi binfotypes.BuildInfo, depsSources []binfotypes.Source) (srcs []binfotypes.Source) { + // dedup sources from deps + for i, src := range bi.Sources { + for _, dsrc := range depsSources { + if src == dsrc { + bi.Sources = append(bi.Sources[:i], bi.Sources[i+1:]...) + } else if src.Type == binfotypes.SourceTypeDockerImage { + _, dgst := ctnref.SplitObject(src.Ref) + if dgst != "" && src.Pin == dsrc.Pin { + bi.Sources = append(bi.Sources[:i], bi.Sources[i+1:]...) + } + } + } + } + // dedup regular sources + msrc := make(map[binfotypes.Source]struct{}) + for _, src := range bi.Sources { + msrc[src] = struct{}{} + } + for src := range msrc { + srcs = append(srcs, src) + } + sort.Slice(srcs, func(i, j int) bool { + return srcs[i].Ref < srcs[j].Ref + }) + return srcs +} + +// allDepsSources gathers dependencies sources. +func allDepsSources(bi binfotypes.BuildInfo, visited map[binfotypes.Source]struct{}) (res []binfotypes.Source) { + if visited == nil { + visited = make(map[binfotypes.Source]struct{}) + } + if len(bi.Deps) == 0 { + return res + } + for _, dbi := range bi.Deps { + for _, dsrc := range dbi.Sources { + if _, ok := visited[dsrc]; ok { + continue + } + visited[dsrc] = struct{}{} + } + res = allDepsSources(dbi, visited) + } + for src := range visited { + res = append(res, src) + } + return res +} + // FormatOpts holds build info format options. type FormatOpts struct { RemoveAttrs bool @@ -263,16 +322,20 @@ func filterAttrs(key string, attrs map[string]*string) map[string]*string { // input context key and value has to be cleaned up // before being included if strings.HasPrefix(k, "context:") { - if platform != "" { - // if platform is defined, only include the relevant platform - if !strings.HasSuffix(k, "::"+platform) { - continue - } - ctxival := strings.TrimSuffix(*v, "::"+platform) - filtered[strings.TrimSuffix(k, "::"+platform)] = &ctxival + ctxkey := strings.SplitN(k, "::", 2) + hasCtxPlatform := len(ctxkey) == 2 + // if platform is set and also defined in key, set context + // for the right one. + if hasCtxPlatform && platform != "" && platform != ctxkey[1] { continue } - filtered[k] = v + if platform == "" && hasCtxPlatform { + ctxval := strings.TrimSuffix(*v, "::"+ctxkey[1]) + filtered[strings.TrimSuffix(k, "::"+ctxkey[1])] = &ctxval + continue + } + ctxival := strings.TrimSuffix(*v, "::"+platform) + filtered[strings.TrimSuffix(k, "::"+platform)] = &ctxival continue } // filter only for known attributes @@ -349,25 +412,6 @@ func GetMetadata(metadata map[string][]byte, key string, reqFrontend string, req return dtbi, nil } -// 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 diff --git a/util/buildinfo/buildinfo_test.go b/util/buildinfo/buildinfo_test.go index 3f13656c5ad5..6c02c25dfcbb 100644 --- a/util/buildinfo/buildinfo_test.go +++ b/util/buildinfo/buildinfo_test.go @@ -1,7 +1,6 @@ package buildinfo import ( - "context" "encoding/json" "testing" @@ -55,7 +54,7 @@ func TestMergeSources(t *testing.T) { }, } - srcs, err := mergeSources(context.Background(), buildSourcesLLB, frontendSources) + srcs, err := mergeSources(buildSourcesLLB, frontendSources) require.NoError(t, err) assert.Equal(t, []binfotypes.Source{ @@ -107,7 +106,7 @@ func TestDecodeDeps(t *testing.T) { "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=\"}"), + "input-metadata:0-base": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJhdHRycyI6eyJidWlsZC1hcmc6YmFyIjoiZm9vIiwiYnVpbGQtYXJnOmZvbyI6ImJhciIsImZpbGVuYW1lIjoiYmFzZWFwcC5Eb2NrZXJmaWxlIn0sInNvdXJjZXMiOlt7InR5cGUiOiJkb2NrZXItaW1hZ2UiLCJyZWYiOiJkb2NrZXIuaW8vbGlicmFyeS9idXN5Ym94OmxhdGVzdCIsInBpbiI6InNoYTI1NjphZmNjN2YxYWMxYjQ5ZGIzMTdhNzE5NmM5MDJlNjFjNmMzYzQ2MDdkNjM1OTllZTFhODJkNzAyZDI0OWEwY2NiIn1dfQ==\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1NjpkMzE1MDVmZDUwNTBmNmI5NmNhMzI2OGQxZGI1OGZjOTFhZTU2MWRkZjE0ZWFhYmM0MWQ2M2VhMmVmOGMxYzZkIl19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMi0wMi0wNFQyMToyMDoxMi4zMTg5MTc4MjJaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjFjODUwN2UzZTliMjJiOTc3OGYyZWRiYjk1MDA2MWUwNmJkZTZhMWY1M2I2OWUxYzYxMDI1MDAyOWMzNzNiNzIgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIyLTAyLTA0VDIxOjIwOjEyLjQ5Nzc5NDgwOVoiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCJzaFwiXSIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJXT1JLRElSIC9zcmMiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCJ9XSwiY29uZmlnIjp7IkVudiI6WyJQQVRIPS91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2JpbiJdLCJDbWQiOlsic2giXSwiV29ya2luZ0RpciI6Ii9zcmMiLCJPbkJ1aWxkIjpudWxsfX0=\"}"), }, want: map[string]binfotypes.BuildInfo{ "0-base": { @@ -119,10 +118,9 @@ func TestDecodeDeps(t *testing.T) { }, Sources: []binfotypes.Source{ { - Type: binfotypes.SourceTypeDockerImage, - Ref: "busybox", - Alias: "docker.io/library/busybox@sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb", - Pin: "sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb", + Type: binfotypes.SourceTypeDockerImage, + Ref: "docker.io/library/busybox:latest", + Pin: "sha256:afcc7f1ac1b49db317a7196c902e61c6c3c4607d63599ee1a82d702d249a0ccb", }, }, }, @@ -135,8 +133,8 @@ func TestDecodeDeps(t *testing.T) { "context:base::linux/amd64": stringPtr("input:base::linux/amd64"), "context:base::linux/arm64": stringPtr("input:base::linux/arm64"), "dockerfilekey": stringPtr("dockerfile2"), - "input-metadata:base::linux/amd64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiYWxwaW5lIiwiYWxpYXMiOiJkb2NrZXIuaW8vbGlicmFyeS9hbHBpbmVAc2hhMjU2OmU3ZDg4ZGU3M2RiM2QzZmQ5YjJkNjNhYTdmNDQ3YTEwZmQwMjIwYjdjYmYzOTgwM2M4MDNmMmFmOWJhMjU2YjMiLCJwaW4iOiJzaGEyNTY6ZTdkODhkZTczZGIzZDNmZDliMmQ2M2FhN2Y0NDdhMTBmZDAyMjBiN2NiZjM5ODAzYzgwM2YyYWY5YmEyNTZiMyJ9XX0=\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYW1kNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hbWQ2NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFtZDY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), - "input-metadata:base::linux/arm64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiYWxwaW5lIiwiYWxpYXMiOiJkb2NrZXIuaW8vbGlicmFyeS9hbHBpbmVAc2hhMjU2OmU3ZDg4ZGU3M2RiM2QzZmQ5YjJkNjNhYTdmNDQ3YTEwZmQwMjIwYjdjYmYzOTgwM2M4MDNmMmFmOWJhMjU2YjMiLCJwaW4iOiJzaGEyNTY6ZTdkODhkZTczZGIzZDNmZDliMmQ2M2FhN2Y0NDdhMTBmZDAyMjBiN2NiZjM5ODAzYzgwM2YyYWY5YmEyNTZiMyJ9XX0=\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYXJtNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hcm02NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFybTY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), + "input-metadata:base::linux/amd64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvYWxwaW5lOmxhdGVzdCIsInBpbiI6InNoYTI1NjplN2Q4OGRlNzNkYjNkM2ZkOWIyZDYzYWE3ZjQ0N2ExMGZkMDIyMGI3Y2JmMzk4MDNjODAzZjJhZjliYTI1NmIzIn1dfQ==\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYW1kNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hbWQ2NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFtZDY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), + "input-metadata:base::linux/arm64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvYWxwaW5lOmxhdGVzdCIsInBpbiI6InNoYTI1NjplN2Q4OGRlNzNkYjNkM2ZkOWIyZDYzYWE3ZjQ0N2ExMGZkMDIyMGI3Y2JmMzk4MDNjODAzZjJhZjliYTI1NmIzIn1dfQ==\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYXJtNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hcm02NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFybTY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), "platform": stringPtr("linux/amd64,linux/arm64"), }, want: map[string]binfotypes.BuildInfo{ @@ -145,10 +143,9 @@ func TestDecodeDeps(t *testing.T) { Attrs: nil, Sources: []binfotypes.Source{ { - Type: binfotypes.SourceTypeDockerImage, - Ref: "alpine", - Alias: "docker.io/library/alpine@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", - Pin: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", + Type: binfotypes.SourceTypeDockerImage, + Ref: "docker.io/library/alpine:latest", + Pin: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", }, }, }, @@ -165,6 +162,116 @@ func TestDecodeDeps(t *testing.T) { } } +func TestDedupSources(t *testing.T) { + cases := []struct { + name string + bi binfotypes.BuildInfo + want []binfotypes.Source + }{ + { + name: "deps", + bi: binfotypes.BuildInfo{ + Frontend: "dockerfile.v0", + Attrs: map[string]*string{ + "context:base": stringPtr("input:base"), + }, + Sources: []binfotypes.Source{ + { + Type: "docker-image", + Ref: "docker.io/library/alpine@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", + Pin: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", + }, + { + Type: "docker-image", + Ref: "docker.io/library/busybox:latest", + Pin: "sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578", + }, + { + Type: "http", + Ref: "https://raw.githubusercontent.com/moby/moby/master/README.md", + Pin: "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", + }, + }, + Deps: map[string]binfotypes.BuildInfo{ + "base": { + Frontend: "dockerfile.v0", + Sources: []binfotypes.Source{ + { + Type: "docker-image", + Ref: "docker.io/library/alpine:latest", + Pin: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", + }, + }, + }, + }, + }, + want: []binfotypes.Source{ + { + Type: "docker-image", + Ref: "docker.io/library/busybox:latest", + Pin: "sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578", + }, + { + Type: "http", + Ref: "https://raw.githubusercontent.com/moby/moby/master/README.md", + Pin: "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c", + }, + }, + }, + { + name: "regular", + bi: binfotypes.BuildInfo{ + Frontend: "dockerfile.v0", + Attrs: map[string]*string{ + "context:base": stringPtr("input:base"), + }, + Sources: []binfotypes.Source{ + { + Type: "docker-image", + Ref: "docker.io/library/alpine@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", + Pin: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", + }, + { + Type: "docker-image", + Ref: "docker.io/library/busybox:latest", + Pin: "sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578", + }, + { + Type: "docker-image", + Ref: "docker.io/library/busybox:latest", + Pin: "sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578", + }, + }, + Deps: map[string]binfotypes.BuildInfo{ + "base": { + Frontend: "dockerfile.v0", + Sources: []binfotypes.Source{ + { + Type: "docker-image", + Ref: "docker.io/library/alpine:latest", + Pin: "sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3", + }, + }, + }, + }, + }, + want: []binfotypes.Source{ + { + Type: "docker-image", + Ref: "docker.io/library/busybox:latest", + Pin: "sha256:b69959407d21e8a062e0416bf13405bb2b71ed7a84dde4158ebafacfa06f5578", + }, + }, + }, + } + for _, tt := range cases { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, dedupSources(tt.bi, allDepsSources(tt.bi, nil))) + }) + } +} + func TestFilterAttrs(t *testing.T) { cases := []struct { name string @@ -198,8 +305,8 @@ func TestFilterAttrs(t *testing.T) { "context:base::linux/amd64": stringPtr("input:base::linux/amd64"), "context:base::linux/arm64": stringPtr("input:base::linux/arm64"), "dockerfilekey": stringPtr("dockerfile2"), - "input-metadata:base::linux/amd64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiYWxwaW5lIiwiYWxpYXMiOiJkb2NrZXIuaW8vbGlicmFyeS9hbHBpbmVAc2hhMjU2OmU3ZDg4ZGU3M2RiM2QzZmQ5YjJkNjNhYTdmNDQ3YTEwZmQwMjIwYjdjYmYzOTgwM2M4MDNmMmFmOWJhMjU2YjMiLCJwaW4iOiJzaGEyNTY6ZTdkODhkZTczZGIzZDNmZDliMmQ2M2FhN2Y0NDdhMTBmZDAyMjBiN2NiZjM5ODAzYzgwM2YyYWY5YmEyNTZiMyJ9XX0=\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYW1kNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hbWQ2NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFtZDY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), - "input-metadata:base::linux/arm64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiYWxwaW5lIiwiYWxpYXMiOiJkb2NrZXIuaW8vbGlicmFyeS9hbHBpbmVAc2hhMjU2OmU3ZDg4ZGU3M2RiM2QzZmQ5YjJkNjNhYTdmNDQ3YTEwZmQwMjIwYjdjYmYzOTgwM2M4MDNmMmFmOWJhMjU2YjMiLCJwaW4iOiJzaGEyNTY6ZTdkODhkZTczZGIzZDNmZDliMmQ2M2FhN2Y0NDdhMTBmZDAyMjBiN2NiZjM5ODAzYzgwM2YyYWY5YmEyNTZiMyJ9XX0=\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYXJtNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hcm02NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFybTY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), + "input-metadata:base::linux/amd64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvYWxwaW5lOmxhdGVzdCIsInBpbiI6InNoYTI1NjplN2Q4OGRlNzNkYjNkM2ZkOWIyZDYzYWE3ZjQ0N2ExMGZkMDIyMGI3Y2JmMzk4MDNjODAzZjJhZjliYTI1NmIzIn1dfQ==\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYW1kNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hbWQ2NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFtZDY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), + "input-metadata:base::linux/arm64": stringPtr("{\"containerimage.buildinfo\":\"eyJmcm9udGVuZCI6ImRvY2tlcmZpbGUudjAiLCJzb3VyY2VzIjpbeyJ0eXBlIjoiZG9ja2VyLWltYWdlIiwicmVmIjoiZG9ja2VyLmlvL2xpYnJhcnkvYWxwaW5lOmxhdGVzdCIsInBpbiI6InNoYTI1NjplN2Q4OGRlNzNkYjNkM2ZkOWIyZDYzYWE3ZjQ0N2ExMGZkMDIyMGI3Y2JmMzk4MDNjODAzZjJhZjliYTI1NmIzIn1dfQ==\",\"containerimage.config\":\"eyJhcmNoaXRlY3R1cmUiOiJhcm02NCIsIm9zIjoibGludXgiLCJyb290ZnMiOnsidHlwZSI6ImxheWVycyIsImRpZmZfaWRzIjpbInNoYTI1Njo4ZDNhYzM0ODk5OTY0MjNmNTNkNjA4N2M4MTE4MDAwNjI2M2I3OWYyMDZkM2ZkZWM5ZTY2ZjBlMjdjZWI4NzU5Il19LCJoaXN0b3J5IjpbeyJjcmVhdGVkIjoiMjAyMS0xMS0yNFQyMDoxOTo0MC4xOTk3MDA5NDZaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApIEFERCBmaWxlOjkyMzNmNmYyMjM3ZDc5NjU5YTk1MjFmN2UzOTBkZjIxN2NlYzQ5ZjFhOGFhM2ExMjE0N2JiY2ExOTU2YWNkYjkgaW4gLyAifSx7ImNyZWF0ZWQiOiIyMDIxLTExLTI0VDIwOjE5OjQwLjQ4MzM2NzU0NloiLCJjcmVhdGVkX2J5IjoiL2Jpbi9zaCAtYyAjKG5vcCkgIENNRCBbXCIvYmluL3NoXCJdIiwiZW1wdHlfbGF5ZXIiOnRydWV9LHsiY3JlYXRlZF9ieSI6IkFSRyBUQVJHRVRBUkNIIiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAiLCJlbXB0eV9sYXllciI6dHJ1ZX0seyJjcmVhdGVkX2J5IjoiRU5WIEZPTz1iYXItYXJtNjQiLCJjb21tZW50IjoiYnVpbGRraXQuZG9ja2VyZmlsZS52MCIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWRfYnkiOiJSVU4gfDEgVEFSR0VUQVJDSD1hcm02NCAvYmluL3NoIC1jIGVjaG8gXCJmb28gJFRBUkdFVEFSQ0hcIiBcdTAwM2UgL291dCAjIGJ1aWxka2l0IiwiY29tbWVudCI6ImJ1aWxka2l0LmRvY2tlcmZpbGUudjAifV0sImNvbmZpZyI6eyJFbnYiOlsiUEFUSD0vdXNyL2xvY2FsL3NiaW46L3Vzci9sb2NhbC9iaW46L3Vzci9zYmluOi91c3IvYmluOi9zYmluOi9iaW4iLCJGT089YmFyLWFybTY0Il0sIkNtZCI6WyIvYmluL3NoIl0sIk9uQnVpbGQiOm51bGx9fQ==\"}"), "platform": stringPtr("linux/amd64,linux/arm64"), }, want: map[string]*string{ diff --git a/util/imageutil/buildinfo.go b/util/imageutil/buildinfo.go new file mode 100644 index 000000000000..2ef1e75cfc0b --- /dev/null +++ b/util/imageutil/buildinfo.go @@ -0,0 +1,32 @@ +package imageutil + +import ( + "encoding/base64" + "encoding/json" + + binfotypes "github.com/moby/buildkit/util/buildinfo/types" + "github.com/pkg/errors" +) + +// BuildInfo returns build info from image config. +func BuildInfo(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 + } + dtbi, err := base64.StdEncoding.DecodeString(config.BuildInfo) + if err != nil { + return nil, err + } + var bi binfotypes.BuildInfo + if err = json.Unmarshal(dtbi, &bi); err != nil { + return nil, errors.Wrap(err, "failed to decode buildinfo from image config") + } + return &bi, nil +}