Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

show name for images #1164

Merged
merged 1 commit into from
Jun 29, 2022
Merged
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
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 repository 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.
junnplus marked this conversation as resolved.
Show resolved Hide resolved
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