
add '--snapshotter-labels' in ctr run and ctr c create which can pass labels to snappshotter on preparing new snapshot. Pass command label to snapshotter can help it determine which kind of writable snapshots should be provide. For some snapshotter, such as overlaybd: ( https://github.com/alibaba/accelerated-container-image ), it can provide 2 kind of writable snapshot (overlayfs dir or blockdevice) by command label values. Signed-off-by: Yifan Yuan <tuji.yyf@alibaba-inc.com>
302 lines
7.4 KiB
Go
302 lines
7.4 KiB
Go
/*
|
|
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(append(commands.SnapshotterFlags, []cli.Flag{commands.SnapshotterLabels}...), 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] [<filter>, ...]",
|
|
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 [<key>=<value>, ...]",
|
|
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",
|
|
Flags: []cli.Flag{
|
|
cli.BoolFlag{
|
|
Name: "spec",
|
|
Usage: "only display the spec",
|
|
},
|
|
},
|
|
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 context.Bool("spec") {
|
|
v, err := typeurl.UnmarshalAny(info.Spec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
commands.PrintAsJSON(v)
|
|
return nil
|
|
}
|
|
|
|
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
|
|
},
|
|
}
|