imagetools: metadata-file flag
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/v2/core/remotes"
|
"github.com/containerd/containerd/v2/core/remotes"
|
||||||
@@ -16,7 +17,9 @@ import (
|
|||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
|
"github.com/moby/sys/atomicwriter"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -34,6 +37,7 @@ type createOptions struct {
|
|||||||
progress string
|
progress string
|
||||||
preferIndex bool
|
preferIndex bool
|
||||||
platforms []string
|
platforms []string
|
||||||
|
metadataFile string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
|
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
|
||||||
@@ -76,11 +80,16 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
|
|||||||
}
|
}
|
||||||
|
|
||||||
repos := map[string]struct{}{}
|
repos := map[string]struct{}{}
|
||||||
|
|
||||||
for _, t := range tags {
|
for _, t := range tags {
|
||||||
repos[t.Name()] = struct{}{}
|
repos[t.Name()] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repoNames := make([]string, 0, len(repos))
|
||||||
|
for repo := range repos {
|
||||||
|
repoNames = append(repoNames, repo)
|
||||||
|
}
|
||||||
|
sort.Strings(repoNames)
|
||||||
|
|
||||||
sourceRefs := false
|
sourceRefs := false
|
||||||
for _, s := range srcs {
|
for _, s := range srcs {
|
||||||
if s.Ref != nil {
|
if s.Ref != nil {
|
||||||
@@ -241,6 +250,15 @@ func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, arg
|
|||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err == nil && len(in.metadataFile) > 0 {
|
||||||
|
if err := writeMetadataFile(in.metadataFile, map[string]any{
|
||||||
|
exptypes.ExporterImageDescriptorKey: desc,
|
||||||
|
exptypes.ExporterImageNameKey: strings.Join(repoNames, ","),
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,6 +366,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
|||||||
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
|
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
|
||||||
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")
|
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")
|
||||||
flags.StringArrayVarP(&options.platforms, "platform", "p", []string{}, "Filter specified platforms of target image")
|
flags.StringArrayVarP(&options.platforms, "platform", "p", []string{}, "Filter specified platforms of target image")
|
||||||
|
flags.StringVar(&options.metadataFile, "metadata-file", "", "Write create result metadata to a file")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -367,3 +386,11 @@ func mergeDesc(d1, d2 ocispecs.Descriptor) (ocispecs.Descriptor, error) {
|
|||||||
}
|
}
|
||||||
return d1, nil
|
return d1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeMetadataFile(filename string, dt any) error {
|
||||||
|
b, err := json.MarshalIndent(dt, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return atomicwriter.WriteFile(filename, b, 0o644)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,18 +9,19 @@ Create a new image based on source images
|
|||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:---------------------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------------------------|
|
|:------------------------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
||||||
| [`--append`](#append) | `bool` | | Append to existing manifest |
|
| [`--append`](#append) | `bool` | | Append to existing manifest |
|
||||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| [`--dry-run`](#dry-run) | `bool` | | Show final image instead of pushing |
|
| [`--dry-run`](#dry-run) | `bool` | | Show final image instead of pushing |
|
||||||
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file |
|
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file |
|
||||||
| `-p`, `--platform` | `stringArray` | | Filter specified platforms of target image |
|
| [`--metadata-file`](#metadata-file) | `string` | | Write create result metadata to a file |
|
||||||
| `--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 |
|
| `-p`, `--platform` | `stringArray` | | Filter specified platforms of target image |
|
||||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `rawjson`, `tty`). Use plain to show container output |
|
| `--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 |
|
||||||
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image |
|
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `none`, `plain`, `rawjson`, `tty`). Use plain to show container output |
|
||||||
|
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image |
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
@@ -100,6 +101,28 @@ The descriptor in the file is merged with existing descriptor in the registry if
|
|||||||
|
|
||||||
The supported fields for the descriptor are defined in [OCI spec](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#properties) .
|
The supported fields for the descriptor are defined in [OCI spec](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#properties) .
|
||||||
|
|
||||||
|
### <a name="metadata-file"></a> Write create result metadata to a file (--metadata-file)
|
||||||
|
|
||||||
|
To output 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.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools create -t user/app:latest -f image1 -f image2 --metadata-file metadata.json
|
||||||
|
$ cat metadata.json
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"containerimage.descriptor": {
|
||||||
|
"mediaType": "application/vnd.oci.image.index.v1+json",
|
||||||
|
"digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3",
|
||||||
|
"size": 4654
|
||||||
|
},
|
||||||
|
"image.name": "docker.io/user/app"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### <a name="tag"></a> Set reference for new image (-t, --tag)
|
### <a name="tag"></a> Set reference for new image (-t, --tag)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
|||||||
+31
-2
@@ -2,7 +2,10 @@ package tests
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containerd/containerd/v2/core/images"
|
"github.com/containerd/containerd/v2/core/images"
|
||||||
@@ -50,10 +53,23 @@ func testImagetoolsCopyManifest(t *testing.T, sb integration.Sandbox) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
target2 := registry2 + "/buildx/imtools2-manifest:latest"
|
target2 := registry2 + "/buildx/imtools2-manifest:latest"
|
||||||
|
|
||||||
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target2, target))
|
cmd = buildxCmd(sb, withArgs("imagetools", "create", "--metadata-file", path.Join(dir, "md.json"), "-t", target2, target))
|
||||||
dt, err = cmd.CombinedOutput()
|
dt, err = cmd.CombinedOutput()
|
||||||
require.NoError(t, err, string(dt))
|
require.NoError(t, err, string(dt))
|
||||||
|
|
||||||
|
mddt, err := os.ReadFile(filepath.Join(dir, "md.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
type mdT struct {
|
||||||
|
ImageDescriptor ocispecs.Descriptor `json:"containerimage.descriptor"`
|
||||||
|
ImageName string `json:"image.name"`
|
||||||
|
}
|
||||||
|
var md mdT
|
||||||
|
err = json.Unmarshal(mddt, &md)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, md.ImageDescriptor)
|
||||||
|
require.Equal(t, registry2+"/buildx/imtools2-manifest", md.ImageName)
|
||||||
|
|
||||||
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
|
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
|
||||||
dt, err = cmd.CombinedOutput()
|
dt, err = cmd.CombinedOutput()
|
||||||
require.NoError(t, err, string(dt))
|
require.NoError(t, err, string(dt))
|
||||||
@@ -123,10 +139,23 @@ func testImagetoolsCopyIndex(t *testing.T, sb integration.Sandbox) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
target2 := registry2 + "/buildx/imtools2:latest"
|
target2 := registry2 + "/buildx/imtools2:latest"
|
||||||
|
|
||||||
cmd = buildxCmd(sb, withArgs("imagetools", "create", "-t", target2, target))
|
cmd = buildxCmd(sb, withArgs("imagetools", "create", "--metadata-file", path.Join(dir, "md.json"), "-t", target2, target))
|
||||||
dt, err = cmd.CombinedOutput()
|
dt, err = cmd.CombinedOutput()
|
||||||
require.NoError(t, err, string(dt))
|
require.NoError(t, err, string(dt))
|
||||||
|
|
||||||
|
mddt, err := os.ReadFile(filepath.Join(dir, "md.json"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
type mdT struct {
|
||||||
|
ImageDescriptor ocispecs.Descriptor `json:"containerimage.descriptor"`
|
||||||
|
ImageName string `json:"image.name"`
|
||||||
|
}
|
||||||
|
var md mdT
|
||||||
|
err = json.Unmarshal(mddt, &md)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, md.ImageDescriptor)
|
||||||
|
require.Equal(t, registry2+"/buildx/imtools2", md.ImageName)
|
||||||
|
|
||||||
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
|
cmd = buildxCmd(sb, withArgs("imagetools", "inspect", target2, "--raw"))
|
||||||
dt, err = cmd.CombinedOutput()
|
dt, err = cmd.CombinedOutput()
|
||||||
require.NoError(t, err, string(dt))
|
require.NoError(t, err, string(dt))
|
||||||
|
|||||||
Reference in New Issue
Block a user