diff --git a/cmd/ctr/images.go b/cmd/ctr/images.go index b2287092e..f64fbaeb4 100644 --- a/cmd/ctr/images.go +++ b/cmd/ctr/images.go @@ -10,6 +10,7 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/progress" "github.com/pkg/errors" "github.com/urfave/cli" @@ -49,19 +50,36 @@ var imagesListCommand = cli.Command{ imageStore := client.ImageService() cs := client.ContentStore() - images, err := imageStore.List(ctx, filters...) + imageList, err := imageStore.List(ctx, filters...) if err != nil { return errors.Wrap(err, "failed to list images") } tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0) - fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSIZE\tLABELS\t") - for _, image := range images { + fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSIZE\tPLATFORM\tLABELS\t") + for _, image := range imageList { size, err := image.Size(ctx, cs) if err != nil { log.G(ctx).WithError(err).Errorf("failed calculating size for image %s", image.Name) } + platformColumn := "-" + specs, err := images.Platforms(ctx, cs, image.Target) + if err != nil { + log.G(ctx).WithError(err).Errorf("failed resolving platform for image %s", image.Name) + } else if len(specs) > 0 { + psm := map[string]struct{}{} + for _, p := range specs { + psm[platforms.Format(p)] = struct{}{} + } + var ps []string + for p := range psm { + ps = append(ps, p) + } + sort.Stable(sort.StringSlice(ps)) + platformColumn = strings.Join(ps, ",") + } + labels := "-" if len(image.Labels) > 0 { var pairs []string @@ -72,11 +90,12 @@ var imagesListCommand = cli.Command{ labels = strings.Join(pairs, ",") } - fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%s\t\n", + fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%s\t\n", image.Name, image.Target.MediaType, image.Target.Digest, progress.Bytes(size), + platformColumn, labels) } diff --git a/cmd/ctr/pull.go b/cmd/ctr/pull.go index d8a8fb269..4f9239d8f 100644 --- a/cmd/ctr/pull.go +++ b/cmd/ctr/pull.go @@ -39,7 +39,9 @@ command. As part of this process, we do the following: // TODO: Show unpack status fmt.Printf("unpacking %s...\n", img.Target().Digest) err = img.Unpack(ctx, clicontext.String("snapshotter")) - fmt.Println("done") + if err == nil { + fmt.Println("done") + } return err }, } diff --git a/images/handlers.go b/images/handlers.go index 452dd7cc5..53248f018 100644 --- a/images/handlers.go +++ b/images/handlers.go @@ -67,7 +67,7 @@ func Walk(ctx context.Context, handler Handler, descs ...ocispec.Descriptor) err children, err := handler.Handle(ctx, desc) if err != nil { if errors.Cause(err) == SkipDesc { - return nil // don't traverse the children. + continue // don't traverse the children. } return err } diff --git a/images/image.go b/images/image.go index ce7189822..652475cf4 100644 --- a/images/image.go +++ b/images/image.go @@ -6,6 +6,7 @@ import ( "time" "github.com/containerd/containerd/content" + "github.com/containerd/containerd/platforms" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -75,7 +76,7 @@ func (image *Image) Size(ctx context.Context, provider content.Provider) (int64, func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor) (ocispec.Descriptor, error) { var configDesc ocispec.Descriptor return configDesc, Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { - switch image.MediaType { + switch desc.MediaType { case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: p, err := content.ReadBlob(ctx, provider, image.Digest) if err != nil { @@ -97,6 +98,34 @@ func Config(ctx context.Context, provider content.Provider, image ocispec.Descri }), image) } +// Platforms returns one or more platforms supported by the image. +func Platforms(ctx context.Context, provider content.Provider, image ocispec.Descriptor) ([]ocispec.Platform, error) { + var platformSpecs []ocispec.Platform + return platformSpecs, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + if desc.Platform != nil { + platformSpecs = append(platformSpecs, *desc.Platform) + return nil, SkipDesc + } + + switch desc.MediaType { + case MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: + p, err := content.ReadBlob(ctx, provider, desc.Digest) + if err != nil { + return nil, err + } + + var image ocispec.Image + if err := json.Unmarshal(p, &image); err != nil { + return nil, err + } + + platformSpecs = append(platformSpecs, + platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) + } + return nil, nil + }), ChildrenHandler(provider)), image) +} + // RootFS returns the unpacked diffids that make up and images rootfs. // // These are used to verify that a set of layers unpacked to the expected