github.com/demonoid81/containerd@v1.3.4/cmd/ctr/commands/images/images.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package images
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"sort"
    23  	"strings"
    24  	"text/tabwriter"
    25  
    26  	"github.com/containerd/containerd/cmd/ctr/commands"
    27  	"github.com/containerd/containerd/errdefs"
    28  	"github.com/containerd/containerd/images"
    29  	"github.com/containerd/containerd/log"
    30  	"github.com/containerd/containerd/pkg/progress"
    31  	"github.com/containerd/containerd/platforms"
    32  	"github.com/pkg/errors"
    33  	"github.com/urfave/cli"
    34  )
    35  
    36  // Command is the cli command for managing images
    37  var Command = cli.Command{
    38  	Name:    "images",
    39  	Aliases: []string{"image", "i"},
    40  	Usage:   "manage images",
    41  	Subcommands: cli.Commands{
    42  		checkCommand,
    43  		exportCommand,
    44  		importCommand,
    45  		listCommand,
    46  		pullCommand,
    47  		pushCommand,
    48  		removeCommand,
    49  		tagCommand,
    50  		setLabelsCommand,
    51  	},
    52  }
    53  
    54  var listCommand = cli.Command{
    55  	Name:        "list",
    56  	Aliases:     []string{"ls"},
    57  	Usage:       "list images known to containerd",
    58  	ArgsUsage:   "[flags] [<filter>, ...]",
    59  	Description: "list images registered with containerd",
    60  	Flags: []cli.Flag{
    61  		cli.BoolFlag{
    62  			Name:  "quiet, q",
    63  			Usage: "print only the image refs",
    64  		},
    65  	},
    66  	Action: func(context *cli.Context) error {
    67  		var (
    68  			filters = context.Args()
    69  			quiet   = context.Bool("quiet")
    70  		)
    71  		client, ctx, cancel, err := commands.NewClient(context)
    72  		if err != nil {
    73  			return err
    74  		}
    75  		defer cancel()
    76  		var (
    77  			imageStore = client.ImageService()
    78  			cs         = client.ContentStore()
    79  		)
    80  		imageList, err := imageStore.List(ctx, filters...)
    81  		if err != nil {
    82  			return errors.Wrap(err, "failed to list images")
    83  		}
    84  		if quiet {
    85  			for _, image := range imageList {
    86  				fmt.Println(image.Name)
    87  			}
    88  			return nil
    89  		}
    90  		tw := tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
    91  		fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSIZE\tPLATFORMS\tLABELS\t")
    92  		for _, image := range imageList {
    93  			size, err := image.Size(ctx, cs, platforms.Default())
    94  			if err != nil {
    95  				log.G(ctx).WithError(err).Errorf("failed calculating size for image %s", image.Name)
    96  			}
    97  
    98  			platformColumn := "-"
    99  			specs, err := images.Platforms(ctx, cs, image.Target)
   100  			if err != nil {
   101  				log.G(ctx).WithError(err).Errorf("failed resolving platform for image %s", image.Name)
   102  			} else if len(specs) > 0 {
   103  				psm := map[string]struct{}{}
   104  				for _, p := range specs {
   105  					psm[platforms.Format(p)] = struct{}{}
   106  				}
   107  				var ps []string
   108  				for p := range psm {
   109  					ps = append(ps, p)
   110  				}
   111  				sort.Stable(sort.StringSlice(ps))
   112  				platformColumn = strings.Join(ps, ",")
   113  			}
   114  
   115  			labels := "-"
   116  			if len(image.Labels) > 0 {
   117  				var pairs []string
   118  				for k, v := range image.Labels {
   119  					pairs = append(pairs, fmt.Sprintf("%v=%v", k, v))
   120  				}
   121  				sort.Strings(pairs)
   122  				labels = strings.Join(pairs, ",")
   123  			}
   124  
   125  			fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%s\t\n",
   126  				image.Name,
   127  				image.Target.MediaType,
   128  				image.Target.Digest,
   129  				progress.Bytes(size),
   130  				platformColumn,
   131  				labels)
   132  		}
   133  
   134  		return tw.Flush()
   135  	},
   136  }
   137  
   138  var setLabelsCommand = cli.Command{
   139  	Name:        "label",
   140  	Usage:       "set and clear labels for an image",
   141  	ArgsUsage:   "[flags] <name> [<key>=<value>, ...]",
   142  	Description: "set and clear labels for an image",
   143  	Flags: []cli.Flag{
   144  		cli.BoolFlag{
   145  			Name:  "replace-all, r",
   146  			Usage: "replace all labels",
   147  		},
   148  	},
   149  	Action: func(context *cli.Context) error {
   150  		var (
   151  			replaceAll   = context.Bool("replace-all")
   152  			name, labels = commands.ObjectWithLabelArgs(context)
   153  		)
   154  		client, ctx, cancel, err := commands.NewClient(context)
   155  		if err != nil {
   156  			return err
   157  		}
   158  		defer cancel()
   159  		if name == "" {
   160  			return errors.New("please specify an image")
   161  		}
   162  
   163  		var (
   164  			is         = client.ImageService()
   165  			fieldpaths []string
   166  		)
   167  
   168  		for k := range labels {
   169  			if replaceAll {
   170  				fieldpaths = append(fieldpaths, "labels")
   171  			} else {
   172  				fieldpaths = append(fieldpaths, strings.Join([]string{"labels", k}, "."))
   173  			}
   174  		}
   175  
   176  		image := images.Image{
   177  			Name:   name,
   178  			Labels: labels,
   179  		}
   180  
   181  		updated, err := is.Update(ctx, image, fieldpaths...)
   182  		if err != nil {
   183  			return err
   184  		}
   185  
   186  		var labelStrings []string
   187  		for k, v := range updated.Labels {
   188  			labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
   189  		}
   190  
   191  		fmt.Println(strings.Join(labelStrings, ","))
   192  
   193  		return nil
   194  	},
   195  }
   196  
   197  var checkCommand = cli.Command{
   198  	Name:        "check",
   199  	Usage:       "check that an image has all content available locally",
   200  	ArgsUsage:   "[flags] [<filter>, ...]",
   201  	Description: "check that an image has all content available locally",
   202  	Flags:       commands.SnapshotterFlags,
   203  	Action: func(context *cli.Context) error {
   204  		var (
   205  			exitErr error
   206  		)
   207  		client, ctx, cancel, err := commands.NewClient(context)
   208  		if err != nil {
   209  			return err
   210  		}
   211  		defer cancel()
   212  		var (
   213  			contentStore = client.ContentStore()
   214  			tw           = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
   215  		)
   216  		fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSTATUS\tSIZE\tUNPACKED\t")
   217  
   218  		args := []string(context.Args())
   219  		imageList, err := client.ListImages(ctx, args...)
   220  		if err != nil {
   221  			return errors.Wrap(err, "failed listing images")
   222  		}
   223  
   224  		for _, image := range imageList {
   225  			var (
   226  				status       string = "complete"
   227  				size         string
   228  				requiredSize int64
   229  				presentSize  int64
   230  			)
   231  
   232  			available, required, present, missing, err := images.Check(ctx, contentStore, image.Target(), platforms.Default())
   233  			if err != nil {
   234  				if exitErr == nil {
   235  					exitErr = errors.Wrapf(err, "unable to check %v", image.Name())
   236  				}
   237  				log.G(ctx).WithError(err).Errorf("unable to check %v", image.Name())
   238  				status = "error"
   239  			}
   240  
   241  			if status != "error" {
   242  				for _, d := range required {
   243  					requiredSize += d.Size
   244  				}
   245  
   246  				for _, d := range present {
   247  					presentSize += d.Size
   248  				}
   249  
   250  				if len(missing) > 0 {
   251  					status = "incomplete"
   252  				}
   253  
   254  				if available {
   255  					status += fmt.Sprintf(" (%v/%v)", len(present), len(required))
   256  					size = fmt.Sprintf("%v/%v", progress.Bytes(presentSize), progress.Bytes(requiredSize))
   257  				} else {
   258  					status = fmt.Sprintf("unavailable (%v/?)", len(present))
   259  					size = fmt.Sprintf("%v/?", progress.Bytes(presentSize))
   260  				}
   261  			} else {
   262  				size = "-"
   263  			}
   264  
   265  			unpacked, err := image.IsUnpacked(ctx, context.String("snapshotter"))
   266  			if err != nil {
   267  				if exitErr == nil {
   268  					exitErr = errors.Wrapf(err, "unable to check unpack for %v", image.Name())
   269  				}
   270  				log.G(ctx).WithError(err).Errorf("unable to check unpack for %v", image.Name())
   271  			}
   272  
   273  			fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%t\n",
   274  				image.Name(),
   275  				image.Target().MediaType,
   276  				image.Target().Digest,
   277  				status,
   278  				size,
   279  				unpacked)
   280  		}
   281  		tw.Flush()
   282  
   283  		return exitErr
   284  	},
   285  }
   286  
   287  var removeCommand = cli.Command{
   288  	Name:        "remove",
   289  	Aliases:     []string{"rm"},
   290  	Usage:       "remove one or more images by reference",
   291  	ArgsUsage:   "[flags] <ref> [<ref>, ...]",
   292  	Description: "remove one or more images by reference",
   293  	Flags: []cli.Flag{
   294  		cli.BoolFlag{
   295  			Name:  "sync",
   296  			Usage: "Synchronously remove image and all associated resources",
   297  		},
   298  	},
   299  	Action: func(context *cli.Context) error {
   300  		client, ctx, cancel, err := commands.NewClient(context)
   301  		if err != nil {
   302  			return err
   303  		}
   304  		defer cancel()
   305  		var (
   306  			exitErr    error
   307  			imageStore = client.ImageService()
   308  		)
   309  		for i, target := range context.Args() {
   310  			var opts []images.DeleteOpt
   311  			if context.Bool("sync") && i == context.NArg()-1 {
   312  				opts = append(opts, images.SynchronousDelete())
   313  			}
   314  			if err := imageStore.Delete(ctx, target, opts...); err != nil {
   315  				if !errdefs.IsNotFound(err) {
   316  					if exitErr == nil {
   317  						exitErr = errors.Wrapf(err, "unable to delete %v", target)
   318  					}
   319  					log.G(ctx).WithError(err).Errorf("unable to delete %v", target)
   320  					continue
   321  				}
   322  				// image ref not found in metadata store; log not found condition
   323  				log.G(ctx).Warnf("%v: image not found", target)
   324  			} else {
   325  				fmt.Println(target)
   326  			}
   327  		}
   328  
   329  		return exitErr
   330  	},
   331  }