diff --git a/cmd/ctr/commands/commands.go b/cmd/ctr/commands/commands.go index 4470cf57..8c6b640d 100644 --- a/cmd/ctr/commands/commands.go +++ b/cmd/ctr/commands/commands.go @@ -37,6 +37,12 @@ var ( }, } + // SnapshotterLabels are cli flags specifying labels which will be add to the new snapshot for container. + SnapshotterLabels = cli.StringSliceFlag{ + Name: "snapshotter-label", + Usage: "labels added to the new snapshot for this container.", + } + // LabelFlag is a cli flag specifying labels LabelFlag = cli.StringSliceFlag{ Name: "label", @@ -78,6 +84,14 @@ var ( Name: "tlskey", Usage: "path to TLS client key", }, + cli.BoolFlag{ + Name: "http-dump", + Usage: "dump all HTTP request/responses when interacting with container registry", + }, + cli.BoolFlag{ + Name: "http-trace", + Usage: "enable HTTP tracing for registry interactions", + }, } // ContainerFlags are cli flags specifying container options @@ -92,19 +106,23 @@ var ( }, cli.StringSliceFlag{ Name: "env", - Usage: "specify additional container environment variables (i.e. FOO=bar)", + Usage: "specify additional container environment variables (e.g. FOO=bar)", }, cli.StringFlag{ Name: "env-file", - Usage: "specify additional container environment variables in a file(i.e. FOO=bar, one per line)", + Usage: "specify additional container environment variables in a file(e.g. FOO=bar, one per line)", }, cli.StringSliceFlag{ Name: "label", - Usage: "specify additional labels (i.e. foo=bar)", + Usage: "specify additional labels (e.g. foo=bar)", + }, + cli.StringSliceFlag{ + Name: "annotation", + Usage: "specify additional OCI annotations (e.g. foo=bar)", }, cli.StringSliceFlag{ Name: "mount", - Usage: "specify additional container mount (ex: type=bind,src=/tmp,dst=/host,options=rbind:ro)", + Usage: "specify additional container mount (e.g. type=bind,src=/tmp,dst=/host,options=rbind:ro)", }, cli.BoolFlag{ Name: "net-host", @@ -139,7 +157,7 @@ var ( Name: "pid-file", Usage: "file path to write the task's pid", }, - cli.IntFlag{ + cli.IntSliceFlag{ Name: "gpus", Usage: "add gpus to the container", }, @@ -153,7 +171,15 @@ var ( }, cli.StringSliceFlag{ Name: "device", - Usage: "add a device to a container", + Usage: "file path to a device to add to the container; or a path to a directory tree of devices to add to the container", + }, + cli.StringSliceFlag{ + Name: "cap-add", + Usage: "add Linux capabilities (Set capabilities with 'CAP_' prefix)", + }, + cli.StringSliceFlag{ + Name: "cap-drop", + Usage: "drop Linux capabilities (Set capabilities with 'CAP_' prefix)", }, cli.BoolFlag{ Name: "seccomp", @@ -171,6 +197,10 @@ var ( Name: "apparmor-profile", Usage: "enable AppArmor with an existing custom profile", }, + cli.StringFlag{ + Name: "rdt-class", + Usage: "name of the RDT class to associate the container with. Specifies a Class of Service (CLOS) for cache and memory bandwidth management.", + }, } // ImageDecryptionFlags are cli flags needed when decrypting an image ImageDecryptionFlags = []cli.Flag{ @@ -217,6 +247,19 @@ func LabelArgs(labelStrings []string) map[string]string { return labels } +// AnnotationArgs returns a map of annotation key,value pairs. +func AnnotationArgs(annoStrings []string) (map[string]string, error) { + annotations := make(map[string]string, len(annoStrings)) + for _, anno := range annoStrings { + parts := strings.SplitN(anno, "=", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("invalid key=value format annotation: %v", anno) + } + annotations[parts[0]] = parts[1] + } + return annotations, nil +} + // PrintAsJSON prints input in JSON format func PrintAsJSON(x interface{}) { b, err := json.MarshalIndent(x, "", " ") diff --git a/cmd/ctr/commands/containers/checkpoint.go b/cmd/ctr/commands/containers/checkpoint.go index 70d57761..62804f48 100644 --- a/cmd/ctr/commands/containers/checkpoint.go +++ b/cmd/ctr/commands/containers/checkpoint.go @@ -23,7 +23,6 @@ import ( "github.com/containerd/containerd" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/errdefs" - "github.com/urfave/cli" ) diff --git a/cmd/ctr/commands/containers/containers.go b/cmd/ctr/commands/containers/containers.go index 3db54c08..76af69e3 100644 --- a/cmd/ctr/commands/containers/containers.go +++ b/cmd/ctr/commands/containers/containers.go @@ -32,7 +32,6 @@ import ( "github.com/containerd/imgcrypt/cmd/ctr/commands/flags" "github.com/containerd/imgcrypt/cmd/ctr/commands/run" "github.com/containerd/typeurl" - "github.com/urfave/cli" ) @@ -150,7 +149,7 @@ var deleteCommand = cli.Command{ Name: "delete", Usage: "delete one or more existing containers", ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]", - Aliases: []string{"del", "rm"}, + Aliases: []string{"del", "remove", "rm"}, Flags: []cli.Flag{ cli.BoolFlag{ Name: "keep-snapshot", @@ -282,7 +281,7 @@ var infoCommand = cli.Command{ return nil } - if info.Spec != nil && info.Spec.GetValue() != nil { + if info.Spec != nil && info.Spec.Value != nil { v, err := typeurl.UnmarshalAny(info.Spec) if err != nil { return err diff --git a/cmd/ctr/commands/containers/restore.go b/cmd/ctr/commands/containers/restore.go index 9362cd9f..2847340c 100644 --- a/cmd/ctr/commands/containers/restore.go +++ b/cmd/ctr/commands/containers/restore.go @@ -23,7 +23,6 @@ import ( "github.com/containerd/containerd/cio" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/errdefs" - "github.com/urfave/cli" ) diff --git a/cmd/ctr/commands/images/export.go b/cmd/ctr/commands/images/export.go index f6231553..5050ff4f 100644 --- a/cmd/ctr/commands/images/export.go +++ b/cmd/ctr/commands/images/export.go @@ -25,7 +25,6 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/images/archive" "github.com/containerd/containerd/platforms" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/urfave/cli" ) @@ -81,7 +80,7 @@ When '--all-platforms' is given all images in a manifest list must be available. } exportOpts = append(exportOpts, archive.WithPlatform(platforms.Ordered(all...))) } else { - exportOpts = append(exportOpts, archive.WithPlatform(platforms.Default())) + exportOpts = append(exportOpts, archive.WithPlatform(platforms.DefaultStrict())) } if context.Bool("all-platforms") { diff --git a/cmd/ctr/commands/images/images.go b/cmd/ctr/commands/images/images.go index b43a89a9..ed979597 100644 --- a/cmd/ctr/commands/images/images.go +++ b/cmd/ctr/commands/images/images.go @@ -30,7 +30,6 @@ import ( "github.com/containerd/containerd/log" "github.com/containerd/containerd/pkg/progress" "github.com/containerd/containerd/platforms" - "github.com/urfave/cli" ) @@ -202,30 +201,42 @@ var setLabelsCommand = cli.Command{ var checkCommand = cli.Command{ Name: "check", - Usage: "check that an image has all content available locally", + Usage: "check existing images to ensure all content is available locally", ArgsUsage: "[flags] [, ...]", - Description: "check that an image has all content available locally", - Flags: commands.SnapshotterFlags, + Description: "check existing images to ensure all content is available locally", + Flags: append([]cli.Flag{ + cli.BoolFlag{ + Name: "quiet, q", + Usage: "print only the ready image refs (fully downloaded and unpacked)", + }, + }, commands.SnapshotterFlags...), Action: func(context *cli.Context) error { var ( exitErr error + quiet = context.Bool("quiet") ) client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err } defer cancel() - var ( - contentStore = client.ContentStore() - tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0) - ) - fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSTATUS\tSIZE\tUNPACKED\t") + + var contentStore = client.ContentStore() args := []string(context.Args()) imageList, err := client.ListImages(ctx, args...) if err != nil { return fmt.Errorf("failed listing images: %w", err) } + if len(imageList) == 0 { + log.G(ctx).Debugf("no images found") + return exitErr + } + + var tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0) + if !quiet { + fmt.Fprintln(tw, "REF\tTYPE\tDIGEST\tSTATUS\tSIZE\tUNPACKED\t") + } for _, image := range imageList { var ( @@ -233,6 +244,7 @@ var checkCommand = cli.Command{ size string requiredSize int64 presentSize int64 + complete bool = true ) available, required, present, missing, err := images.Check(ctx, contentStore, image.Target(), platforms.Default()) @@ -242,6 +254,7 @@ var checkCommand = cli.Command{ } log.G(ctx).WithError(err).Errorf("unable to check %v", image.Name()) status = "error" + complete = false } if status != "error" { @@ -255,6 +268,7 @@ var checkCommand = cli.Command{ if len(missing) > 0 { status = "incomplete" + complete = false } if available { @@ -263,6 +277,7 @@ var checkCommand = cli.Command{ } else { status = fmt.Sprintf("unavailable (%v/?)", len(present)) size = fmt.Sprintf("%v/?", progress.Bytes(presentSize)) + complete = false } } else { size = "-" @@ -276,23 +291,30 @@ var checkCommand = cli.Command{ log.G(ctx).WithError(err).Errorf("unable to check unpack for %v", image.Name()) } - fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%t\n", - image.Name(), - image.Target().MediaType, - image.Target().Digest, - status, - size, - unpacked) + if !quiet { + fmt.Fprintf(tw, "%v\t%v\t%v\t%v\t%v\t%t\n", + image.Name(), + image.Target().MediaType, + image.Target().Digest, + status, + size, + unpacked) + } else { + if complete { + fmt.Println(image.Name()) + } + } + } + if !quiet { + tw.Flush() } - tw.Flush() - return exitErr }, } var removeCommand = cli.Command{ - Name: "remove", - Aliases: []string{"rm"}, + Name: "delete", + Aliases: []string{"del", "remove", "rm"}, Usage: "remove one or more images by reference", ArgsUsage: "[flags] [, ...]", Description: "remove one or more images by reference", diff --git a/cmd/ctr/commands/images/import.go b/cmd/ctr/commands/images/import.go index 5c9b9c52..8e22ce2c 100644 --- a/cmd/ctr/commands/images/import.go +++ b/cmd/ctr/commands/images/import.go @@ -97,9 +97,9 @@ decrypting the image later on. Action: func(context *cli.Context) error { var ( - in = context.Args().First() - opts []containerd.ImportOpt - platformMacher platforms.MatchComparer + in = context.Args().First() + opts []containerd.ImportOpt + platformMatcher platforms.MatchComparer ) prefix := context.String("base-name") @@ -134,8 +134,8 @@ decrypting the image later on. if err != nil { return err } - platformMacher = platforms.Only(platSpec) - opts = append(opts, containerd.WithImportPlatform(platformMacher)) + platformMatcher = platforms.OnlyStrict(platSpec) + opts = append(opts, containerd.WithImportPlatform(platformMatcher)) } opts = append(opts, containerd.WithAllPlatforms(context.Bool("all-platforms"))) @@ -177,10 +177,10 @@ decrypting the image later on. log.G(ctx).Debugf("unpacking %d images", len(imgs)) for _, img := range imgs { - if platformMacher == nil { // if platform not specified use default. - platformMacher = platforms.Default() + if platformMatcher == nil { // if platform not specified use default. + platformMatcher = platforms.Default() } - image := containerd.NewImageWithPlatform(client, img, platformMacher) + image := containerd.NewImageWithPlatform(client, img, platformMatcher) // TODO: Show unpack status fmt.Printf("unpacking %s (%s)...", img.Name, img.Target.Digest) diff --git a/cmd/ctr/commands/images/mount.go b/cmd/ctr/commands/images/mount.go index db34a651..a907ad59 100644 --- a/cmd/ctr/commands/images/mount.go +++ b/cmd/ctr/commands/images/mount.go @@ -26,7 +26,6 @@ import ( "github.com/containerd/containerd/leases" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/platforms" - "github.com/opencontainers/image-spec/identity" "github.com/urfave/cli" ) diff --git a/cmd/ctr/commands/images/push.go b/cmd/ctr/commands/images/push.go index c6512328..71238780 100644 --- a/cmd/ctr/commands/images/push.go +++ b/cmd/ctr/commands/images/push.go @@ -35,8 +35,7 @@ import ( "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" - - "github.com/opencontainers/go-digest" + digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/urfave/cli" "golang.org/x/sync/errgroup" @@ -69,6 +68,9 @@ var pushCommand = cli.Command{ }, cli.IntFlag{ Name: "max-concurrent-uploaded-layers", Usage: "Set the max concurrent uploaded layers for each push", + }, cli.BoolFlag{ + Name: "allow-non-distributable-blobs", + Usage: "Allow pushing blobs that are marked as non-distributable", }), Action: func(context *cli.Context) error { var ( @@ -145,13 +147,21 @@ var pushCommand = cli.Command{ log.G(ctx).WithField("image", ref).WithField("digest", desc.Digest).Debug("pushing") jobHandler := images.HandlerFunc(func(ctx gocontext.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { + if !context.Bool("allow-non-distributable-blobs") && images.IsNonDistributable(desc.MediaType) { + return nil, nil + } ongoing.add(remotes.MakeRefKey(ctx, desc)) return nil, nil }) + handler := jobHandler + if !context.Bool("allow-non-distributable-blobs") { + handler = remotes.SkipNonDistributableBlobs(handler) + } + ropts := []containerd.RemoteOpt{ containerd.WithResolver(resolver), - containerd.WithImageHandler(jobHandler), + containerd.WithImageHandler(handler), } if context.IsSet("max-concurrent-uploaded-layers") { diff --git a/cmd/ctr/commands/images/unmount.go b/cmd/ctr/commands/images/unmount.go index 41672081..f98570d8 100644 --- a/cmd/ctr/commands/images/unmount.go +++ b/cmd/ctr/commands/images/unmount.go @@ -23,7 +23,6 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/leases" "github.com/containerd/containerd/mount" - "github.com/urfave/cli" ) diff --git a/cmd/ctr/commands/run/run.go b/cmd/ctr/commands/run/run.go index dbf56762..a2eb3969 100644 --- a/cmd/ctr/commands/run/run.go +++ b/cmd/ctr/commands/run/run.go @@ -30,12 +30,12 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands/tasks" "github.com/containerd/containerd/containers" + clabels "github.com/containerd/containerd/labels" "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/oci" gocni "github.com/containerd/go-cni" "github.com/containerd/imgcrypt/cmd/ctr/commands/flags" - - "github.com/opencontainers/runtime-spec/specs-go" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -65,8 +65,8 @@ func parseMountFlag(m string) (specs.Mount, error) { } for _, field := range fields { - v := strings.Split(field, "=") - if len(v) != 2 { + v := strings.SplitN(field, "=", 2) + if len(v) < 2 { return mount, fmt.Errorf("invalid mount specification: expected key=val") } @@ -98,7 +98,7 @@ var Command = cli.Command{ Flags: append([]cli.Flag{ cli.BoolFlag{ Name: "rm", - Usage: "remove the container after running", + Usage: "remove the container after running, cannot be used with --detach", }, cli.BoolFlag{ Name: "null-io", @@ -110,7 +110,7 @@ var Command = cli.Command{ }, cli.BoolFlag{ Name: "detach,d", - Usage: "detach from the task after it has started execution", + Usage: "detach from the task after it has started execution, cannot be used with --rm", }, cli.StringFlag{ Name: "fifo-dir", @@ -124,13 +124,20 @@ var Command = cli.Command{ Name: "platform", Usage: "run image for specific platform", }, - }, append(platformRunFlags, append(commands.SnapshotterFlags, append(commands.ContainerFlags, flags.ImageDecryptionFlags...)...)...)...), + cli.BoolFlag{ + Name: "cni", + Usage: "enable cni networking for the container", + }, + }, append(platformRunFlags, + append(append(append(commands.SnapshotterFlags, []cli.Flag{commands.SnapshotterLabels}...), + commands.ContainerFlags...),flags.ImageDecryptionFlags...)...)...), Action: func(context *cli.Context) error { var ( err error id string ref string + rm = context.Bool("rm") tty = context.Bool("tty") detach = context.Bool("detach") config = context.IsSet("config") @@ -153,6 +160,10 @@ var Command = cli.Command{ if id == "" { return errors.New("container id must be provided") } + if rm && detach { + return errors.New("flags --detach and --rm cannot be specified together") + } + client, ctx, cancel, err := commands.NewClient(context) if err != nil { return err @@ -162,7 +173,7 @@ var Command = cli.Command{ if err != nil { return err } - if context.Bool("rm") && !detach { + if rm && !detach { defer container.Delete(ctx, containerd.WithSnapshotCleanup) } var con console.Console @@ -208,7 +219,12 @@ var Command = cli.Command{ } } if enableCNI { - if _, err := network.Setup(ctx, fullID(ctx, container), fmt.Sprintf("/proc/%d/ns/net", task.Pid())); err != nil { + netNsPath, err := getNetNSPath(ctx, task) + if err != nil { + return err + } + + if _, err := network.Setup(ctx, fullID(ctx, container), netNsPath); err != nil { return err } } @@ -249,3 +265,22 @@ func fullID(ctx context.Context, c containerd.Container) string { } return fmt.Sprintf("%s-%s", ns, id) } + +// buildLabel builds the labels from command line labels and the image labels +func buildLabels(cmdLabels, imageLabels map[string]string) map[string]string { + labels := make(map[string]string) + for k, v := range imageLabels { + if err := clabels.Validate(k, v); err == nil { + labels[k] = v + } else { + // In case the image label is invalid, we output a warning and skip adding it to the + // container. + logrus.WithError(err).Warnf("unable to add image label with key %s to the container", k) + } + } + // labels from the command line will override image and the initial image config labels + for k, v := range cmdLabels { + labels[k] = v + } + return labels +} diff --git a/cmd/ctr/commands/run/run_unix.go b/cmd/ctr/commands/run/run_unix.go index 3b54cddc..f3f4b206 100644 --- a/cmd/ctr/commands/run/run_unix.go +++ b/cmd/ctr/commands/run/run_unix.go @@ -23,11 +23,13 @@ import ( gocontext "context" "errors" "fmt" + "os" "path/filepath" "strconv" "strings" "github.com/containerd/containerd" + "github.com/containerd/containerd/containers" "github.com/containerd/containerd/contrib/apparmor" "github.com/containerd/containerd/contrib/nvidia" "github.com/containerd/containerd/contrib/seccomp" @@ -35,6 +37,7 @@ import ( runtimeoptions "github.com/containerd/containerd/pkg/runtimeoptions/v1" "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/runtime/v2/runc/options" + "github.com/containerd/containerd/snapshots" "github.com/containerd/imgcrypt" "github.com/containerd/imgcrypt/cmd/ctr/commands" "github.com/containerd/imgcrypt/cmd/ctr/commands/images" @@ -76,9 +79,10 @@ var platformRunFlags = []cli.Flag{ Usage: "set the CFS cpu quota", Value: 0.0, }, - cli.BoolFlag{ - Name: "cni", - Usage: "enable cni networking for the container", + cli.IntFlag{ + Name: "cpu-shares", + Usage: "set the cpu shares", + Value: 1024, }, } @@ -100,8 +104,8 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli spec containerd.NewContainerOpts ) - cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) if config { + cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) opts = append(opts, oci.WithSpecFromFile(context.String("config"))) } else { var ( @@ -122,6 +126,7 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli return nil, err } opts = append(opts, oci.WithRootFSPath(rootfs)) + cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) } else { snapshotter := context.String("snapshotter") var image containerd.Image @@ -157,9 +162,12 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli return nil, err } } + labels := buildLabels(commands.LabelArgs(context.StringSlice("label")), image.Labels()) opts = append(opts, oci.WithImageConfig(image)) cOpts = append(cOpts, containerd.WithImage(image), + containerd.WithImageConfigLabels(image), + containerd.WithAdditionalContainerLabels(labels), containerd.WithSnapshotter(snapshotter)) if uidmap, gidmap := context.String("uidmap"), context.String("gidmap"); uidmap != "" && gidmap != "" { uidMap, err := parseIDMapping(uidmap) @@ -185,7 +193,10 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli // Even when "read-only" is set, we don't use KindView snapshot here. (#1495) // We pass writable snapshot to the OCI runtime, and the runtime remounts it as read-only, // after creating some mount points on demand. - cOpts = append(cOpts, containerd.WithNewSnapshot(id, image)) + // For some snapshotter, such as overlaybd, it can provide 2 kind of writable snapshot(overlayfs dir or block-device) + // by command label values. + cOpts = append(cOpts, containerd.WithNewSnapshot(id, image, + snapshots.WithLabels(commands.LabelArgs(context.StringSlice("snapshotter-label"))))) } cOpts = append(cOpts, containerd.WithImageStopSignal(image, "SIGTERM")) } @@ -205,7 +216,41 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli opts = append(opts, oci.WithPrivileged, oci.WithAllDevicesAllowed, oci.WithHostDevices) } if context.Bool("net-host") { - opts = append(opts, oci.WithHostNamespace(specs.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf) + hostname, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("get hostname: %w", err) + } + opts = append(opts, + oci.WithHostNamespace(specs.NetworkNamespace), + oci.WithHostHostsFile, + oci.WithHostResolvconf, + oci.WithEnv([]string{fmt.Sprintf("HOSTNAME=%s", hostname)}), + ) + } + if annoStrings := context.StringSlice("annotation"); len(annoStrings) > 0 { + annos, err := commands.AnnotationArgs(annoStrings) + if err != nil { + return nil, err + } + opts = append(opts, oci.WithAnnotations(annos)) + } + + if caps := context.StringSlice("cap-add"); len(caps) > 0 { + for _, cap := range caps { + if !strings.HasPrefix(cap, "CAP_") { + return nil, fmt.Errorf("capabilities must be specified with 'CAP_' prefix") + } + } + opts = append(opts, oci.WithAddedCapabilities(caps)) + } + + if caps := context.StringSlice("cap-drop"); len(caps) > 0 { + for _, cap := range caps { + if !strings.HasPrefix(cap, "CAP_") { + return nil, fmt.Errorf("capabilities must be specified with 'CAP_' prefix") + } + } + opts = append(opts, oci.WithDroppedCapabilities(caps)) } seccompProfile := context.String("seccomp-profile") @@ -241,6 +286,10 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli opts = append(opts, oci.WithCPUCFS(quota, period)) } + if shares := context.Int("cpu-shares"); shares > 0 { + opts = append(opts, oci.WithCPUShares(uint64(shares))) + } + quota := context.Int64("cpu-quota") period := context.Uint64("cpu-period") if quota != -1 || period != 0 { @@ -265,7 +314,7 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli })) } if context.IsSet("gpus") { - opts = append(opts, nvidia.WithGPUs(nvidia.WithDevices(context.Int("gpus")), nvidia.WithAllCapabilities)) + opts = append(opts, nvidia.WithGPUs(nvidia.WithDevices(context.IntSlice("gpus")...), nvidia.WithAllCapabilities)) } if context.IsSet("allow-new-privs") { opts = append(opts, oci.WithNewPrivileges) @@ -281,6 +330,25 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli for _, dev := range context.StringSlice("device") { opts = append(opts, oci.WithDevices(dev, "", "rwm")) } + + rootfsPropagation := context.String("rootfs-propagation") + if rootfsPropagation != "" { + opts = append(opts, func(_ gocontext.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { + if s.Linux != nil { + s.Linux.RootfsPropagation = rootfsPropagation + } else { + s.Linux = &specs.Linux{ + RootfsPropagation: rootfsPropagation, + } + } + + return nil + }) + } + + if c := context.String("rdt-class"); c != "" { + opts = append(opts, oci.WithRdt(c, "", "")) + } } runtimeOpts, err := getRuntimeOptions(context) @@ -411,3 +479,7 @@ func validNamespace(ns string) bool { return false } } + +func getNetNSPath(_ gocontext.Context, task containerd.Task) (string, error) { + return fmt.Sprintf("/proc/%d/ns/net", task.Pid()), nil +} diff --git a/cmd/ctr/commands/run/run_windows.go b/cmd/ctr/commands/run/run_windows.go index ef028259..612b150a 100644 --- a/cmd/ctr/commands/run/run_windows.go +++ b/cmd/ctr/commands/run/run_windows.go @@ -20,16 +20,17 @@ import ( gocontext "context" "errors" + "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" "github.com/containerd/console" "github.com/containerd/containerd" "github.com/containerd/containerd/oci" + "github.com/containerd/containerd/pkg/netns" "github.com/containerd/imgcrypt" "github.com/containerd/imgcrypt/cmd/ctr/commands" "github.com/containerd/imgcrypt/cmd/ctr/commands/images" "github.com/containerd/imgcrypt/images/encryption" "github.com/containerd/imgcrypt/images/encryption/parsehelpers" - "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -56,6 +57,7 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli if config { id = context.Args().First() opts = append(opts, oci.WithSpecFromFile(context.String("config"))) + cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) } else { var ( ref = context.Args().First() @@ -102,9 +104,13 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli } } opts = append(opts, oci.WithImageConfig(image)) - cOpts = append(cOpts, containerd.WithImage(image)) - cOpts = append(cOpts, containerd.WithSnapshotter(snapshotter)) - cOpts = append(cOpts, containerd.WithNewSnapshot(id, image)) + labels := buildLabels(commands.LabelArgs(context.StringSlice("label")), image.Labels()) + cOpts = append(cOpts, + containerd.WithImage(image), + containerd.WithImageConfigLabels(image), + containerd.WithSnapshotter(snapshotter), + containerd.WithNewSnapshot(id, image), + containerd.WithAdditionalContainerLabels(labels)) if len(args) > 0 { opts = append(opts, oci.WithProcessArgs(args...)) @@ -125,6 +131,13 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli if context.Bool("net-host") { return nil, errors.New("Cannot use host mode networking with Windows containers") } + if context.Bool("cni") { + ns, err := netns.NewNetNS("") + if err != nil { + return nil, err + } + opts = append(opts, oci.WithWindowsNetworkNamespace(ns.GetPath())) + } if context.Bool("isolated") { opts = append(opts, oci.WithWindowsHyperV) } @@ -138,7 +151,6 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli } } - cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) runtime := context.String("runtime") var runtimeOpts interface{} if runtime == "io.containerd.runhcs.v1" { @@ -167,3 +179,14 @@ func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli func getNewTaskOpts(_ *cli.Context) []containerd.NewTaskOpts { return nil } + +func getNetNSPath(ctx gocontext.Context, t containerd.Task) (string, error) { + s, err := t.Spec(ctx) + if err != nil { + return "", err + } + if s.Windows == nil || s.Windows.Network == nil { + return "", nil + } + return s.Windows.Network.NetworkNamespace, nil +}