From 04061944987c74402df45b7fa0e0202b5022b8ad Mon Sep 17 00:00:00 2001 From: Jesse Rittner Date: Wed, 28 Apr 2021 22:08:53 -0400 Subject: [PATCH] Add --metadata-file flag to output build metadata Signed-off-by: Jesse Rittner --- README.md | 14 +++++++++ cmd/buildctl/build.go | 23 +++++++++++++- cmd/buildctl/build_test.go | 56 +++++++++++++++++++++++++++++++++++ cmd/buildctl/buildctl_test.go | 1 + 4 files changed, 93 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cb7459ad..94cb40a1 100644 --- a/README.md +++ b/README.md @@ -374,6 +374,20 @@ consider client-side load balancing using consistent hashing. See [`./examples/kubernetes/consistenthash`](./examples/kubernetes/consistenthash). +## Metadata + +To output build metadata such as the image digest, pass the `--metadata-file` flag. +The metadata will be written as a JSON object to the specified file. +The directory of the specified file must already exist and be writable. + +``` +buildctl build ... --metadata-file metadata.json +``` + +``` +{"containerimage.digest": "sha256:ea0cfb27fd41ea0405d3095880c1efa45710f5bcdddb7d7d5a7317ad4825ae14",...} +``` + ## Systemd socket activation On Systemd based systems, you can communicate with the daemon via [Systemd socket activation](http://0pointer.de/blog/projects/socket-activation.html), use `buildkitd --addr fd://`. diff --git a/cmd/buildctl/build.go b/cmd/buildctl/build.go index fa8d0284..c7684f3c 100644 --- a/cmd/buildctl/build.go +++ b/cmd/buildctl/build.go @@ -6,6 +6,7 @@ import ( "io" "os" + "github.com/containerd/continuity" "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/cmd/buildctl/build" @@ -101,6 +102,10 @@ var buildCommand = cli.Command{ Name: "ssh", Usage: "Allow forwarding SSH agent to the builder. Format default|[=|[,]]", }, + cli.StringFlag{ + Name: "metadata-file", + Usage: "Output build metadata (e.g., image digest) to a file as JSON", + }, }, } @@ -290,7 +295,15 @@ func buildAction(clicontext *cli.Context) error { for k, v := range resp.ExporterResponse { logrus.Debugf("exporter response: %s=%s", k, v) } - return err + + metadataFile := clicontext.String("metadata-file") + if metadataFile != "" && resp.ExporterResponse != nil { + if err := writeMetadataFile(metadataFile, resp.ExporterResponse); err != nil { + return err + } + } + + return nil }) eg.Go(func() error { @@ -300,3 +313,11 @@ func buildAction(clicontext *cli.Context) error { return eg.Wait() } + +func writeMetadataFile(filename string, exporterResponse map[string]string) error { + b, err := json.Marshal(exporterResponse) + if err != nil { + return err + } + return continuity.AtomicWriteFile(filename, b, 0666) +} diff --git a/cmd/buildctl/build_test.go b/cmd/buildctl/build_test.go index 4f116a5b..c6b5722b 100644 --- a/cmd/buildctl/build_test.go +++ b/cmd/buildctl/build_test.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "encoding/json" "fmt" "io" "io/ioutil" @@ -111,6 +112,61 @@ func testBuildContainerdExporter(t *testing.T, sb integration.Sandbox) { require.Equal(t, ok, true) } +func testBuildMetadataFile(t *testing.T, sb integration.Sandbox) { + st := llb.Image("busybox"). + Run(llb.Shlex("sh -c 'echo -n bar > /foo'")) + + rdr, err := marshal(st.Root()) + require.NoError(t, err) + + tmpDir, err := ioutil.TempDir("", "buildkit-buildctl") + require.NoError(t, err) + defer os.RemoveAll(tmpDir) + + imageName := "example.com/moby/metadata:test" + metadataFile := filepath.Join(tmpDir, "metadata.json") + + buildCmd := []string{ + "build", "--progress=plain", + "--output type=image,name=" + imageName + ",push=false", + "--metadata-file", metadataFile, + } + + cmd := sb.Cmd(strings.Join(buildCmd, " ")) + cmd.Stdin = rdr + err = cmd.Run() + require.NoError(t, err) + + require.FileExists(t, metadataFile) + metadataBytes, err := ioutil.ReadFile(metadataFile) + require.NoError(t, err) + + var metadata map[string]string + err = json.Unmarshal(metadataBytes, &metadata) + require.NoError(t, err) + + require.Equal(t, imageName, metadata["image.name"]) + + digest := metadata["containerimage.digest"] + require.NotEmpty(t, digest) + + cdAddress := sb.ContainerdAddress() + if cdAddress == "" { + t.Log("no containerd worker, skipping digest verification") + } else { + client, err := containerd.New(cdAddress, containerd.WithTimeout(60*time.Second)) + require.NoError(t, err) + defer client.Close() + + ctx := namespaces.WithNamespace(context.Background(), "buildkit") + + img, err := client.GetImage(ctx, imageName) + require.NoError(t, err) + + require.Equal(t, img.Metadata().Target.Digest.String(), digest) + } +} + func marshal(st llb.State) (io.Reader, error) { def, err := st.Marshal(context.TODO()) if err != nil { diff --git a/cmd/buildctl/buildctl_test.go b/cmd/buildctl/buildctl_test.go index bc8b0609..71bcad7b 100644 --- a/cmd/buildctl/buildctl_test.go +++ b/cmd/buildctl/buildctl_test.go @@ -18,6 +18,7 @@ func TestCLIIntegration(t *testing.T) { testBuildWithLocalFiles, testBuildLocalExporter, testBuildContainerdExporter, + testBuildMetadataFile, testPrune, testUsage, },