github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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  }