/* Copyright The containerd Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package containers import ( "context" "fmt" "os" "strings" "text/tabwriter" "github.com/containerd/containerd" "github.com/containerd/containerd/cio" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands/run" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" "github.com/containerd/typeurl" "github.com/pkg/errors" "github.com/urfave/cli" ) // Command is the cli command for managing containers var Command = cli.Command{ Name: "containers", Usage: "manage containers", Aliases: []string{"c", "container"}, Subcommands: []cli.Command{ createCommand, deleteCommand, infoCommand, listCommand, setLabelsCommand, checkpointCommand, restoreCommand, }, } var createCommand = cli.Command{ Name: "create", Usage: "create container", ArgsUsage: "[flags] Image|RootFS CONTAINER [COMMAND] [ARG...]", Flags: append(commands.SnapshotterFlags, commands.ContainerFlags...), Action: func(context *cli.Context) error { var ( id string ref string config = context.IsSet("config") ) if config { id = context.Args().First() if context.NArg() > 1 { return errors.Wrap(errdefs.ErrInvalidArgument, "with spec config file, only container id should be provided") } } else { id = context.Args().Get(1) ref = context.Args().First() if ref == "" { return errors.Wrap(errdefs.ErrInvalidArgument, "image ref must be provided") } } if id == "" { return errors.Wrap(errdefs.ErrInvalidArgument, "container id must be provided") } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() _, err = run.NewContainer(ctx, client, context) if err != nil { return err } return nil }, } var listCommand = cli.Command{ Name: "list", Aliases: []string{"ls"}, Usage: "list containers", ArgsUsage: "[flags] [, ...]", Flags: []cli.Flag{ cli.BoolFlag{ Name: "quiet, q", Usage: "print only the container id", }, }, Action: func(context *cli.Context) error { var ( filters = context.Args() quiet = context.Bool("quiet") ) client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() containers, err := client.Containers(ctx, filters...) if err != nil { return err } if quiet { for _, c := range containers { fmt.Printf("%s\n", c.ID()) } return nil } w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0) fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t") for _, c := range containers { info, err := c.Info(ctx, containerd.WithoutRefreshedMetadata) if err != nil { return err } imageName := info.Image if imageName == "" { imageName = "-" } if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n", c.ID(), imageName, info.Runtime.Name, ); err != nil { return err } } return w.Flush() }, } var deleteCommand = cli.Command{ Name: "delete", Usage: "delete one or more existing containers", ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]", Aliases: []string{"del", "rm"}, Flags: []cli.Flag{ cli.BoolFlag{ Name: "keep-snapshot", Usage: "do not clean up snapshot with container", }, }, Action: func(context *cli.Context) error { var exitErr error client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() deleteOpts := []containerd.DeleteOpts{} if !context.Bool("keep-snapshot") { deleteOpts = append(deleteOpts, containerd.WithSnapshotCleanup) } if context.NArg() == 0 { return errors.Wrap(errdefs.ErrInvalidArgument, "must specify at least one container to delete") } for _, arg := range context.Args() { if err := deleteContainer(ctx, client, arg, deleteOpts...); err != nil { if exitErr == nil { exitErr = err } log.G(ctx).WithError(err).Errorf("failed to delete container %q", arg) } } return exitErr }, } func deleteContainer(ctx context.Context, client *containerd.Client, id string, opts ...containerd.DeleteOpts) error { container, err := client.LoadContainer(ctx, id) if err != nil { return err } task, err := container.Task(ctx, cio.Load) if err != nil { return container.Delete(ctx, opts...) } status, err := task.Status(ctx) if err != nil { return err } if status.Status == containerd.Stopped || status.Status == containerd.Created { if _, err := task.Delete(ctx); err != nil { return err } return container.Delete(ctx, opts...) } return fmt.Errorf("cannot delete a non stopped container: %v", status) } var setLabelsCommand = cli.Command{ Name: "label", Usage: "set and clear labels for a container", ArgsUsage: "[flags] CONTAINER [=, ...]", Description: "set and clear labels for a container", Flags: []cli.Flag{}, Action: func(context *cli.Context) error { containerID, labels := commands.ObjectWithLabelArgs(context) if containerID == "" { return errors.Wrap(errdefs.ErrInvalidArgument, "container id must be provided") } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() container, err := client.LoadContainer(ctx, containerID) if err != nil { return err } setlabels, err := container.SetLabels(ctx, labels) if err != nil { return err } var labelStrings []string for k, v := range setlabels { labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v)) } fmt.Println(strings.Join(labelStrings, ",")) return nil }, } var infoCommand = cli.Command{ Name: "info", Usage: "get info about a container", ArgsUsage: "CONTAINER", Action: func(context *cli.Context) error { id := context.Args().First() if id == "" { return errors.Wrap(errdefs.ErrInvalidArgument, "container id must be provided") } client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() container, err := client.LoadContainer(ctx, id) if err != nil { return err } info, err := container.Info(ctx, containerd.WithoutRefreshedMetadata) if err != nil { return err } if info.Spec != nil && info.Spec.Value != nil { v, err := typeurl.UnmarshalAny(info.Spec) if err != nil { return err } commands.PrintAsJSON(struct { containers.Container Spec interface{} `json:"Spec,omitempty"` }{ Container: info, Spec: v, }) return nil } commands.PrintAsJSON(info) return nil }, }