diff --git a/cmd/ctr/commands/containers/containers.go b/cmd/ctr/commands/containers/containers.go new file mode 100644 index 000000000..9cf5df465 --- /dev/null +++ b/cmd/ctr/commands/containers/containers.go @@ -0,0 +1,210 @@ +package containers + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + "text/tabwriter" + + "github.com/containerd/containerd" + "github.com/containerd/containerd/cmd/ctr/commands" + "github.com/containerd/containerd/log" + "github.com/urfave/cli" +) + +// Command is the cli command for managing containers +var Command = cli.Command{ + Name: "containers", + Usage: "manage containers", + Aliases: []string{"c"}, + Subcommands: []cli.Command{ + listCommand, + deleteCommand, + setLabelsCommand, + infoCommand, + }, +} + +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) + 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.New("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, nil) + 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] [=, ...]", + 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.New("please specify a container") + } + 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.New("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) + if err != nil { + return err + } + commands.PrintAsJSON(info) + + return nil + }, +} diff --git a/cmd/ctr/content.go b/cmd/ctr/commands/content/content.go similarity index 88% rename from cmd/ctr/content.go rename to cmd/ctr/commands/content/content.go index 63587b1c5..424b81da3 100644 --- a/cmd/ctr/content.go +++ b/cmd/ctr/commands/content/content.go @@ -1,4 +1,4 @@ -package main +package content import ( "fmt" @@ -21,26 +21,26 @@ import ( ) var ( - contentCommand = cli.Command{ + // Command is the cli command for managing content + Command = cli.Command{ Name: "content", Usage: "manage content", Subcommands: cli.Commands{ - listContentCommand, - ingestContentCommand, + listCommand, + ingestCommand, activeIngestCommand, - getContentCommand, - editContentCommand, - deleteContentCommand, - labelContentCommand, + getCommand, + editCommand, + deleteCommand, + setLabelsCommand, }, } - getContentCommand = cli.Command{ + getCommand = cli.Command{ Name: "get", Usage: "get the data for an object", - ArgsUsage: "[flags] [, ...]", - Description: "Display the image object.", - Flags: []cli.Flag{}, + ArgsUsage: "[, ...]", + Description: "display the image object", Action: func(context *cli.Context) error { dgst, err := digest.Parse(context.Args().First()) if err != nil { @@ -63,11 +63,11 @@ var ( }, } - ingestContentCommand = cli.Command{ + ingestCommand = cli.Command{ Name: "ingest", Usage: "accept content into the store", ArgsUsage: "[flags] ", - Description: `Ingest objects into the local content store.`, + Description: "ingest objects into the local content store", Flags: []cli.Flag{ cli.Int64Flag{ Name: "expected-size", @@ -107,9 +107,9 @@ var ( activeIngestCommand = cli.Command{ Name: "active", - Usage: "display active transfers.", + Usage: "display active transfers", ArgsUsage: "[flags] []", - Description: `Display the ongoing transfers.`, + Description: "display the ongoing transfers", Flags: []cli.Flag{ cli.DurationFlag{ Name: "timeout, t", @@ -147,12 +147,12 @@ var ( }, } - listContentCommand = cli.Command{ + listCommand = cli.Command{ Name: "list", Aliases: []string{"ls"}, - Usage: "list all blobs in the store.", - ArgsUsage: "[flags] [, ...]", - Description: `List blobs in the content store.`, + Usage: "list all blobs in the store", + ArgsUsage: "[flags]", + Description: "list blobs in the content store", Flags: []cli.Flag{ cli.BoolFlag{ Name: "quiet, q", @@ -206,12 +206,11 @@ var ( }, } - labelContentCommand = cli.Command{ + setLabelsCommand = cli.Command{ Name: "label", Usage: "add labels to content", - ArgsUsage: "[flags] [