From 6a1e5a8b99a337524b70ec588aa9fec3fe59217a Mon Sep 17 00:00:00 2001 From: fahed dorgaa Date: Wed, 2 Jun 2021 09:44:45 +0200 Subject: [PATCH 1/2] setup nerdctl inspect cmd Signed-off-by: fahed dorgaa --- container_inspect.go | 4 +- image.go | 1 + image_inspect.go | 134 ++++++++++++++++++ image_inspect_test.go | 36 +++++ images.go | 28 +--- images_test.go | 3 +- inspect.go | 66 ++++++++- pkg/imageinspector/imageinspector.go | 53 +++++++ pkg/imgutil/commit/commit.go | 25 +--- pkg/imgutil/imgutil.go | 43 ++++++ pkg/inspecttypes/dockercompat/dockercompat.go | 106 ++++++++++++++ pkg/inspecttypes/native/image.go | 30 ++++ pkg/testutil/testutil.go | 11 ++ 13 files changed, 487 insertions(+), 53 deletions(-) create mode 100644 image_inspect.go create mode 100644 image_inspect_test.go create mode 100644 pkg/imageinspector/imageinspector.go create mode 100644 pkg/inspecttypes/native/image.go diff --git a/container_inspect.go b/container_inspect.go index 4ee47c87446..06206991234 100644 --- a/container_inspect.go +++ b/container_inspect.go @@ -34,7 +34,7 @@ var containerInspectCommand = &cli.Command{ Usage: "Display detailed information on one or more containers.", ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]", Description: "Hint: set `--mode=native` for showing the full output", - Action: containerInspectAction, + Action: ContainerInspectAction, BashComplete: containerInspectBashComplete, Flags: []cli.Flag{ &cli.StringFlag{ @@ -45,7 +45,7 @@ var containerInspectCommand = &cli.Command{ }, } -func containerInspectAction(clicontext *cli.Context) error { +func ContainerInspectAction(clicontext *cli.Context) error { if clicontext.NArg() == 0 { return errors.Errorf("requires at least 1 argument") } diff --git a/image.go b/image.go index 3e04f70b10d..ff72f7b41c7 100644 --- a/image.go +++ b/image.go @@ -35,6 +35,7 @@ var imageCommand = &cli.Command{ tagCommand, imageRmCommand(), imageConvertCommand, + imageInspectCommand, }, } diff --git a/image_inspect.go b/image_inspect.go new file mode 100644 index 00000000000..1a01710d3e2 --- /dev/null +++ b/image_inspect.go @@ -0,0 +1,134 @@ +/* + 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 ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/containerd/nerdctl/pkg/idutil/imagewalker" + "github.com/containerd/nerdctl/pkg/imageinspector" + "github.com/containerd/nerdctl/pkg/inspecttypes/dockercompat" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +var imageInspectCommand = &cli.Command{ + Name: "inspect", + Usage: "Display detailed information on one or more images.", + ArgsUsage: "[OPTIONS] IMAGE [IMAGE...]", + Description: "Hint: set `--mode=native` for showing the full output", + Action: ImageInspectAction, + BashComplete: imageInspectBashComplete, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "mode", + Usage: "Inspect mode, \"dockercompat\" for Docker-compatible output, \"native\" for containerd-native output", + Value: "dockercompat", + }, + }, +} + +func ImageInspectAction(clicontext *cli.Context) error { + if clicontext.NArg() == 0 { + return errors.Errorf("requires at least 1 argument") + } + + client, ctx, cancel, err := newClient(clicontext) + if err != nil { + return err + } + defer cancel() + + f := &imageInspector{ + mode: clicontext.String("mode"), + } + walker := &imagewalker.ImageWalker{ + Client: client, + OnFound: func(ctx context.Context, found imagewalker.Found) error { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + n, err := imageinspector.Inspect(ctx, client, found.Image) + if err != nil { + return err + } + switch f.mode { + case "native": + f.entries = append(f.entries, n) + case "dockercompat": + d, err := dockercompat.ImageFromNative(n) + if err != nil { + return err + } + f.entries = append(f.entries, d) + default: + return errors.Errorf("unknown mode %q", f.mode) + } + return nil + }, + } + + var errs []error + for _, req := range clicontext.Args().Slice() { + n, err := walker.Walk(ctx, req) + if err != nil { + errs = append(errs, err) + } else if n == 0 { + errs = append(errs, errors.Errorf("no such object: %s", req)) + } + } + + b, err := json.MarshalIndent(f.entries, "", " ") + if err != nil { + return err + } + fmt.Fprintln(clicontext.App.Writer, string(b)) + + if len(errs) > 0 { + return errors.Errorf("%d errors: %v", len(errs), errs) + } + return nil +} + +type imageInspector struct { + mode string + entries []interface{} +} + +func imageInspectBashComplete(clicontext *cli.Context) { + coco := parseCompletionContext(clicontext) + if coco.boring { + defaultBashComplete(clicontext) + return + } + if coco.flagTakesValue { + w := clicontext.App.Writer + switch coco.flagName { + case "mode": + fmt.Fprintln(w, "dockercompat") + fmt.Fprintln(w, "native") + return + } + defaultBashComplete(clicontext) + return + } + // show image names + bashCompleteImageNames(clicontext) +} diff --git a/image_inspect_test.go b/image_inspect_test.go new file mode 100644 index 00000000000..31ed012691d --- /dev/null +++ b/image_inspect_test.go @@ -0,0 +1,36 @@ +/* + 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" + "gotest.tools/v3/assert" +) + +func TestImageInspectContainsSomeStuff(t *testing.T) { + base := testutil.NewBase(t) + defer base.Cmd("rmi", "-f", testutil.NginxAlpineImage).Run() + + base.Cmd("pull", testutil.NginxAlpineImage).AssertOK() + inspect := base.InspectImage(testutil.NginxAlpineImage) + + assert.Assert(base.T, len(inspect.RootFS.Layers) > 0) + assert.Assert(base.T, inspect.RootFS.Type != "") + assert.Assert(base.T, inspect.Architecture != "") +} diff --git a/images.go b/images.go index 8585c4e009f..e0f6d75f544 100644 --- a/images.go +++ b/images.go @@ -27,6 +27,7 @@ import ( "github.com/containerd/containerd/images" "github.com/containerd/containerd/pkg/progress" refdocker "github.com/containerd/containerd/reference/docker" + "github.com/containerd/nerdctl/pkg/imgutil" "github.com/opencontainers/image-spec/identity" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -100,7 +101,7 @@ func printImages(ctx context.Context, clicontext *cli.Context, client *container if err != nil { errs = append(errs, err) } - repository, tag := parseRepoTag(img.Name) + repository, tag := imgutil.ParseRepoTag(img.Name) var digest string if !noTrunc { @@ -132,31 +133,6 @@ func printImages(ctx context.Context, clicontext *cli.Context, client *container return w.Flush() } -func parseRepoTag(imgName string) (string, string) { - logrus.Debugf("raw image name=%q", imgName) - - if strings.Contains(imgName, "@") { - logrus.Warnf("unparsable image name %q", imgName) - return "", "" - } - - if _, err := refdocker.ParseDockerRef(imgName); err != nil { - logrus.WithError(err).Warnf("unparsable image name %q", imgName) - return "", "" - } - - var tag string - nameWithTagSplit := strings.Split(imgName, ":") - if len(nameWithTagSplit) > 1 { - tag = nameWithTagSplit[len(nameWithTagSplit)-1] - } - repository := strings.TrimSuffix(imgName, ":"+tag) - repository = strings.TrimPrefix(repository, "docker.io/library/") - repository = strings.TrimPrefix(repository, "docker.io/") - - return repository, tag -} - func imagesBashComplete(clicontext *cli.Context) { coco := parseCompletionContext(clicontext) if coco.boring || coco.flagTakesValue { diff --git a/images_test.go b/images_test.go index 8cafca5f8f9..3b5793a1555 100644 --- a/images_test.go +++ b/images_test.go @@ -19,6 +19,7 @@ package main import ( "testing" + "github.com/containerd/nerdctl/pkg/imgutil" "gotest.tools/v3/assert" ) @@ -51,7 +52,7 @@ func TestParseRepoTag(t *testing.T) { }, } for _, tc := range testCases { - repo, tag := parseRepoTag(tc.imgName) + repo, tag := imgutil.ParseRepoTag(tc.imgName) assert.Equal(t, tc.repo, repo) assert.Equal(t, tc.tag, tag) } diff --git a/inspect.go b/inspect.go index f29df0d72ff..3f3c200e642 100644 --- a/inspect.go +++ b/inspect.go @@ -17,14 +17,76 @@ package main import ( + "context" + + "github.com/containerd/nerdctl/pkg/idutil/containerwalker" + "github.com/containerd/nerdctl/pkg/idutil/imagewalker" + "github.com/pkg/errors" "github.com/urfave/cli/v2" ) var inspectCommand = &cli.Command{ Name: "inspect", - Usage: "Return low-level information on objects. Currently, only supports container objects.", + Usage: "Return low-level information on objects.", Description: containerInspectCommand.Description, - Action: containerInspectAction, + Action: inspectAction, BashComplete: containerInspectBashComplete, Flags: containerInspectCommand.Flags, } + +func inspectAction(clicontext *cli.Context) error { + if clicontext.NArg() == 0 { + return errors.Errorf("requires at least 1 argument") + } + + client, ctx, cancel, err := newClient(clicontext) + if err != nil { + return err + } + defer cancel() + + imagewalker := &imagewalker.ImageWalker{ + Client: client, + OnFound: func(ctx context.Context, found imagewalker.Found) error { + return nil + }, + } + + containerwalker := &containerwalker.ContainerWalker{ + Client: client, + OnFound: func(ctx context.Context, found containerwalker.Found) error { + return nil + }, + } + + for _, req := range clicontext.Args().Slice() { + ni, err := imagewalker.Walk(ctx, req) + if err != nil { + return err + } + + nc, err := containerwalker.Walk(ctx, req) + if err != nil { + return err + } + + if ni != 0 && nc != 0 { + return errors.Errorf("multiple IDs found with provided prefix: %s", req) + } + + if nc == 0 && ni == 0 { + return errors.Errorf("no such object %s", req) + } + if ni != 0 { + if err := ImageInspectAction(clicontext); err != nil { + return err + } + } else { + if err := ContainerInspectAction(clicontext); err != nil { + return err + } + } + } + + return nil +} diff --git a/pkg/imageinspector/imageinspector.go b/pkg/imageinspector/imageinspector.go new file mode 100644 index 00000000000..bf81d5e21da --- /dev/null +++ b/pkg/imageinspector/imageinspector.go @@ -0,0 +1,53 @@ +/* + 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 imageinspector + +import ( + "context" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" + imgutil "github.com/containerd/nerdctl/pkg/imgutil" + "github.com/containerd/nerdctl/pkg/inspecttypes/native" + "github.com/sirupsen/logrus" +) + +func Inspect(ctx context.Context, client *containerd.Client, image images.Image) (*native.Image, error) { + + n := &native.Image{} + + img := containerd.NewImage(client, image) + imageConfig, err := imgutil.ReadImageConfig(ctx, img) + if err != nil { + logrus.WithError(err).WithField("id", image.Name).Warnf("failed to inspect Rootfs") + return nil, err + } + n.ImageSpec = imageConfig + + cs := client.ContentStore() + config, err := image.Config(ctx, cs, platforms.DefaultStrict()) + if err != nil { + logrus.WithError(err).WithField("id", image.Name).Warnf("failed to inspect Rootfs") + return nil, err + } + + n.Descriptor = config + n.Image = image + + return n, nil +} diff --git a/pkg/imgutil/commit/commit.go b/pkg/imgutil/commit/commit.go index e676d3bc935..ce917891615 100644 --- a/pkg/imgutil/commit/commit.go +++ b/pkg/imgutil/commit/commit.go @@ -37,6 +37,7 @@ import ( "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/rootfs" "github.com/containerd/containerd/snapshots" + imgutil "github.com/containerd/nerdctl/pkg/imgutil" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/specs-go" @@ -74,7 +75,7 @@ func Commit(ctx context.Context, client *containerd.Client, id string, opts *Opt return emptyDigest, err } - baseImgConfig, err := readImageConfig(ctx, baseImg) + baseImgConfig, err := imgutil.ReadImageConfig(ctx, baseImg) if err != nil { return emptyDigest, err } @@ -168,7 +169,7 @@ func generateCommitImageConfig(ctx context.Context, container containerd.Contain return ocispec.Image{}, err } - baseConfig, err := readImageConfig(ctx, img) + baseConfig, err := imgutil.ReadImageConfig(ctx, img) if err != nil { return ocispec.Image{}, err } @@ -336,26 +337,6 @@ func applyDiffLayer(ctx context.Context, name string, baseImg ocispec.Image, sn return nil } -// readImageConfig reads config spec from content store. -func readImageConfig(ctx context.Context, img containerd.Image) (ocispec.Image, error) { - var config ocispec.Image - - configDesc, err := img.Config(ctx) - if err != nil { - return config, err - } - - p, err := content.ReadBlob(ctx, img.ContentStore(), configDesc) - if err != nil { - return config, err - } - - if err := json.Unmarshal(p, &config); err != nil { - return config, err - } - return config, nil -} - // copied from github.com/containerd/containerd/rootfs/apply.go func uniquePart() string { t := time.Now() diff --git a/pkg/imgutil/imgutil.go b/pkg/imgutil/imgutil.go index c454458317f..fd6f5cd6961 100644 --- a/pkg/imgutil/imgutil.go +++ b/pkg/imgutil/imgutil.go @@ -208,3 +208,46 @@ func getImageConfig(ctx context.Context, image containerd.Image) (*ocispec.Image return nil, errors.Errorf("unknown media type %q", desc.MediaType) } } + +// readImageConfig reads config spec from content store. +func ReadImageConfig(ctx context.Context, img containerd.Image) (ocispec.Image, error) { + var config ocispec.Image + + configDesc, err := img.Config(ctx) + if err != nil { + return config, err + } + p, err := content.ReadBlob(ctx, img.ContentStore(), configDesc) + if err != nil { + return config, err + } + if err := json.Unmarshal(p, &config); err != nil { + return config, err + } + return config, nil +} + +func ParseRepoTag(imgName string) (string, string) { + logrus.Debugf("raw image name=%q", imgName) + + if strings.Contains(imgName, "@") { + logrus.Warnf("unparsable image name %q", imgName) + return "", "" + } + + if _, err := refdocker.ParseDockerRef(imgName); err != nil { + logrus.WithError(err).Warnf("unparsable image name %q", imgName) + return "", "" + } + + var tag string + nameWithTagSplit := strings.Split(imgName, ":") + if len(nameWithTagSplit) > 1 { + tag = nameWithTagSplit[len(nameWithTagSplit)-1] + } + repository := strings.TrimSuffix(imgName, ":"+tag) + repository = strings.TrimPrefix(repository, "docker.io/library/") + repository = strings.TrimPrefix(repository, "docker.io/") + + return repository, tag +} diff --git a/pkg/inspecttypes/dockercompat/dockercompat.go b/pkg/inspecttypes/dockercompat/dockercompat.go index 2f03ad3aef1..1656ab2872b 100644 --- a/pkg/inspecttypes/dockercompat/dockercompat.go +++ b/pkg/inspecttypes/dockercompat/dockercompat.go @@ -35,6 +35,7 @@ import ( "github.com/containerd/containerd" gocni "github.com/containerd/go-cni" + "github.com/containerd/nerdctl/pkg/imgutil" "github.com/containerd/nerdctl/pkg/inspecttypes/native" "github.com/containerd/nerdctl/pkg/labels" "github.com/docker/go-connections/nat" @@ -42,6 +43,41 @@ import ( "github.com/sirupsen/logrus" ) +// Image mimics a `docker image inspect` object. +// From https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L340-L374 +type Image struct { + ID string `json:"Id"` + RepoTags []string + RepoDigests []string + // TODO: Parent string + Comment string + Created string + // TODO: Container string + // TODO: ContainerConfig *container.Config + // TDOO: DockerVersion string + Author string + Config *Config + Architecture string + // TODO: Variant string `json:",omitempty"` + Os string + // TODO: OsVersion string `json:",omitempty"` + // TODO: Size int64 + // TODO: VirtualSize int64 + // TODO: GraphDriver GraphDriverData + RootFS RootFS + Metadata ImageMetadata +} + +type RootFS struct { + Type string + Layers []string `json:",omitempty"` + BaseLayer string `json:",omitempty"` +} + +type ImageMetadata struct { + LastTagTime time.Time `json:",omitempty"` +} + // Container mimics a `docker container inspect` object. // From https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L340-L374 type Container struct { @@ -74,6 +110,35 @@ type Container struct { NetworkSettings *NetworkSettings } +//config is from https://github.com/moby/moby/blob/8dbd90ec00daa26dc45d7da2431c965dec99e8b4/api/types/container/config.go#L37-L69 +type Config struct { + // TODO: Hostname string // Hostname + // TODO: Domainname string // Domainname + User string // User that will run the command(s) inside the container, also support user:group + // TODO: AttachStdin bool // Attach the standard input, makes possible user interaction + // TODO: AttachStdout bool // Attach the standard output + // TODO: AttachStderr bool // Attach the standard error + ExposedPorts nat.PortSet `json:",omitempty"` // List of exposed ports + // TODO: Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + // TODO: OpenStdin bool // Open stdin + // TODO: StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string // List of environment variable to set in the container + Cmd []string // Command to run when starting the container + // TODO Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy + // TODO: ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (meaning treat as a command line) (Windows specific). + // TODO: Image string // Name of the image as it was passed by the operator (e.g. could be symbolic) + Volumes map[string]struct{} // List of volumes (mounts) used for the container + WorkingDir string // Current directory (PWD) in the command will be launched + Entrypoint []string // Entrypoint to run when starting the container + // TODO: NetworkDisabled bool `json:",omitempty"` // Is network disabled + // TODO: MacAddress string `json:",omitempty"` // Mac Address of the container + // TODO: OnBuild []string // ONBUILD metadata that were defined on the image Dockerfile + Labels map[string]string // List of labels set to this container + // TODO: StopSignal string `json:",omitempty"` // Signal to stop a container + // TODO: StopTimeout *int `json:",omitempty"` // Timeout (in seconds) to stop a container + // TODO: Shell []string `json:",omitempty"` // Shell for shell-form of RUN, CMD, ENTRYPOINT +} + // ContainerState is from https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L313-L326 type ContainerState struct { Status string // String representation of the container state. Can be one of "created", "running", "paused", "restarting", "removing", "exited", or "dead" @@ -176,6 +241,47 @@ func ContainerFromNative(n *native.Container) (*Container, error) { return c, nil } +func ImageFromNative(n *native.Image) (*Image, error) { + i := &Image{} + + imgoci := n.ImageSpec + + i.RootFS.Type = imgoci.RootFS.Type + diffIDs := imgoci.RootFS.DiffIDs + for _, d := range diffIDs { + i.RootFS.Layers = append(i.RootFS.Layers, d.String()) + } + i.Comment = imgoci.History[len(imgoci.History)-1].Comment + i.Created = imgoci.History[len(imgoci.History)-1].Created.String() + i.Author = imgoci.History[len(imgoci.History)-1].Author + i.Architecture = imgoci.Architecture + i.Os = imgoci.OS + + portSet := make(nat.PortSet) + for k := range imgoci.Config.ExposedPorts { + portSet[nat.Port(k)] = struct{}{} + } + + i.Config = &Config{ + Cmd: imgoci.Config.Cmd, + Volumes: imgoci.Config.Volumes, + Env: imgoci.Config.Env, + User: imgoci.Config.User, + WorkingDir: imgoci.Config.WorkingDir, + Entrypoint: imgoci.Config.Entrypoint, + Labels: imgoci.Config.Labels, + ExposedPorts: portSet, + } + + i.ID = n.Descriptor.Digest.String() + + repository, tag := imgutil.ParseRepoTag(n.Image.Name) + + i.RepoTags = []string{fmt.Sprintf("%s:%s", repository, tag)} + i.RepoDigests = []string{fmt.Sprintf("%s@%s", repository, n.Image.Target.Digest.String())} + + return i, nil +} func statusFromNative(x containerd.ProcessStatus) string { switch x { case containerd.Stopped: diff --git a/pkg/inspecttypes/native/image.go b/pkg/inspecttypes/native/image.go new file mode 100644 index 00000000000..62f3540441e --- /dev/null +++ b/pkg/inspecttypes/native/image.go @@ -0,0 +1,30 @@ +/* + 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 native + +import ( + "github.com/containerd/containerd/images" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// Image corresponds to a containerd-native image object. +// Not compatible with `docker image inspect`. +type Image struct { + images.Image + ImageSpec ocispec.Image `json:"ImageSpec"` + Descriptor ocispec.Descriptor `json:"Descriptor"` +} diff --git a/pkg/testutil/testutil.go b/pkg/testutil/testutil.go index f38fbdf53c9..2409602ae06 100644 --- a/pkg/testutil/testutil.go +++ b/pkg/testutil/testutil.go @@ -163,6 +163,17 @@ func (b *Base) InspectContainer(name string) dockercompat.Container { return dc[0] } +func (b *Base) InspectImage(name string) dockercompat.Image { + cmdResult := b.Cmd("image", "inspect", name).Run() + assert.Equal(b.T, cmdResult.ExitCode, 0) + var dc []dockercompat.Image + if err := json.Unmarshal([]byte(cmdResult.Stdout()), &dc); err != nil { + b.T.Fatal(err) + } + assert.Equal(b.T, 1, len(dc)) + return dc[0] +} + func (b *Base) Info() dockercompat.Info { cmdResult := b.Cmd("info", "--format", "{{ json . }}").Run() assert.Equal(b.T, cmdResult.ExitCode, 0) From bcd29a70902726dc6222be807f55e3a88cc50a0b Mon Sep 17 00:00:00 2001 From: Akihiro Suda Date: Sat, 19 Jun 2021 17:57:16 +0900 Subject: [PATCH 2/2] follow-up to "setup nerdctl inspect cmd" Signed-off-by: Akihiro Suda --- README.md | 12 +++++++++++- pkg/imageinspector/imageinspector.go | 4 ++-- pkg/inspecttypes/dockercompat/dockercompat.go | 4 ++-- pkg/inspecttypes/native/image.go | 7 ++++--- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 45531a2f41a..111468c35ac 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ It does not necessarily mean that the corresponding features are missing in cont - [:whale: nerdctl save](#whale-nerdctl-save) - [:whale: nerdctl tag](#whale-nerdctl-tag) - [:whale: nerdctl rmi](#whale-nerdctl-rmi) + - [:whale: nerdctl image inspect](#whale-nerdctl-image-inspect) - [:nerd_face: nerdctl image convert](#nerd_face-nerdctl-image-convert) - [Registry](#registry) - [:whale: nerdctl login](#whale-nerdctl-login) @@ -621,6 +622,16 @@ Usage: `nerdctl rmi [OPTIONS] IMAGE [IMAGE...]` Unimplemented `docker rmi` flags: `--force`, `--no-prune` +### :whale: nerdctl image inspect +Display detailed information on one or more images. + +Usage: `nerctl image inspect [OPTIONS] NAME|ID [NAME|ID...]` + +Flags: +- :nerd_face: `--mode=(dockercompat|native)`: Inspection mode. "native" produces more information. + +Unimplemented `docker image inspect` flags: `--format` + ### :nerd_face: nerdctl image convert Convert an image format. @@ -865,7 +876,6 @@ Image: - `docker export` and `docker import` - `docker history` -- `docker image inspect` - `docker image prune` - `docker trust *` diff --git a/pkg/imageinspector/imageinspector.go b/pkg/imageinspector/imageinspector.go index bf81d5e21da..26d6b6b7690 100644 --- a/pkg/imageinspector/imageinspector.go +++ b/pkg/imageinspector/imageinspector.go @@ -37,7 +37,7 @@ func Inspect(ctx context.Context, client *containerd.Client, image images.Image) logrus.WithError(err).WithField("id", image.Name).Warnf("failed to inspect Rootfs") return nil, err } - n.ImageSpec = imageConfig + n.ImageConfig = imageConfig cs := client.ContentStore() config, err := image.Config(ctx, cs, platforms.DefaultStrict()) @@ -46,7 +46,7 @@ func Inspect(ctx context.Context, client *containerd.Client, image images.Image) return nil, err } - n.Descriptor = config + n.ImageConfigDesc = config n.Image = image return n, nil diff --git a/pkg/inspecttypes/dockercompat/dockercompat.go b/pkg/inspecttypes/dockercompat/dockercompat.go index 1656ab2872b..dc9b9a01a11 100644 --- a/pkg/inspecttypes/dockercompat/dockercompat.go +++ b/pkg/inspecttypes/dockercompat/dockercompat.go @@ -244,7 +244,7 @@ func ContainerFromNative(n *native.Container) (*Container, error) { func ImageFromNative(n *native.Image) (*Image, error) { i := &Image{} - imgoci := n.ImageSpec + imgoci := n.ImageConfig i.RootFS.Type = imgoci.RootFS.Type diffIDs := imgoci.RootFS.DiffIDs @@ -273,7 +273,7 @@ func ImageFromNative(n *native.Image) (*Image, error) { ExposedPorts: portSet, } - i.ID = n.Descriptor.Digest.String() + i.ID = n.ImageConfigDesc.Digest.String() repository, tag := imgutil.ParseRepoTag(n.Image.Name) diff --git a/pkg/inspecttypes/native/image.go b/pkg/inspecttypes/native/image.go index 62f3540441e..46aed7bc1a1 100644 --- a/pkg/inspecttypes/native/image.go +++ b/pkg/inspecttypes/native/image.go @@ -24,7 +24,8 @@ import ( // Image corresponds to a containerd-native image object. // Not compatible with `docker image inspect`. type Image struct { - images.Image - ImageSpec ocispec.Image `json:"ImageSpec"` - Descriptor ocispec.Descriptor `json:"Descriptor"` + Image images.Image `json:"Image"` + // e.g., "application/vnd.docker.container.image.v1+json" + ImageConfigDesc ocispec.Descriptor `json:"ImageConfigDesc"` + ImageConfig ocispec.Image `json:"ImageConfig"` }