Skip to content

Commit

Permalink
Merge pull request #239 from fahedouch/inspect-image
Browse files Browse the repository at this point in the history
Add Inspect image
  • Loading branch information
AkihiroSuda authored Jun 19, 2021
2 parents 5e43a15 + bcd29a7 commit 1b4635a
Show file tree
Hide file tree
Showing 14 changed files with 499 additions and 54 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,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)
Expand Down Expand Up @@ -630,6 +631,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.

Expand Down Expand Up @@ -874,7 +885,6 @@ Image:
- `docker export` and `docker import`
- `docker history`

- `docker image inspect`
- `docker image prune`

- `docker trust *`
Expand Down
4 changes: 2 additions & 2 deletions container_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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")
}
Expand Down
1 change: 1 addition & 0 deletions image.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var imageCommand = &cli.Command{
tagCommand,
imageRmCommand(),
imageConvertCommand,
imageInspectCommand,
},
}

Expand Down
134 changes: 134 additions & 0 deletions image_inspect.go
Original file line number Diff line number Diff line change
@@ -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)
}
36 changes: 36 additions & 0 deletions image_inspect_test.go
Original file line number Diff line number Diff line change
@@ -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 != "")
}
28 changes: 2 additions & 26 deletions images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
3 changes: 2 additions & 1 deletion images_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"testing"

"github.com/containerd/nerdctl/pkg/imgutil"
"gotest.tools/v3/assert"
)

Expand Down Expand Up @@ -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)
}
Expand Down
66 changes: 64 additions & 2 deletions inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading

0 comments on commit 1b4635a

Please sign in to comment.