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 }