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 }