ctr: move images command
Signed-off-by: Jess Valarezo <valarezo.jessica@gmail.com>
This commit is contained in:
102
cmd/ctr/commands/images/export.go
Normal file
102
cmd/ctr/commands/images/export.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/reference"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var exportCommand = cli.Command{
|
||||
Name: "export",
|
||||
Usage: "export an image",
|
||||
ArgsUsage: "[flags] <out> <image>",
|
||||
Description: "export an image to a tar stream",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "oci-ref-name",
|
||||
Value: "",
|
||||
Usage: "override org.opencontainers.image.ref.name annotation",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Usage: "digest of manifest",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest-type",
|
||||
Usage: "media type of manifest digest",
|
||||
Value: ocispec.MediaTypeImageManifest,
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
out = context.Args().First()
|
||||
local = context.Args().Get(1)
|
||||
desc ocispec.Descriptor
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
if manifest := context.String("manifest"); manifest != "" {
|
||||
desc.Digest, err = digest.Parse(manifest)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "invalid manifest digest")
|
||||
}
|
||||
desc.MediaType = context.String("manifest-type")
|
||||
} else {
|
||||
img, err := client.ImageService().Get(ctx, local)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to resolve image to manifest")
|
||||
}
|
||||
desc = img.Target
|
||||
}
|
||||
|
||||
if desc.Annotations == nil {
|
||||
desc.Annotations = make(map[string]string)
|
||||
}
|
||||
if s, ok := desc.Annotations[ocispec.AnnotationRefName]; !ok || s == "" {
|
||||
if ociRefName := determineOCIRefName(local); ociRefName != "" {
|
||||
desc.Annotations[ocispec.AnnotationRefName] = ociRefName
|
||||
}
|
||||
if ociRefName := context.String("oci-ref-name"); ociRefName != "" {
|
||||
desc.Annotations[ocispec.AnnotationRefName] = ociRefName
|
||||
}
|
||||
}
|
||||
var w io.WriteCloser
|
||||
if out == "-" {
|
||||
w = os.Stdout
|
||||
} else {
|
||||
w, err = os.Create(out)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
r, err := client.Export(ctx, desc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(w, r); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Close()
|
||||
},
|
||||
}
|
||||
|
||||
func determineOCIRefName(local string) string {
|
||||
refspec, err := reference.Parse(local)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
tag, _ := reference.SplitObject(refspec.Object)
|
||||
return tag
|
||||
}
|
||||
290
cmd/ctr/commands/images/images.go
Normal file
290
cmd/ctr/commands/images/images.go
Normal file
@@ -0,0 +1,290 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/progress"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Command is the cli command for managing images
|
||||
var Command = cli.Command{
|
||||
Name: "images",
|
||||
Usage: "manage images",
|
||||
Subcommands: cli.Commands{
|
||||
listCommand,
|
||||
checkCommand,
|
||||
removeCommand,
|
||||
setLabelsCommand,
|
||||
importCommand,
|
||||
exportCommand,
|
||||
},
|
||||
}
|
||||
|
||||
var listCommand = cli.Command{
|
||||
Name: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Usage: "list images known to containerd",
|
||||
ArgsUsage: "[flags] <ref>",
|
||||
Description: "list images registered with containerd",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "quiet, q",
|
||||
Usage: "print only the image refs",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
filters = context.Args()
|
||||
quiet = context.Bool("quiet")
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
imageStore = client.ImageService()
|
||||
cs = client.ContentStore()
|
||||
)
|
||||
imageList, err := imageStore.List(ctx, filters...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to list images")
|
||||
}
|
||||
if quiet {
|
||||
for _, image := range imageList {
|
||||
fmt.Println(image.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
|
||||
fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSIZE\tPLATFORM\tLABELS\t")
|
||||
for _, image := range imageList {
|
||||
size, err := image.Size(ctx, cs, platforms.Default())
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Errorf("failed calculating size for image %s", image.Name)
|
||||
}
|
||||
|
||||
platformColumn := "-"
|
||||
specs, err := images.Platforms(ctx, cs, image.Target)
|
||||
if err != nil {
|
||||
log.G(ctx).WithError(err).Errorf("failed resolving platform for image %s", image.Name)
|
||||
} else if len(specs) > 0 {
|
||||
psm := map[string]struct{}{}
|
||||
for _, p := range specs {
|
||||
psm[platforms.Format(p)] = struct{}{}
|
||||
}
|
||||
var ps []string
|
||||
for p := range psm {
|
||||
ps = append(ps, p)
|
||||
}
|
||||
sort.Stable(sort.StringSlice(ps))
|
||||
platformColumn = strings.Join(ps, ",")
|
||||
}
|
||||
|
||||
labels := "-"
|
||||
if len(image.Labels) > 0 {
|
||||
var pairs []string
|
||||
for k, v := range image.Labels {
|
||||
pairs = append(pairs, fmt.Sprintf("%v=%v", k, v))
|
||||
}
|
||||
sort.Strings(pairs)
|
||||
labels = strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%s\t\n",
|
||||
image.Name,
|
||||
image.Target.MediaType,
|
||||
image.Target.Digest,
|
||||
progress.Bytes(size),
|
||||
platformColumn,
|
||||
labels)
|
||||
}
|
||||
|
||||
return tw.Flush()
|
||||
},
|
||||
}
|
||||
|
||||
var setLabelsCommand = cli.Command{
|
||||
Name: "label",
|
||||
Usage: "set and clear labels for an image",
|
||||
ArgsUsage: "[flags] <name> [<key>=<value>, ...]",
|
||||
Description: "set and clear labels for an image",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "replace-all, r",
|
||||
Usage: "replace all labels",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
replaceAll = context.Bool("replace-all")
|
||||
name, labels = commands.ObjectWithLabelArgs(context)
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
if name == "" {
|
||||
return errors.New("please specify an image")
|
||||
}
|
||||
|
||||
var (
|
||||
is = client.ImageService()
|
||||
fieldpaths []string
|
||||
)
|
||||
|
||||
for k := range labels {
|
||||
if replaceAll {
|
||||
fieldpaths = append(fieldpaths, "labels")
|
||||
} else {
|
||||
fieldpaths = append(fieldpaths, strings.Join([]string{"labels", k}, "."))
|
||||
}
|
||||
}
|
||||
|
||||
image := images.Image{
|
||||
Name: name,
|
||||
Labels: labels,
|
||||
}
|
||||
|
||||
updated, err := is.Update(ctx, image, fieldpaths...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var labelStrings []string
|
||||
for k, v := range updated.Labels {
|
||||
labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
|
||||
fmt.Println(strings.Join(labelStrings, ","))
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var checkCommand = cli.Command{
|
||||
Name: "check",
|
||||
Usage: "check that an image has all content available locally",
|
||||
ArgsUsage: "<ref> [<ref>, ...]",
|
||||
Description: "check that an image has all content available locally",
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
exitErr error
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
imageStore = client.ImageService()
|
||||
contentStore = client.ContentStore()
|
||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
|
||||
)
|
||||
fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSTATUS\tSIZE\t")
|
||||
|
||||
args := []string(context.Args())
|
||||
imageList, err := imageStore.List(ctx, args...)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed listing images")
|
||||
}
|
||||
|
||||
for _, image := range imageList {
|
||||
var (
|
||||
status string = "complete"
|
||||
size string
|
||||
requiredSize int64
|
||||
presentSize int64
|
||||
)
|
||||
|
||||
available, required, present, missing, err := images.Check(ctx, contentStore, image.Target, platforms.Default())
|
||||
if err != nil {
|
||||
if exitErr == nil {
|
||||
exitErr = errors.Wrapf(err, "unable to check %v", image.Name)
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("unable to check %v", image.Name)
|
||||
status = "error"
|
||||
}
|
||||
|
||||
if status != "error" {
|
||||
for _, d := range required {
|
||||
requiredSize += d.Size
|
||||
}
|
||||
|
||||
for _, d := range present {
|
||||
presentSize += d.Size
|
||||
}
|
||||
|
||||
if len(missing) > 0 {
|
||||
status = "incomplete"
|
||||
}
|
||||
|
||||
if available {
|
||||
status += fmt.Sprintf(" (%v/%v)", len(present), len(required))
|
||||
size = fmt.Sprintf("%v/%v", progress.Bytes(presentSize), progress.Bytes(requiredSize))
|
||||
} else {
|
||||
status = fmt.Sprintf("unavailable (%v/?)", len(present))
|
||||
size = fmt.Sprintf("%v/?", progress.Bytes(presentSize))
|
||||
}
|
||||
} else {
|
||||
size = "-"
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t\n",
|
||||
image.Name,
|
||||
image.Target.MediaType,
|
||||
image.Target.Digest,
|
||||
status,
|
||||
size)
|
||||
}
|
||||
tw.Flush()
|
||||
|
||||
return exitErr
|
||||
},
|
||||
}
|
||||
|
||||
var removeCommand = cli.Command{
|
||||
Name: "remove",
|
||||
Aliases: []string{"rm"},
|
||||
Usage: "remove one or more images by reference",
|
||||
ArgsUsage: "<ref> [<ref>, ...]",
|
||||
Description: "remove one or more images by reference",
|
||||
Action: func(context *cli.Context) error {
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var (
|
||||
exitErr error
|
||||
imageStore = client.ImageService()
|
||||
)
|
||||
for _, target := range context.Args() {
|
||||
if err := imageStore.Delete(ctx, target); err != nil {
|
||||
if !errdefs.IsNotFound(err) {
|
||||
if exitErr == nil {
|
||||
exitErr = errors.Wrapf(err, "unable to delete %v", target)
|
||||
}
|
||||
log.G(ctx).WithError(err).Errorf("unable to delete %v", target)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(target)
|
||||
}
|
||||
|
||||
return exitErr
|
||||
},
|
||||
}
|
||||
69
cmd/ctr/commands/images/import.go
Normal file
69
cmd/ctr/commands/images/import.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd"
|
||||
"github.com/containerd/containerd/cmd/ctr/commands"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var importCommand = cli.Command{
|
||||
Name: "import",
|
||||
Usage: "import an image",
|
||||
ArgsUsage: "[flags] <ref> <in>",
|
||||
Description: "import an image from a tar stream",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "ref-object",
|
||||
Value: "",
|
||||
Usage: "reference object e.g. tag@digest (default: use the object specified in ref)",
|
||||
},
|
||||
commands.LabelFlag,
|
||||
},
|
||||
Action: func(context *cli.Context) error {
|
||||
var (
|
||||
ref = context.Args().First()
|
||||
in = context.Args().Get(1)
|
||||
refObject = context.String("ref-object")
|
||||
labels = commands.LabelArgs(context.StringSlice("label"))
|
||||
)
|
||||
client, ctx, cancel, err := commands.NewClient(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer cancel()
|
||||
var r io.ReadCloser
|
||||
if in == "-" {
|
||||
r = os.Stdin
|
||||
} else {
|
||||
r, err = os.Open(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
img, err := client.Import(ctx,
|
||||
ref,
|
||||
r,
|
||||
containerd.WithRefObject(refObject),
|
||||
containerd.WithImportLabels(labels),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = r.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.G(ctx).WithField("image", ref).Debug("unpacking")
|
||||
|
||||
// TODO: Show unpack status
|
||||
fmt.Printf("unpacking %s...", img.Target().Digest)
|
||||
err = img.Unpack(ctx, context.String("snapshotter"))
|
||||
fmt.Println("done")
|
||||
return err
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user