From 531c6d4ff109bd792bdcb8791f8812a486b3936a Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:42:23 +0200 Subject: [PATCH] support metadata file with call flag for build and bake commands Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- commands/bake.go | 8 ++++--- commands/build.go | 29 ++++++++++++++++------- tests/bake.go | 60 +++++++++++++++++++++++++++++++++++++++++++++++ tests/build.go | 38 ++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/commands/bake.go b/commands/bake.go index 9815c802..3d7fce87 100644 --- a/commands/bake.go +++ b/commands/bake.go @@ -252,13 +252,15 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba if progressMode != progressui.QuietMode && progressMode != progressui.RawJSONMode { desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term) } - if callFunc == nil && len(in.metadataFile) > 0 { + if len(in.metadataFile) > 0 { dt := make(map[string]interface{}) for t, r := range resp { dt[t] = decodeExporterResponse(r.ExporterResponse) } - if warnings := printer.Warnings(); len(warnings) > 0 && confutil.MetadataWarningsEnabled() { - dt["buildx.build.warnings"] = warnings + if callFunc == nil { + if warnings := printer.Warnings(); len(warnings) > 0 && confutil.MetadataWarningsEnabled() { + dt["buildx.build.warnings"] = warnings + } } if err := writeMetadataFile(in.metadataFile, dt); err != nil { return err diff --git a/commands/build.go b/commands/build.go index 7359a194..b495e93b 100644 --- a/commands/build.go +++ b/commands/build.go @@ -367,20 +367,23 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) return errors.Wrap(err, "writing image ID file") } } + if options.metadataFile != "" { + dt := decodeExporterResponse(resp.ExporterResponse) + if opts.PrintFunc == nil { + if warnings := printer.Warnings(); len(warnings) > 0 && confutil.MetadataWarningsEnabled() { + dt["buildx.build.warnings"] = warnings + } + } + if err := writeMetadataFile(options.metadataFile, dt); err != nil { + return err + } + } if opts.PrintFunc != nil { if exitcode, err := printResult(dockerCli.Out(), opts.PrintFunc, resp.ExporterResponse); err != nil { return err } else if exitcode != 0 { os.Exit(exitcode) } - } else if options.metadataFile != "" { - dt := decodeExporterResponse(resp.ExporterResponse) - if warnings := printer.Warnings(); len(warnings) > 0 && confutil.MetadataWarningsEnabled() { - dt["buildx.build.warnings"] = warnings - } - if err := writeMetadataFile(options.metadataFile, dt); err != nil { - return err - } } return nil } @@ -731,9 +734,17 @@ func writeMetadataFile(filename string, dt interface{}) error { } func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} { + decFunc := func(k, v string) ([]byte, error) { + if k == "result.json" { + // result.json is part of metadata response for subrequests which + // is already a JSON object: https://github.com/moby/buildkit/blob/f6eb72f2f5db07ddab89ac5e2bd3939a6444f4be/frontend/dockerui/requests.go#L100-L102 + return []byte(v), nil + } + return base64.StdEncoding.DecodeString(v) + } out := make(map[string]interface{}) for k, v := range exporterResponse { - dt, err := base64.StdEncoding.DecodeString(v) + dt, err := decFunc(k, v) if err != nil { out[k] = v continue diff --git a/tests/bake.go b/tests/bake.go index 390a2aad..39b9c9a9 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -13,6 +13,7 @@ import ( "github.com/docker/buildx/bake" "github.com/docker/buildx/util/gitutil" "github.com/moby/buildkit/client" + "github.com/moby/buildkit/frontend/subrequests/lint" "github.com/moby/buildkit/identity" provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types" "github.com/moby/buildkit/util/contentutil" @@ -56,6 +57,7 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){ testListVariables, testBakeCallCheck, testBakeCallCheckFlag, + testBakeCallMetadata, testBakeMultiPlatform, } @@ -1214,3 +1216,61 @@ target "another" { require.Len(t, warnings, 1) } + +func testBakeCallMetadata(t *testing.T, sb integration.Sandbox) { + dockerfile := []byte(` +frOM busybox as base +cOpy Dockerfile . +from scratch +COPy --from=base \ + /Dockerfile \ + / + `) + bakefile := []byte(` +target "default" {} +`) + dir := tmpdir( + t, + fstest.CreateFile("docker-bake.hcl", bakefile, 0600), + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + + cmd := buildxCmd( + sb, + withDir(dir), + withArgs("bake", "--call", "check,format=json", "--metadata-file", filepath.Join(dir, "md.json")), + ) + stdout := bytes.Buffer{} + stderr := bytes.Buffer{} + cmd.Stdout = &stdout + cmd.Stderr = &stderr + require.Error(t, cmd.Run(), stdout.String(), stderr.String()) + + var res map[string]any + require.NoError(t, json.Unmarshal(stdout.Bytes(), &res), stdout.String()) + targets, ok := res["target"].(map[string]any) + require.True(t, ok) + def, ok := targets["default"].(map[string]any) + require.True(t, ok) + _, ok = def["build"] + require.True(t, ok) + check, ok := def["check"].(map[string]any) + require.True(t, ok) + warnings, ok := check["warnings"].([]any) + require.True(t, ok) + require.Len(t, warnings, 3) + + dt, err := os.ReadFile(filepath.Join(dir, "md.json")) + require.NoError(t, err) + + type mdT struct { + Default struct { + BuildRef string `json:"buildx.build.ref"` + ResultJSON lint.LintResults `json:"result.json"` + } `json:"default"` + } + var md mdT + require.NoError(t, json.Unmarshal(dt, &md), dt) + require.NotEmpty(t, md.Default.BuildRef) + require.Len(t, md.Default.ResultJSON.Warnings, 3) +} diff --git a/tests/build.go b/tests/build.go index 8968a2ee..3a22a592 100644 --- a/tests/build.go +++ b/tests/build.go @@ -1193,6 +1193,44 @@ FROM second AS binary require.Equal(t, 1, len(res.Sources)) }) + + t.Run("check metadata", func(t *testing.T) { + dockerfile := []byte(` +frOM busybox as base +cOpy Dockerfile . +from scratch +COPy --from=base \ + /Dockerfile \ + / + `) + dir := tmpdir( + t, + fstest.CreateFile("Dockerfile", dockerfile, 0600), + ) + + cmd := buildxCmd(sb, withArgs("build", "--call=check,format=json", "--metadata-file", filepath.Join(dir, "md.json"), dir)) + stdout := bytes.Buffer{} + stderr := bytes.Buffer{} + cmd.Stdout = &stdout + cmd.Stderr = &stderr + require.Error(t, cmd.Run(), stdout.String(), stderr.String()) + + var res lint.LintResults + require.NoError(t, json.Unmarshal(stdout.Bytes(), &res), stdout.String()) + require.Len(t, res.Warnings, 3) + + dt, err := os.ReadFile(filepath.Join(dir, "md.json")) + require.NoError(t, err) + + type mdT struct { + BuildRef string `json:"buildx.build.ref"` + ResultJSON lint.LintResults `json:"result.json"` + } + var md mdT + require.NoError(t, json.Unmarshal(dt, &md), dt) + require.NotEmpty(t, md.BuildRef) + require.Len(t, md.ResultJSON.Warnings, 3) + }) } func createTestProject(t *testing.T) string {