github.com/containerd/Containerd@v1.4.13/cmd/ctr/commands/containers/containers.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 containers 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "strings" 24 "text/tabwriter" 25 26 "github.com/containerd/containerd" 27 "github.com/containerd/containerd/cio" 28 "github.com/containerd/containerd/cmd/ctr/commands" 29 "github.com/containerd/containerd/cmd/ctr/commands/run" 30 "github.com/containerd/containerd/containers" 31 "github.com/containerd/containerd/errdefs" 32 "github.com/containerd/containerd/log" 33 "github.com/containerd/typeurl" 34 "github.com/pkg/errors" 35 "github.com/urfave/cli" 36 ) 37 38 // Command is the cli command for managing containers 39 var Command = cli.Command{ 40 Name: "containers", 41 Usage: "manage containers", 42 Aliases: []string{"c", "container"}, 43 Subcommands: []cli.Command{ 44 createCommand, 45 deleteCommand, 46 infoCommand, 47 listCommand, 48 setLabelsCommand, 49 checkpointCommand, 50 restoreCommand, 51 }, 52 } 53 54 var createCommand = cli.Command{ 55 Name: "create", 56 Usage: "create container", 57 ArgsUsage: "[flags] Image|RootFS CONTAINER [COMMAND] [ARG...]", 58 Flags: append(commands.SnapshotterFlags, commands.ContainerFlags...), 59 Action: func(context *cli.Context) error { 60 var ( 61 id string 62 ref string 63 config = context.IsSet("config") 64 ) 65 66 if config { 67 id = context.Args().First() 68 if context.NArg() > 1 { 69 return errors.Wrap(errdefs.ErrInvalidArgument, "with spec config file, only container id should be provided") 70 } 71 } else { 72 id = context.Args().Get(1) 73 ref = context.Args().First() 74 if ref == "" { 75 return errors.Wrap(errdefs.ErrInvalidArgument, "image ref must be provided") 76 } 77 } 78 if id == "" { 79 return errors.Wrap(errdefs.ErrInvalidArgument, "container id must be provided") 80 } 81 client, ctx, cancel, err := commands.NewClient(context) 82 if err != nil { 83 return err 84 } 85 defer cancel() 86 _, err = run.NewContainer(ctx, client, context) 87 if err != nil { 88 return err 89 } 90 return nil 91 }, 92 } 93 94 var listCommand = cli.Command{ 95 Name: "list", 96 Aliases: []string{"ls"}, 97 Usage: "list containers", 98 ArgsUsage: "[flags] [<filter>, ...]", 99 Flags: []cli.Flag{ 100 cli.BoolFlag{ 101 Name: "quiet, q", 102 Usage: "print only the container id", 103 }, 104 }, 105 Action: func(context *cli.Context) error { 106 var ( 107 filters = context.Args() 108 quiet = context.Bool("quiet") 109 ) 110 client, ctx, cancel, err := commands.NewClient(context) 111 if err != nil { 112 return err 113 } 114 defer cancel() 115 containers, err := client.Containers(ctx, filters...) 116 if err != nil { 117 return err 118 } 119 if quiet { 120 for _, c := range containers { 121 fmt.Printf("%s\n", c.ID()) 122 } 123 return nil 124 } 125 w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0) 126 fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t") 127 for _, c := range containers { 128 info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata) 129 if err != nil { 130 return err 131 } 132 imageName := info.Image 133 if imageName == "" { 134 imageName = "-" 135 } 136 if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n", 137 c.ID(), 138 imageName, 139 info.Runtime.Name, 140 ); err != nil { 141 return err 142 } 143 } 144 return w.Flush() 145 }, 146 } 147 148 var deleteCommand = cli.Command{ 149 Name: "delete", 150 Usage: "delete one or more existing containers", 151 ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]", 152 Aliases: []string{"del", "rm"}, 153 Flags: []cli.Flag{ 154 cli.BoolFlag{ 155 Name: "keep-snapshot", 156 Usage: "do not clean up snapshot with container", 157 }, 158 }, 159 Action: func(context *cli.Context) error { 160 var exitErr error 161 client, ctx, cancel, err := commands.NewClient(context) 162 if err != nil { 163 return err 164 } 165 defer cancel() 166 deleteOpts := []containerd.DeleteOpts{} 167 if !context.Bool("keep-snapshot") { 168 deleteOpts = append(deleteOpts, containerd.WithSnapshotCleanup) 169 } 170 171 if context.NArg() == 0 { 172 return errors.Wrap(errdefs.ErrInvalidArgument, "must specify at least one container to delete") 173 } 174 for _, arg := range context.Args() { 175 if err := deleteContainer(ctx, client, arg, deleteOpts...); err != nil { 176 if exitErr == nil { 177 exitErr = err 178 } 179 log.G(ctx).WithError(err).Errorf("failed to delete container %q", arg) 180 } 181 } 182 return exitErr 183 }, 184 } 185 186 func deleteContainer(ctx context.Context, client *containerd.Client, id string, opts ...containerd.DeleteOpts) error { 187 container, err := client.LoadContainer(ctx, id) 188 if err != nil { 189 return err 190 } 191 task, err := container.Task(ctx, cio.Load) 192 if err != nil { 193 return container.Delete(ctx, opts...) 194 } 195 status, err := task.Status(ctx) 196 if err != nil { 197 return err 198 } 199 if status.Status == containerd.Stopped || status.Status == containerd.Created { 200 if _, err := task.Delete(ctx); err != nil { 201 return err 202 } 203 return container.Delete(ctx, opts...) 204 } 205 return fmt.Errorf("cannot delete a non stopped container: %v", status) 206 207 } 208 209 var setLabelsCommand = cli.Command{ 210 Name: "label", 211 Usage: "set and clear labels for a container", 212 ArgsUsage: "[flags] CONTAINER [<key>=<value>, ...]", 213 Description: "set and clear labels for a container", 214 Flags: []cli.Flag{}, 215 Action: func(context *cli.Context) error { 216 containerID, labels := commands.ObjectWithLabelArgs(context) 217 if containerID == "" { 218 return errors.Wrap(errdefs.ErrInvalidArgument, "container id must be provided") 219 } 220 client, ctx, cancel, err := commands.NewClient(context) 221 if err != nil { 222 return err 223 } 224 defer cancel() 225 226 container, err := client.LoadContainer(ctx, containerID) 227 if err != nil { 228 return err 229 } 230 231 setlabels, err := container.SetLabels(ctx, labels) 232 if err != nil { 233 return err 234 } 235 236 var labelStrings []string 237 for k, v := range setlabels { 238 labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v)) 239 } 240 241 fmt.Println(strings.Join(labelStrings, ",")) 242 243 return nil 244 }, 245 } 246 247 var infoCommand = cli.Command{ 248 Name: "info", 249 Usage: "get info about a container", 250 ArgsUsage: "CONTAINER", 251 Flags: []cli.Flag{ 252 cli.BoolFlag{ 253 Name: "spec", 254 Usage: "only display the spec", 255 }, 256 }, 257 Action: func(context *cli.Context) error { 258 id := context.Args().First() 259 if id == "" { 260 return errors.Wrap(errdefs.ErrInvalidArgument, "container id must be provided") 261 } 262 client, ctx, cancel, err := commands.NewClient(context) 263 if err != nil { 264 return err 265 } 266 defer cancel() 267 container, err := client.LoadContainer(ctx, id) 268 if err != nil { 269 return err 270 } 271 info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata) 272 if err != nil { 273 return err 274 } 275 if context.Bool("spec") { 276 v, err := typeurl.UnmarshalAny(info.Spec) 277 if err != nil { 278 return err 279 } 280 commands.PrintAsJSON(v) 281 return nil 282 } 283 284 if info.Spec != nil && info.Spec.Value != nil { 285 v, err := typeurl.UnmarshalAny(info.Spec) 286 if err != nil { 287 return err 288 } 289 commands.PrintAsJSON(struct { 290 containers.Container 291 Spec interface{} `json:"Spec,omitempty"` 292 }{ 293 Container: info, 294 Spec: v, 295 }) 296 return nil 297 } 298 commands.PrintAsJSON(info) 299 return nil 300 }, 301 }