Skip to content

Commit

Permalink
show name for images
Browse files Browse the repository at this point in the history
Signed-off-by: Ye Sijun <junnplus@gmail.com>
  • Loading branch information
junnplus committed Jun 28, 2022
1 parent b3caee1 commit 12641e4
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 41 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,7 @@ Flags:
- :nerd_face: `--format=wide`: Wide table
- :nerd_face: `--format=json`: Alias of `--format='{{json .}}'`
- :whale: `--digests`: Show digests (compatible with Docker, unlike ID)
- :nerd_face: `--names`: Show image names

Unimplemented `docker images` flags: `--filter`

Expand Down
104 changes: 63 additions & 41 deletions cmd/nerdctl/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func newImagesCommand() *cobra.Command {
Properties:
- REPOSITORY: Repository
- TAG: Tag
- NAME: Name of the image, --names for skip parsing as respository and tag.
- IMAGE ID: OCI Digest. Usually different from Docker image ID. Shared for multi-platform images.
- CREATED: Created time
- PLATFORM: Platform
Expand All @@ -76,6 +77,7 @@ Properties:
return []string{"json", "table", "wide"}, cobra.ShellCompDirectiveNoFileComp
})
imagesCommand.Flags().Bool("digests", false, "Show digests (compatible with Docker, unlike ID)")
imagesCommand.Flags().Bool("names", false, "Show image names")
imagesCommand.Flags().BoolP("all", "a", true, "(unimplemented yet, always true)")

return imagesCommand
Expand All @@ -90,6 +92,7 @@ func imagesAction(cmd *cobra.Command, args []string) error {
return err
}
filters = append(filters, fmt.Sprintf("name==%s", canonicalRef.String()))
filters = append(filters, fmt.Sprintf("name==%s", args[0]))
}
client, ctx, cancel, err := newClient(cmd)
if err != nil {
Expand Down Expand Up @@ -119,6 +122,7 @@ type imagePrintable struct {
ID string // image target digest (not config digest, unlike Docker), or its short form
Repository string
Tag string // "<none>" or tag
Name string // image name
Size string // the size of the unpacked snapshots.
BlobSize string // the size of the blobs in the content store (nerdctl extension)
// TODO: "SharedSize", "UniqueSize", "VirtualSize"
Expand All @@ -138,6 +142,10 @@ func printImages(ctx context.Context, cmd *cobra.Command, client *containerd.Cli
if err != nil {
return err
}
namesFlag, err := cmd.Flags().GetBool("names")
if err != nil {
return err
}
var w io.Writer
w = os.Stdout
format, err := cmd.Flags().GetString("format")
Expand All @@ -152,11 +160,17 @@ func printImages(ctx context.Context, cmd *cobra.Command, client *containerd.Cli
case "", "table", "wide":
w = tabwriter.NewWriter(w, 4, 8, 4, ' ', 0)
if !quiet {
if digestsFlag {
fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tPLATFORM\tSIZE\tBLOB SIZE")
printHeader := ""
if namesFlag {
printHeader += "NAME\t"
} else {
fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tPLATFORM\tSIZE\tBLOB SIZE")
printHeader += "REPOSITORY\tTAG\t"
}
if digestsFlag {
printHeader += "DIGEST\t"
}
printHeader += "IMAGE ID\tCREATED\tPLATFORM\tSIZE\tBLOB SIZE"
fmt.Fprintln(w, printHeader)
}
case "raw":
return errors.New("unsupported format: \"raw\"")
Expand All @@ -181,6 +195,7 @@ func printImages(ctx context.Context, cmd *cobra.Command, client *containerd.Cli
quiet: quiet,
noTrunc: noTrunc,
digestsFlag: digestsFlag,
namesFlag: namesFlag,
tmpl: tmpl,
client: client,
contentStore: client.ContentStore(),
Expand All @@ -199,12 +214,12 @@ func printImages(ctx context.Context, cmd *cobra.Command, client *containerd.Cli
}

type imagePrinter struct {
w io.Writer
quiet, noTrunc, digestsFlag bool
tmpl *template.Template
client *containerd.Client
contentStore content.Store
snapshotter snapshots.Snapshotter
w io.Writer
quiet, noTrunc, digestsFlag, namesFlag bool
tmpl *template.Template
client *containerd.Client
contentStore content.Store
snapshotter snapshots.Snapshotter
}

func (x *imagePrinter) printImage(ctx context.Context, img images.Image) error {
Expand All @@ -228,29 +243,45 @@ func (x *imagePrinter) printImageSinglePlatform(ctx context.Context, img images.
return nil
}

blobSize, err := img.Size(ctx, x.contentStore, platMC)
image := containerd.NewImageWithPlatform(x.client, img, platMC)
desc, err := image.Config(ctx)
if err != nil {
logrus.WithError(err).Warnf("failed to get config of image %q for platform %q", img.Name, platforms.Format(ociPlatform))
}
var (
repository string
tag string
)
// cri plugin will create an image named digest of image's config, skip parsing.
if x.namesFlag || desc.Digest.String() != img.Name {
repository, tag = imgutil.ParseRepoTag(img.Name)
}

blobSize, err := image.Size(ctx)
if err != nil {
logrus.WithError(err).Warnf("failed to get blob size of image %q for platform %q", img.Name, platforms.Format(ociPlatform))
}

size, err := unpackedImageSize(ctx, x.client, x.snapshotter, img, platMC)
size, err := unpackedImageSize(ctx, x.snapshotter, image)
if err != nil {
logrus.WithError(err).Warnf("failed to get unpacked size of image %q for platform %q", img.Name, platforms.Format(ociPlatform))
}

repository, tag := imgutil.ParseRepoTag(img.Name)

p := imagePrintable{
CreatedAt: img.CreatedAt.Round(time.Second).Local().String(), // format like "2021-08-07 02:19:45 +0900 JST"
CreatedSince: formatter.TimeSinceInHuman(img.CreatedAt),
Digest: img.Target.Digest.String(),
ID: img.Target.Digest.String(),
Repository: repository,
Tag: tag,
Name: img.Name,
Size: progress.Bytes(size).String(),
BlobSize: progress.Bytes(blobSize).String(),
Platform: platforms.Format(ociPlatform),
}
if p.Repository == "" {
p.Repository = "<none>"
}
if p.Tag == "" {
p.Tag = "<none>" // for Docker compatibility
}
Expand All @@ -271,31 +302,24 @@ func (x *imagePrinter) printImageSinglePlatform(ctx context.Context, img images.
return err
}
} else {
if x.digestsFlag {
if _, err := fmt.Fprintf(x.w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
p.Repository,
p.Tag,
p.Digest,
p.ID,
p.CreatedSince,
p.Platform,
p.Size,
p.BlobSize,
); err != nil {
return err
}
format := ""
args := []interface{}{}
if x.namesFlag {
format += "%s\t"
args = append(args, p.Name)
} else {
if _, err := fmt.Fprintf(x.w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
p.Repository,
p.Tag,
p.ID,
p.CreatedSince,
p.Platform,
p.Size,
p.BlobSize,
); err != nil {
return err
}
format += "%s\t%s\t"
args = append(args, p.Repository, p.Tag)
}
if x.digestsFlag {
format += "%s\t"
args = append(args, p.Digest)
}

format += "%s\t%s\t%s\t%s\t%s\n"
args = append(args, p.ID, p.CreatedSince, p.Platform, p.Size, p.BlobSize)
if _, err := fmt.Fprintf(x.w, format, args...); err != nil {
return err
}
}
return nil
Expand Down Expand Up @@ -335,9 +359,7 @@ func (key snapshotKey) add(ctx context.Context, s snapshots.Snapshotter, usage *

// unpackedImageSize is the size of the unpacked snapshots.
// Does not contain the size of the blobs in the content store. (Corresponds to Docker).
func unpackedImageSize(ctx context.Context, client *containerd.Client, s snapshots.Snapshotter, i images.Image, platMC platforms.MatchComparer) (int64, error) {
img := containerd.NewImageWithPlatform(client, i, platMC)

func unpackedImageSize(ctx context.Context, s snapshots.Snapshotter, img containerd.Image) (int64, error) {
diffIDs, err := img.RootFS(ctx)
if err != nil {
return 0, err
Expand All @@ -347,7 +369,7 @@ func unpackedImageSize(ctx context.Context, client *containerd.Client, s snapsho
usage, err := s.Usage(ctx, chainID)
if err != nil {
if errdefs.IsNotFound(err) {
logrus.WithError(err).Debugf("image %q seems not unpacked", i.Name)
logrus.WithError(err).Debugf("image %q seems not unpacked", img.Name())
return 0, nil
}
return 0, err
Expand Down
32 changes: 32 additions & 0 deletions cmd/nerdctl/images_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"testing"

"github.com/containerd/nerdctl/pkg/testutil"
)

func TestImagesWithNames(t *testing.T) {
t.Parallel()
testutil.DockerIncompatible(t)
base := testutil.NewBase(t)

base.Cmd("pull", testutil.CommonImage).AssertOK()
base.Cmd("images", "--names", testutil.CommonImage).AssertOutContains(testutil.CommonImage)
}
1 change: 1 addition & 0 deletions pkg/idutil/imagewalker/imagewalker.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func (w *ImageWalker) Walk(ctx context.Context, req string) (int, error) {
filters = append(filters, fmt.Sprintf("name==%s", canonicalRef.String()))
}
filters = append(filters,
fmt.Sprintf("name==%s", req),
fmt.Sprintf("target.digest~=^sha256:%s.*$", regexp.QuoteMeta(req)),
fmt.Sprintf("target.digest~=^%s.*$", regexp.QuoteMeta(req)),
)
Expand Down

0 comments on commit 12641e4

Please sign in to comment.