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