Skip to content

Commit

Permalink
setup nerdctl inspect cmd
Browse files Browse the repository at this point in the history
Signed-off-by: fahed dorgaa <fahed.dorgaa@gmail.com>
  • Loading branch information
fahedouch committed Jun 17, 2021
1 parent f6c5862 commit a918ad2
Show file tree
Hide file tree
Showing 13 changed files with 487 additions and 53 deletions.
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
}
53 changes: 53 additions & 0 deletions pkg/imageinspector/imageinspector.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading

0 comments on commit a918ad2

Please sign in to comment.