Skip to content

Commit 1db71a1

Browse files
committed
imagetools: metadata-file flag
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
1 parent 0a62a9e commit 1db71a1

3 files changed

Lines changed: 84 additions & 14 deletions

File tree

commands/imagetools/create.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import (
1616
"github.com/docker/buildx/util/imagetools"
1717
"github.com/docker/buildx/util/progress"
1818
"github.com/docker/cli/cli/command"
19+
"github.com/moby/buildkit/exporter/containerimage/exptypes"
1920
"github.com/moby/buildkit/util/progress/progressui"
21+
"github.com/moby/sys/atomicwriter"
2022
"github.com/opencontainers/go-digest"
2123
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2224
"github.com/pkg/errors"
@@ -34,6 +36,7 @@ type createOptions struct {
3436
progress string
3537
preferIndex bool
3638
platforms []string
39+
metadataFile string
3740
}
3841

3942
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
@@ -241,6 +244,14 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
241244
err = err1
242245
}
243246

247+
if err == nil && len(in.metadataFile) > 0 {
248+
if err := writeMetadataFile(in.metadataFile, map[string]any{
249+
exptypes.ExporterImageDigestKey: desc.Digest.String(),
250+
}); err != nil {
251+
return err
252+
}
253+
}
254+
244255
return err
245256
}
246257

@@ -348,6 +359,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
348359
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
349360
flags.BoolVar(&options.preferIndex, "prefer-index", true, "When only a single source is specified, prefer outputting an image index or manifest list instead of performing a carbon copy")
350361
flags.StringArrayVarP(&options.platforms, "platform", "p", []string{}, "Filter specified platforms of target image")
362+
flags.StringVar(&options.metadataFile, "metadata-file", "", "Write create result metadata to a file")
351363

352364
return cmd
353365
}
@@ -367,3 +379,11 @@ func mergeDesc(d1, d2 ocispecs.Descriptor) (ocispecs.Descriptor, error) {
367379
}
368380
return d1, nil
369381
}
382+
383+
func writeMetadataFile(filename string, dt any) error {
384+
b, err := json.MarshalIndent(dt, "", " ")
385+
if err != nil {
386+
return err
387+
}
388+
return atomicwriter.WriteFile(filename, b, 0o644)
389+
}

docs/reference/buildx_imagetools_create.md

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,19 @@ Create a new image based on source images
99

1010
### Options
1111

12-
| Name | Type | Default | Description |
13-
|:---------------------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------------------------|
14-
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
15-
| [`--append`](#append) | `bool` | | Append to existing manifest |
16-
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
17-
| `-D`, `--debug` | `bool` | | Enable debug logging |
18-
| [`--dry-run`](#dry-run) | `bool` | | Show final image instead of pushing |
19-
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file |
20-
| `-p`, `--platform` | `stringArray` | | Filter specified platforms of target image |
21-
| `--prefer-index` | `bool` | `true` | When only a single source is specified, prefer outputting an image index or manifest list instead of performing a carbon copy |
22-
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `rawjson`, `tty`). Use plain to show container output |
23-
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image |
12+
| Name | Type | Default | Description |
13+
|:------------------------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------------------------|
14+
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
15+
| [`--append`](#append) | `bool` | | Append to existing manifest |
16+
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
17+
| `-D`, `--debug` | `bool` | | Enable debug logging |
18+
| [`--dry-run`](#dry-run) | `bool` | | Show final image instead of pushing |
19+
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file |
20+
| [`--metadata-file`](#metadata-file) | `string` | | Write create result metadata to a file |
21+
| `-p`, `--platform` | `stringArray` | | Filter specified platforms of target image |
22+
| `--prefer-index` | `bool` | `true` | When only a single source is specified, prefer outputting an image index or manifest list instead of performing a carbon copy |
23+
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `rawjson`, `tty`). Use plain to show container output |
24+
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image |
2425

2526

2627
<!---MARKER_GEN_END-->
@@ -100,6 +101,23 @@ The descriptor in the file is merged with existing descriptor in the registry if
100101

101102
The supported fields for the descriptor are defined in [OCI spec](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#properties) .
102103

104+
### <a name="metadata-file"></a> Write create result metadata to a file (--metadata-file)
105+
106+
To output metadata such as the image digest, pass the `--metadata-file` flag.
107+
The metadata will be written as a JSON object to the specified file. The
108+
directory of the specified file must already exist and be writable.
109+
110+
```console
111+
$ docker buildx imagetools create -t tonistiigi/myapp -f image1 -f image2 --metadata-file metadata.json
112+
$ cat metadata.json
113+
```
114+
115+
```json
116+
{
117+
"containerimage.digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3"
118+
}
119+
```
120+
103121
### <a name="tag"></a> Set reference for new image (-t, --tag)
104122

105123
```text

tests/imagetools.go

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ package tests
22

33
import (
44
"encoding/json"
5+
"os"
56
"os/exec"
7+
"path"
8+
"path/filepath"
69
"testing"
710

811
"github.com/containerd/containerd/v2/core/images"
912
"github.com/containerd/continuity/fs/fstest"
1013
"github.com/containerd/platforms"
1114
"github.com/moby/buildkit/util/testutil/integration"
15+
"github.com/opencontainers/go-digest"
1216
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
1317
"github.com/pkg/errors"
1418
"github.com/stretchr/testify/require"
@@ -50,10 +54,24 @@ func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) {
5054
require.NoError(t, err)
5155
target2 := registry2 + "/buildx/imtools2-manifest:latest"
5256

53-
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target2, target))
57+
cmd = buildxCmd(sb, withArgs("imagetools", "create", "--metadata-file", path.Join(dir, "md.json"), "-t", target2, target))
5458
dt, err = cmd.CombinedOutput()
5559
require.NoError(t, err, string(dt))
5660

61+
mddt, err := os.ReadFile(filepath.Join(dir, "md.json"))
62+
require.NoError(t, err)
63+
64+
type mdT struct {
65+
ImageDigest string `json:"containerimage.digest"`
66+
}
67+
var md mdT
68+
err = json.Unmarshal(mddt, &md)
69+
require.NoError(t, err)
70+
71+
require.NotEmpty(t, md.ImageDigest)
72+
_, err = digest.Parse(md.ImageDigest)
73+
require.NoError(t, err)
74+
5775
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
5876
dt, err = cmd.CombinedOutput()
5977
require.NoError(t, err, string(dt))
@@ -123,10 +141,24 @@ func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) {
123141
require.NoError(t, err)
124142
target2 := registry2 + "/buildx/imtools2:latest"
125143

126-
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target2, target))
144+
cmd = buildxCmd(sb, withArgs("imagetools", "create", "--metadata-file", path.Join(dir, "md.json"), "-t", target2, target))
127145
dt, err = cmd.CombinedOutput()
128146
require.NoError(t, err, string(dt))
129147

148+
mddt, err := os.ReadFile(filepath.Join(dir, "md.json"))
149+
require.NoError(t, err)
150+
151+
type mdT struct {
152+
ImageDigest string `json:"containerimage.digest"`
153+
}
154+
var md mdT
155+
err = json.Unmarshal(mddt, &md)
156+
require.NoError(t, err)
157+
158+
require.NotEmpty(t, md.ImageDigest)
159+
_, err = digest.Parse(md.ImageDigest)
160+
require.NoError(t, err)
161+
130162
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
131163
dt, err = cmd.CombinedOutput()
132164
require.NoError(t, err, string(dt))

0 commit comments

Comments
 (0)