github.com/demonoid81/containerd@v1.3.4/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 Action: func(context *cli.Context) error { 252 id := context.Args().First() 253 if id == "" { 254 return errors.Wrap(errdefs.ErrInvalidArgument, "container id must be provided") 255 } 256 client, ctx, cancel, err := commands.NewClient(context) 257 if err != nil { 258 return err 259 } 260 defer cancel() 261 container, err := client.LoadContainer(ctx, id) 262 if err != nil { 263 return err 264 } 265 info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata) 266 if err != nil { 267 return err 268 } 269 270 if info.Spec != nil && info.Spec.Value != nil { 271 v, err := typeurl.UnmarshalAny(info.Spec) 272 if err != nil { 273 return err 274 } 275 commands.PrintAsJSON(struct { 276 containers.Container 277 Spec interface{} `json:"Spec,omitempty"` 278 }{ 279 Container: info, 280 Spec: v, 281 }) 282 return nil 283 } 284 commands.PrintAsJSON(info) 285 return nil 286 }, 287 }