diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b0d3ac43..484e20fc 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -84,6 +84,8 @@ jobs: endpoint: tcp://localhost:1234 - driver: docker-container metadata-provenance: max + - driver: docker-container + metadata-warnings: true exclude: - driver: docker multi-node: mnode-true @@ -134,6 +136,9 @@ jobs: if [ -n "${{ matrix.metadata-provenance }}" ]; then echo "BUILDX_METADATA_PROVENANCE=${{ matrix.metadata-provenance }}" >> $GITHUB_ENV fi + if [ -n "${{ matrix.metadata-warnings }}" ]; then + echo "BUILDX_METADATA_WARNINGS=${{ matrix.metadata-warnings }}" >> $GITHUB_ENV + fi - name: Install k3s if: matrix.driver == 'kubernetes' diff --git a/commands/bake.go b/commands/bake.go index 6c5a02e2..a8805eb9 100644 --- a/commands/bake.go +++ b/commands/bake.go @@ -22,6 +22,7 @@ import ( "github.com/docker/buildx/util/progress" "github.com/docker/buildx/util/tracing" "github.com/docker/cli/cli/command" + "github.com/moby/buildkit/client" "github.com/moby/buildkit/identity" "github.com/moby/buildkit/util/progress/progressui" "github.com/pkg/errors" @@ -130,15 +131,30 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba return err } + var resp map[string]*client.SolveResponse + defer func() { if printer != nil { err1 := printer.Wait() if err == nil { err = err1 } - if err == nil && progressMode != progressui.QuietMode && progressMode != progressui.RawJSONMode { + if err != nil { + return + } + if progressMode != progressui.QuietMode && progressMode != progressui.RawJSONMode { desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term) } + if resp != nil && 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 + } + err = writeMetadataFile(in.metadataFile, dt) + } } }() @@ -229,22 +245,12 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba return err } - resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer) + resp, err = build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer) if err != nil { return wrapBuildError(err, true) } - if len(in.metadataFile) > 0 { - dt := make(map[string]interface{}) - for t, r := range resp { - dt[t] = decodeExporterResponse(r.ExporterResponse) - } - if err := writeMetadataFile(in.metadataFile, dt); err != nil { - return err - } - } - - return err + return } func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { diff --git a/commands/build.go b/commands/build.go index 7301e1cc..c3b40bd7 100644 --- a/commands/build.go +++ b/commands/build.go @@ -374,7 +374,11 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) return err } } else if options.metadataFile != "" { - if err := writeMetadataFile(options.metadataFile, decodeExporterResponse(resp.ExporterResponse)); err != nil { + 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 } } diff --git a/docs/reference/buildx_bake.md b/docs/reference/buildx_bake.md index d50b8f77..cc21e523 100644 --- a/docs/reference/buildx_bake.md +++ b/docs/reference/buildx_bake.md @@ -119,6 +119,7 @@ $ cat metadata.json ```json { + "buildx.build.warnings": {}, "db": { "buildx.build.provenance": {}, "buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611", @@ -161,6 +162,12 @@ $ cat metadata.json > * `max` sets full provenance. > * `disabled`, `false` or `0` does not set any provenance. +> **Note** +> +> Build warnings (`buildx.build.warnings`) are not included by default. Set the +> `BUILDX_METADATA_WARNINGS` environment variable to `1` or `true` to +> include them. + ### Don't use cache when building the image (--no-cache) Same as `build --no-cache`. Don't use cache when building the image. diff --git a/docs/reference/buildx_build.md b/docs/reference/buildx_build.md index 3196de92..ea74ea20 100644 --- a/docs/reference/buildx_build.md +++ b/docs/reference/buildx_build.md @@ -330,6 +330,7 @@ $ cat metadata.json { "buildx.build.provenance": {}, "buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611", + "buildx.build.warnings": {}, "containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66", "containerimage.descriptor": { "annotations": { @@ -353,6 +354,12 @@ $ cat metadata.json > * `max` sets full provenance. > * `disabled`, `false` or `0` does not set any provenance. +> **Note** +> +> Build warnings (`buildx.build.warnings`) are not included by default. Set the +> `BUILDX_METADATA_WARNINGS` environment variable to `1` or `true` to +> include them. + ### Ignore build cache for specific stages (--no-cache-filter) The `--no-cache-filter` lets you specify one or more stages of a multi-stage diff --git a/hack/test-driver b/hack/test-driver index 42659592..5d244a9e 100755 --- a/hack/test-driver +++ b/hack/test-driver @@ -109,21 +109,21 @@ buildxCmd inspect --bootstrap --builder="${builderName}" # create dockerfile cat > "${dockerfile}" < /log -FROM busybox AS log +FROM busybox As log COPY --from=build /log /log RUN cat /log RUN uname -a -FROM busybox AS hello +FROm busybox AS hello RUN echo hello > /hello FROM scratch -COPY --from=log /log /log +CoPY --from=log /log /log COPY --from=hello /hello /hello EOL diff --git a/tests/bake.go b/tests/bake.go index 3b3591fe..bca99b0c 100644 --- a/tests/bake.go +++ b/tests/bake.go @@ -9,6 +9,7 @@ import ( "github.com/containerd/continuity/fs/fstest" "github.com/docker/buildx/util/gitutil" + "github.com/moby/buildkit/client" "github.com/moby/buildkit/identity" provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types" "github.com/moby/buildkit/util/contentutil" @@ -42,7 +43,8 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){ testBakeEmpty, testBakeShmSize, testBakeUlimits, - testBakeMetadata, + testBakeMetadataProvenance, + testBakeMetadataWarnings, testBakeMultiExporters, testBakeLoadPush, } @@ -633,19 +635,22 @@ target "default" { require.Contains(t, string(dt), `1024`) } -func testBakeMetadata(t *testing.T, sb integration.Sandbox) { +func testBakeMetadataProvenance(t *testing.T, sb integration.Sandbox) { + t.Run("default", func(t *testing.T) { + bakeMetadataProvenance(t, sb, "") + }) t.Run("max", func(t *testing.T) { - bakeMetadata(t, sb, "max") + bakeMetadataProvenance(t, sb, "max") }) t.Run("min", func(t *testing.T) { - bakeMetadata(t, sb, "min") + bakeMetadataProvenance(t, sb, "min") }) t.Run("disabled", func(t *testing.T) { - bakeMetadata(t, sb, "disabled") + bakeMetadataProvenance(t, sb, "disabled") }) } -func bakeMetadata(t *testing.T, sb integration.Sandbox, metadataMode string) { +func bakeMetadataProvenance(t *testing.T, sb integration.Sandbox, metadataMode string) { dockerfile := []byte(` FROM scratch COPY foo /foo @@ -676,7 +681,7 @@ target "default" { withEnv("BUILDX_METADATA_PROVENANCE="+metadataMode), ) out, err := cmd.CombinedOutput() - require.NoError(t, err, out) + require.NoError(t, err, string(out)) dt, err := os.ReadFile(filepath.Join(dirDest, "md.json")) require.NoError(t, err) @@ -706,6 +711,71 @@ target "default" { require.Equal(t, provenancetypes.BuildKitBuildType, prv.BuildType) } +func testBakeMetadataWarnings(t *testing.T, sb integration.Sandbox) { + t.Run("default", func(t *testing.T) { + bakeMetadataWarnings(t, sb, "") + }) + t.Run("true", func(t *testing.T) { + bakeMetadataWarnings(t, sb, "true") + }) + t.Run("false", func(t *testing.T) { + bakeMetadataWarnings(t, sb, "false") + }) +} + +func bakeMetadataWarnings(t *testing.T, sb integration.Sandbox, mode string) { + 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), + ) + + dirDest := t.TempDir() + + cmd := buildxCmd( + sb, + withDir(dir), + withArgs("bake", "--metadata-file", filepath.Join(dirDest, "md.json"), "--set", "*.output=type=cacheonly"), + withEnv("BUILDX_METADATA_WARNINGS="+mode), + ) + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + + dt, err := os.ReadFile(filepath.Join(dirDest, "md.json")) + require.NoError(t, err) + + type mdT struct { + BuildWarnings []client.VertexWarning `json:"buildx.build.warnings"` + Default struct { + BuildRef string `json:"buildx.build.ref"` + } `json:"default"` + } + var md mdT + err = json.Unmarshal(dt, &md) + require.NoError(t, err, string(dt)) + + require.NotEmpty(t, md.Default.BuildRef, string(dt)) + if mode == "" || mode == "false" { + require.Empty(t, md.BuildWarnings, string(dt)) + return + } + + skipNoCompatBuildKit(t, sb, ">= 0.14.0-0", "lint") + require.Len(t, md.BuildWarnings, 3, string(dt)) +} + func testBakeMultiExporters(t *testing.T, sb integration.Sandbox) { if !isDockerContainerWorker(sb) { t.Skip("only testing with docker-container worker") diff --git a/tests/build.go b/tests/build.go index c79b2228..7d586a15 100644 --- a/tests/build.go +++ b/tests/build.go @@ -16,6 +16,7 @@ import ( "github.com/containerd/containerd/platforms" "github.com/containerd/continuity/fs/fstest" "github.com/creack/pty" + "github.com/moby/buildkit/client" "github.com/moby/buildkit/frontend/subrequests/lint" "github.com/moby/buildkit/frontend/subrequests/outline" "github.com/moby/buildkit/frontend/subrequests/targets" @@ -59,7 +60,8 @@ var buildTests = []func(t *testing.T, sb integration.Sandbox){ testBuildNetworkModeBridge, testBuildShmSize, testBuildUlimit, - testBuildMetadata, + testBuildMetadataProvenance, + testBuildMetadataWarnings, testBuildMultiExporters, testBuildLoadPush, testBuildSecret, @@ -560,19 +562,22 @@ COPY --from=build /ulimit / require.Contains(t, string(dt), `1024`) } -func testBuildMetadata(t *testing.T, sb integration.Sandbox) { +func testBuildMetadataProvenance(t *testing.T, sb integration.Sandbox) { + t.Run("default", func(t *testing.T) { + buildMetadataProvenance(t, sb, "") + }) t.Run("max", func(t *testing.T) { - buildMetadata(t, sb, "max") + buildMetadataProvenance(t, sb, "max") }) t.Run("min", func(t *testing.T) { - buildMetadata(t, sb, "min") + buildMetadataProvenance(t, sb, "min") }) t.Run("disabled", func(t *testing.T) { - buildMetadata(t, sb, "disabled") + buildMetadataProvenance(t, sb, "disabled") }) } -func buildMetadata(t *testing.T, sb integration.Sandbox, metadataMode string) { +func buildMetadataProvenance(t *testing.T, sb integration.Sandbox, metadataMode string) { dir := createTestProject(t) dirDest := t.TempDir() @@ -616,6 +621,61 @@ func buildMetadata(t *testing.T, sb integration.Sandbox, metadataMode string) { require.Equal(t, provenancetypes.BuildKitBuildType, prv.BuildType) } +func testBuildMetadataWarnings(t *testing.T, sb integration.Sandbox) { + t.Run("default", func(t *testing.T) { + buildMetadataWarnings(t, sb, "") + }) + t.Run("true", func(t *testing.T) { + buildMetadataWarnings(t, sb, "true") + }) + t.Run("false", func(t *testing.T) { + buildMetadataWarnings(t, sb, "false") + }) +} + +func buildMetadataWarnings(t *testing.T, sb integration.Sandbox, mode string) { + 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", "--metadata-file", filepath.Join(dir, "md.json"), dir), + withEnv("BUILDX_METADATA_WARNINGS="+mode), + ) + out, err := cmd.CombinedOutput() + require.NoError(t, err, string(out)) + + dt, err := os.ReadFile(filepath.Join(dir, "md.json")) + require.NoError(t, err) + + type mdT struct { + BuildRef string `json:"buildx.build.ref"` + BuildWarnings []client.VertexWarning `json:"buildx.build.warnings"` + } + var md mdT + err = json.Unmarshal(dt, &md) + require.NoError(t, err, string(dt)) + + require.NotEmpty(t, md.BuildRef, string(dt)) + if mode == "" || mode == "false" { + require.Empty(t, md.BuildWarnings, string(dt)) + return + } + + skipNoCompatBuildKit(t, sb, ">= 0.14.0-0", "lint") + require.Len(t, md.BuildWarnings, 3, string(dt)) +} + func testBuildMultiExporters(t *testing.T, sb integration.Sandbox) { if !isDockerContainerWorker(sb) { t.Skip("only testing with docker-container worker") diff --git a/util/confutil/metadata.go b/util/confutil/metadata.go index a655eec8..b30a7b26 100644 --- a/util/confutil/metadata.go +++ b/util/confutil/metadata.go @@ -39,3 +39,12 @@ func ParseMetadataProvenance(inp string) MetadataProvenanceMode { } return MetadataProvenanceModeMin } + +// MetadataWarningsEnabled returns whether metadata warnings are enabled from +// BUILDX_METADATA_WARNINGS environment variable (default false) +func MetadataWarningsEnabled() bool { + if ok, err := strconv.ParseBool(os.Getenv("BUILDX_METADATA_WARNINGS")); err == nil { + return ok + } + return false +}