Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 28 additions & 26 deletions cli/command/manifest/annotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/cli/cli/manifest/types"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -58,38 +58,40 @@ func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error {
}

manifestStore := dockerCli.ManifestStore()
imageManifest, err := manifestStore.Get(targetRef, imgRef)
imageManifests, err := manifestStore.Get(targetRef, imgRef)
switch {
case store.IsNotFound(err):
case errors.Is(err, types.ErrManifestNotFound):
return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target)
case err != nil:
return err
}

// Update the mf
if imageManifest.Descriptor.Platform == nil {
imageManifest.Descriptor.Platform = new(ocispec.Platform)
}
if opts.os != "" {
imageManifest.Descriptor.Platform.OS = opts.os
}
if opts.arch != "" {
imageManifest.Descriptor.Platform.Architecture = opts.arch
}
for _, osFeature := range opts.osFeatures {
imageManifest.Descriptor.Platform.OSFeatures = appendIfUnique(imageManifest.Descriptor.Platform.OSFeatures, osFeature)
}
if opts.variant != "" {
imageManifest.Descriptor.Platform.Variant = opts.variant
}
if opts.osVersion != "" {
imageManifest.Descriptor.Platform.OSVersion = opts.osVersion
}

if !isValidOSArch(imageManifest.Descriptor.Platform.OS, imageManifest.Descriptor.Platform.Architecture) {
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
for i, imageManifest := range imageManifests {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, in hindsight, I'm not convinced about annotate looping through every manifest.

e.g. assuming an image with BuildKit attestations, the platform would be unknown/unknown, so updating the platform is probably not the right idea?

I guess annotation only makes sense over a single manifest, so when multiple are found, we should error out?

// Update the mf
if imageManifest.Descriptor.Platform == nil {
imageManifest.Descriptor.Platform = new(ocispec.Platform)
}
if opts.os != "" {
imageManifest.Descriptor.Platform.OS = opts.os
}
if opts.arch != "" {
imageManifest.Descriptor.Platform.Architecture = opts.arch
}
for _, osFeature := range opts.osFeatures {
imageManifest.Descriptor.Platform.OSFeatures = appendIfUnique(imageManifest.Descriptor.Platform.OSFeatures, osFeature)
}
if opts.variant != "" {
imageManifest.Descriptor.Platform.Variant = opts.variant
}
if opts.osVersion != "" {
imageManifest.Descriptor.Platform.OSVersion = opts.osVersion
}
if !isValidOSArch(imageManifest.Descriptor.Platform.OS, imageManifest.Descriptor.Platform.Architecture) {
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
}
imageManifests[i] = imageManifest
}
return manifestStore.Save(targetRef, imgRef, imageManifest)
return manifestStore.Save(targetRef, imgRef, imageManifests...)
}

func appendIfUnique(list []string, str string) []string {
Expand Down
8 changes: 4 additions & 4 deletions cli/command/manifest/create_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/cli/cli/manifest/types"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -50,7 +50,7 @@ func createManifestList(dockerCli command.Cli, args []string, opts createOpts) e
manifestStore := dockerCli.ManifestStore()
_, err = manifestStore.GetList(targetRef)
switch {
case store.IsNotFound(err):
case errors.Is(err, types.ErrManifestNotFound):
// New manifest list
case err != nil:
return err
Expand All @@ -69,11 +69,11 @@ func createManifestList(dockerCli command.Cli, args []string, opts createOpts) e
return err
}

manifest, err := getManifest(ctx, dockerCli, targetRef, namedRef, opts.insecure)
manifests, err := getManifests(ctx, dockerCli, targetRef, namedRef, opts.insecure)
if err != nil {
return err
}
if err := manifestStore.Save(targetRef, namedRef, manifest); err != nil {
if err := manifestStore.Save(targetRef, namedRef, manifests...); err != nil {
return err
}
}
Expand Down
6 changes: 3 additions & 3 deletions cli/command/manifest/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,16 @@ func TestManifestCreateNoManifest(t *testing.T) {
cli.SetManifestStore(store)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, errors.Errorf("No such image: %v", ref)
return manifesttypes.ImageManifest{}, errors.Wrapf(manifesttypes.ErrManifestNotFound, "%q does not exist", ref)
},
getManifestListFunc: func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, errors.Errorf("No such manifest: %s", ref)
return nil, errors.Wrapf(manifesttypes.ErrManifestNotFound, "%q does not exist", ref)
},
})

cmd := newCreateListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
cmd.SetOut(io.Discard)
err := cmd.Execute()
assert.Error(t, err, "No such image: example.com/alpine:3.0")
assert.Error(t, err, `"example.com/alpine:3.0" does not exist: manifest not found`)
}
36 changes: 12 additions & 24 deletions cli/command/manifest/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,40 +55,28 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error {
return err
}

// If list reference is provided, display the local manifest in a list
var listRef reference.Named
if opts.list != "" {
listRef, err := normalizeReference(opts.list)
listRef, err = normalizeReference(opts.list)
if err != nil {
return err
}

imageManifest, err := dockerCli.ManifestStore().Get(listRef, namedRef)
if err != nil {
return err
}
return printManifest(dockerCli, imageManifest, opts)
}

// Try a local manifest list first
localManifestList, err := dockerCli.ManifestStore().GetList(namedRef)
if err == nil {
return printManifestList(dockerCli, namedRef, localManifestList, opts)
}

// Next try a remote manifest
ctx := context.Background()
registryClient := dockerCli.RegistryClient(opts.insecure)
imageManifest, err := registryClient.GetManifest(ctx, namedRef)
if err == nil {
return printManifest(dockerCli, imageManifest, opts)
}

// Finally try a remote manifest list
manifestList, err := registryClient.GetManifestList(ctx, namedRef)
manifests, err := getManifests(ctx, dockerCli, listRef, namedRef, opts.insecure)
if err != nil {
return err
}
return printManifestList(dockerCli, namedRef, manifestList, opts)

switch len(manifests) {
case 0:
return nil
case 1:
return printManifest(dockerCli, manifests[0], opts)
default:
return printManifestList(dockerCli, namedRef, manifests, opts)
}
}

func printManifest(dockerCli command.Cli, manifest types.ImageManifest, opts inspectOptions) error {
Expand Down
10 changes: 5 additions & 5 deletions cli/command/manifest/inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func TestInspectCommandLocalManifestNotFound(t *testing.T) {
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
err := cmd.Execute()
assert.Error(t, err, "No such manifest: example.com/alpine:3.0")
assert.Error(t, err, `"example.com/alpine:3.0" does not exist: manifest not found`)
}

func TestInspectCommandNotFound(t *testing.T) {
Expand All @@ -79,19 +79,19 @@ func TestInspectCommandNotFound(t *testing.T) {
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, _ reference.Named) (types.ImageManifest, error) {
return types.ImageManifest{}, errors.New("missing")
getManifestFunc: func(ctx context.Context, ref reference.Named) (types.ImageManifest, error) {
return types.ImageManifest{}, errors.Wrapf(types.ErrManifestNotFound, "%q does not exist", ref)
},
getManifestListFunc: func(ctx context.Context, ref reference.Named) ([]types.ImageManifest, error) {
return nil, errors.Errorf("No such manifest: %s", ref)
return nil, errors.Wrapf(types.ErrManifestNotFound, "%q does not exist", ref)
},
})

cmd := newInspectCommand(cli)
cmd.SetOut(io.Discard)
cmd.SetArgs([]string{"example.com/alpine:3.0"})
err := cmd.Execute()
assert.Error(t, err, "No such manifest: example.com/alpine:3.0")
assert.Error(t, err, `"example.com/alpine:3.0" does not exist: manifest not found`)
}

func TestInspectCommandLocalManifest(t *testing.T) {
Expand Down
8 changes: 4 additions & 4 deletions cli/command/manifest/rm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ func TestRmSeveralManifests(t *testing.T) {

_, search1 := cli.ManifestStore().GetList(list1)
_, search2 := cli.ManifestStore().GetList(list2)
assert.Error(t, search1, "No such manifest: example.com/first:1")
assert.Error(t, search2, "No such manifest: example.com/second:2")
assert.Error(t, search1, `"example.com/first:1" does not exist: manifest not found`)
assert.Error(t, search2, `"example.com/second:2" does not exist: manifest not found`)
}

// attempt to remove a manifest list which was never created
Expand All @@ -57,8 +57,8 @@ func TestRmManifestNotCreated(t *testing.T) {
cmd.SetArgs([]string{"example.com/first:1", "example.com/second:2"})
cmd.SetOut(io.Discard)
err = cmd.Execute()
assert.Error(t, err, "No such manifest: example.com/first:1")
assert.Error(t, err, `"example.com/first:1" does not exist: manifest not found`)

_, err = cli.ManifestStore().GetList(list2)
assert.Error(t, err, "No such manifest: example.com/second:2")
assert.Error(t, err, `"example.com/second:2" does not exist: manifest not found`)
}
50 changes: 37 additions & 13 deletions cli/command/manifest/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"context"

"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/cli/cli/manifest/types"
"github.com/docker/distribution/reference"
"github.com/pkg/errors"
)

type osArch struct {
Expand Down Expand Up @@ -67,18 +67,42 @@ func normalizeReference(ref string) (reference.Named, error) {
return namedRef, nil
}

// getManifest from the local store, and fallback to the remote registry if it
// getManifests from the local store, and fallback to the remote registry if it
// doesn't exist locally
func getManifest(ctx context.Context, dockerCli command.Cli, listRef, namedRef reference.Named, insecure bool) (types.ImageManifest, error) {
data, err := dockerCli.ManifestStore().Get(listRef, namedRef)
switch {
case store.IsNotFound(err):
return dockerCli.RegistryClient(insecure).GetManifest(ctx, namedRef)
case err != nil:
return types.ImageManifest{}, err
case len(data.Raw) == 0:
return dockerCli.RegistryClient(insecure).GetManifest(ctx, namedRef)
default:
return data, nil
func getManifests(ctx context.Context, dockerCli command.Cli, listRef, namedRef reference.Named, insecure bool) ([]types.ImageManifest, error) {
// load from the local store
if listRef != nil {
data, err := dockerCli.ManifestStore().Get(listRef, namedRef)
if err == nil {
return data, nil
} else if !errors.Is(err, types.ErrManifestNotFound) {
return nil, err
}
}
datas, err := dockerCli.ManifestStore().GetList(namedRef)
if err == nil {
return datas, nil
} else if !errors.Is(err, types.ErrManifestNotFound) {
return nil, err
}

// load from the remote registry
client := dockerCli.RegistryClient(insecure)
if client != nil {
data, err := client.GetManifest(ctx, namedRef)
if err == nil {
return []types.ImageManifest{data}, nil
} else if !errors.Is(err, types.ErrManifestNotFound) {
return nil, err
}

datas, err = client.GetManifestList(ctx, namedRef)
if err == nil {
return datas, nil
} else if !errors.Is(err, types.ErrManifestNotFound) {
return nil, err
}
}

return nil, errors.Wrapf(types.ErrManifestNotFound, "%q does not exist", namedRef)
}
Loading