diff --git a/client.go b/client.go index 39acb255a..075d09f74 100644 --- a/client.go +++ b/client.go @@ -36,7 +36,6 @@ import ( "github.com/containerd/containerd/snapshot" "github.com/containerd/containerd/typeurl" pempty "github.com/golang/protobuf/ptypes/empty" - "github.com/opencontainers/image-spec/identity" ocispec "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -53,33 +52,6 @@ func init() { typeurl.Register(&specs.WindowsResources{}, "opencontainers/runtime-spec", major, "WindowsResources") } -type clientOpts struct { - defaultns string - dialOptions []grpc.DialOption -} - -// ClientOpt allows callers to set options on the containerd client -type ClientOpt func(c *clientOpts) error - -// WithDefaultNamespace sets the default namespace on the client -// -// Any operation that does not have a namespace set on the context will -// be provided the default namespace -func WithDefaultNamespace(ns string) ClientOpt { - return func(c *clientOpts) error { - c.defaultns = ns - return nil - } -} - -// WithDialOpts allows grpc.DialOptions to be set on the connection -func WithDialOpts(opts []grpc.DialOption) ClientOpt { - return func(c *clientOpts) error { - c.dialOptions = opts - return nil - } -} - // New returns a new containerd client that is connected to the containerd // instance provided by address func New(address string, opts ...ClientOpt) (*Client, error) { @@ -154,90 +126,6 @@ func (c *Client) Containers(ctx context.Context, filters ...string) ([]Container return out, nil } -// NewContainerOpts allows the caller to set additional options when creating a container -type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error - -// WithContainerLabels adds the provided labels to the container -func WithContainerLabels(labels map[string]string) NewContainerOpts { - return func(_ context.Context, _ *Client, c *containers.Container) error { - c.Labels = labels - return nil - } -} - -// WithSnapshot uses an existing root filesystem for the container -func WithSnapshot(id string) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - // check that the snapshot exists, if not, fail on creation - if _, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, id); err != nil { - return err - } - c.RootFS = id - return nil - } -} - -// WithNewSnapshot allocates a new snapshot to be used by the container as the -// root filesystem in read-write mode -func WithNewSnapshot(id string, i Image) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) - if err != nil { - return err - } - if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, id, identity.ChainID(diffIDs).String()); err != nil { - return err - } - c.RootFS = id - c.Image = i.Name() - return nil - } -} - -// WithNewSnapshotView allocates a new snapshot to be used by the container as the -// root filesystem in read-only mode -func WithNewSnapshotView(id string, i Image) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) - if err != nil { - return err - } - if _, err := client.SnapshotService(c.Snapshotter).View(ctx, id, identity.ChainID(diffIDs).String()); err != nil { - return err - } - c.RootFS = id - c.Image = i.Name() - return nil - } -} - -// WithRuntime allows a user to specify the runtime name and additional options that should -// be used to create tasks for the container -func WithRuntime(name string) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - c.Runtime = containers.RuntimeInfo{ - Name: name, - } - return nil - } -} - -// WithSnapshotter sets the provided snapshotter for use by the container -func WithSnapshotter(name string) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - c.Snapshotter = name - return nil - } -} - -// WithImage sets the provided image as the base for the container -func WithImage(i Image) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - c.Image = i.Name() - return nil - } -} - // NewContainer will create a new container in container with the provided id // the id must be unique within the namespace func (c *Client) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) { @@ -268,9 +156,6 @@ func (c *Client) LoadContainer(ctx context.Context, id string) (Container, error return containerFromRecord(c, r), nil } -// RemoteOpts allows the caller to set distribution options for a remote -type RemoteOpts func(*Client, *RemoteContext) error - // RemoteContext is used to configure object resolutions and transfers with // remote content stores and image providers. type RemoteContext struct { @@ -305,46 +190,6 @@ func defaultRemoteContext() *RemoteContext { } } -// WithPullUnpack is used to unpack an image after pull. This -// uses the snapshotter, content store, and diff service -// configured for the client. -func WithPullUnpack(client *Client, c *RemoteContext) error { - c.Unpack = true - return nil -} - -// WithPullSnapshotter specifies snapshotter name used for unpacking -func WithPullSnapshotter(snapshotterName string) RemoteOpts { - return func(client *Client, c *RemoteContext) error { - c.Snapshotter = snapshotterName - return nil - } -} - -// WithSchema1Conversion is used to convert Docker registry schema 1 -// manifests to oci manifests on pull. Without this option schema 1 -// manifests will return a not supported error. -func WithSchema1Conversion(client *Client, c *RemoteContext) error { - c.ConvertSchema1 = true - return nil -} - -// WithResolver specifies the resolver to use. -func WithResolver(resolver remotes.Resolver) RemoteOpts { - return func(client *Client, c *RemoteContext) error { - c.Resolver = resolver - return nil - } -} - -// WithImageHandler adds a base handler to be called on dispatch. -func WithImageHandler(h images.Handler) RemoteOpts { - return func(client *Client, c *RemoteContext) error { - c.BaseHandlers = append(c.BaseHandlers, h) - return nil - } -} - // Pull downloads the provided content into containerd's content store func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpts) (Image, error) { pullCtx := defaultRemoteContext() diff --git a/client_opts.go b/client_opts.go new file mode 100644 index 000000000..83d5288c4 --- /dev/null +++ b/client_opts.go @@ -0,0 +1,77 @@ +package containerd + +import ( + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/remotes" + "google.golang.org/grpc" +) + +type clientOpts struct { + defaultns string + dialOptions []grpc.DialOption +} + +// ClientOpt allows callers to set options on the containerd client +type ClientOpt func(c *clientOpts) error + +// WithDefaultNamespace sets the default namespace on the client +// +// Any operation that does not have a namespace set on the context will +// be provided the default namespace +func WithDefaultNamespace(ns string) ClientOpt { + return func(c *clientOpts) error { + c.defaultns = ns + return nil + } +} + +// WithDialOpts allows grpc.DialOptions to be set on the connection +func WithDialOpts(opts []grpc.DialOption) ClientOpt { + return func(c *clientOpts) error { + c.dialOptions = opts + return nil + } +} + +// RemoteOpts allows the caller to set distribution options for a remote +type RemoteOpts func(*Client, *RemoteContext) error + +// WithPullUnpack is used to unpack an image after pull. This +// uses the snapshotter, content store, and diff service +// configured for the client. +func WithPullUnpack(client *Client, c *RemoteContext) error { + c.Unpack = true + return nil +} + +// WithPullSnapshotter specifies snapshotter name used for unpacking +func WithPullSnapshotter(snapshotterName string) RemoteOpts { + return func(client *Client, c *RemoteContext) error { + c.Snapshotter = snapshotterName + return nil + } +} + +// WithSchema1Conversion is used to convert Docker registry schema 1 +// manifests to oci manifests on pull. Without this option schema 1 +// manifests will return a not supported error. +func WithSchema1Conversion(client *Client, c *RemoteContext) error { + c.ConvertSchema1 = true + return nil +} + +// WithResolver specifies the resolver to use. +func WithResolver(resolver remotes.Resolver) RemoteOpts { + return func(client *Client, c *RemoteContext) error { + c.Resolver = resolver + return nil + } +} + +// WithImageHandler adds a base handler to be called on dispatch. +func WithImageHandler(h images.Handler) RemoteOpts { + return func(client *Client, c *RemoteContext) error { + c.BaseHandlers = append(c.BaseHandlers, h) + return nil + } +} diff --git a/container.go b/container.go index 38f88d7dc..6df235d6e 100644 --- a/container.go +++ b/container.go @@ -11,7 +11,6 @@ import ( "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/mount" "github.com/containerd/containerd/typeurl" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -122,14 +121,6 @@ func (c *container) Spec() (*specs.Spec, error) { return &s, nil } -// WithSnapshotCleanup deletes the rootfs allocated for the container -func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Container) error { - if c.RootFS != "" { - return client.SnapshotService(c.Snapshotter).Remove(ctx, c.RootFS) - } - return nil -} - // Delete deletes an existing container // an error is returned if the container has running tasks func (c *container) Delete(ctx context.Context, opts ...DeleteOpts) (err error) { @@ -167,17 +158,6 @@ func (c *container) Image(ctx context.Context) (Image, error) { }, nil } -// NewTaskOpts allows the caller to set options on a new task -type NewTaskOpts func(context.Context, *Client, *TaskInfo) error - -// WithRootFS allows a task to be created without a snapshot being allocated to its container -func WithRootFS(mounts []mount.Mount) NewTaskOpts { - return func(ctx context.Context, c *Client, ti *TaskInfo) error { - ti.RootFS = mounts - return nil - } -} - func (c *container) NewTask(ctx context.Context, ioCreate IOCreation, opts ...NewTaskOpts) (Task, error) { c.mu.Lock() defer c.mu.Unlock() diff --git a/container_opts.go b/container_opts.go new file mode 100644 index 000000000..8140c7889 --- /dev/null +++ b/container_opts.go @@ -0,0 +1,100 @@ +package containerd + +import ( + "context" + + "github.com/containerd/containerd/containers" + "github.com/opencontainers/image-spec/identity" +) + +// NewContainerOpts allows the caller to set additional options when creating a container +type NewContainerOpts func(ctx context.Context, client *Client, c *containers.Container) error + +// WithRuntime allows a user to specify the runtime name and additional options that should +// be used to create tasks for the container +func WithRuntime(name string) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + c.Runtime = containers.RuntimeInfo{ + Name: name, + } + return nil + } +} + +// WithImage sets the provided image as the base for the container +func WithImage(i Image) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + c.Image = i.Name() + return nil + } +} + +// WithContainerLabels adds the provided labels to the container +func WithContainerLabels(labels map[string]string) NewContainerOpts { + return func(_ context.Context, _ *Client, c *containers.Container) error { + c.Labels = labels + return nil + } +} + +// WithSnapshotter sets the provided snapshotter for use by the container +func WithSnapshotter(name string) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + c.Snapshotter = name + return nil + } +} + +// WithSnapshot uses an existing root filesystem for the container +func WithSnapshot(id string) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + // check that the snapshot exists, if not, fail on creation + if _, err := client.SnapshotService(c.Snapshotter).Mounts(ctx, id); err != nil { + return err + } + c.RootFS = id + return nil + } +} + +// WithNewSnapshot allocates a new snapshot to be used by the container as the +// root filesystem in read-write mode +func WithNewSnapshot(id string, i Image) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + if err != nil { + return err + } + if _, err := client.SnapshotService(c.Snapshotter).Prepare(ctx, id, identity.ChainID(diffIDs).String()); err != nil { + return err + } + c.RootFS = id + c.Image = i.Name() + return nil + } +} + +// WithSnapshotCleanup deletes the rootfs allocated for the container +func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Container) error { + if c.RootFS != "" { + return client.SnapshotService(c.Snapshotter).Remove(ctx, c.RootFS) + } + return nil +} + +// WithNewSnapshotView allocates a new snapshot to be used by the container as the +// root filesystem in read-only mode +func WithNewSnapshotView(id string, i Image) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + if err != nil { + return err + } + if _, err := client.SnapshotService(c.Snapshotter).View(ctx, id, identity.ChainID(diffIDs).String()); err != nil { + return err + } + c.RootFS = id + c.Image = i.Name() + return nil + } +} diff --git a/container_unix.go b/container_opts_unix.go similarity index 100% rename from container_unix.go rename to container_opts_unix.go diff --git a/spec.go b/spec.go index 3d52b9cc3..8567ace67 100644 --- a/spec.go +++ b/spec.go @@ -2,17 +2,6 @@ package containerd import specs "github.com/opencontainers/runtime-spec/specs-go" -// SpecOpts sets spec specific information to a newly generated OCI spec -type SpecOpts func(s *specs.Spec) error - -// WithProcessArgs replaces the args on the generated spec -func WithProcessArgs(args ...string) SpecOpts { - return func(s *specs.Spec) error { - s.Process.Args = args - return nil - } -} - // GenerateSpec will generate a default spec from the provided image // for use as a containerd container func GenerateSpec(opts ...SpecOpts) (*specs.Spec, error) { diff --git a/spec_opts.go b/spec_opts.go new file mode 100644 index 000000000..8a22ed1af --- /dev/null +++ b/spec_opts.go @@ -0,0 +1,14 @@ +package containerd + +import specs "github.com/opencontainers/runtime-spec/specs-go" + +// SpecOpts sets spec specific information to a newly generated OCI spec +type SpecOpts func(s *specs.Spec) error + +// WithProcessArgs replaces the args on the generated spec +func WithProcessArgs(args ...string) SpecOpts { + return func(s *specs.Spec) error { + s.Process.Args = args + return nil + } +} diff --git a/spec_opts_unix.go b/spec_opts_unix.go new file mode 100644 index 000000000..7ee366718 --- /dev/null +++ b/spec_opts_unix.go @@ -0,0 +1,265 @@ +// +build !windows + +package containerd + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/typeurl" + "github.com/opencontainers/image-spec/identity" + "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +// WithTTY sets the information on the spec as well as the environment variables for +// using a TTY +func WithTTY(s *specs.Spec) error { + s.Process.Terminal = true + s.Process.Env = append(s.Process.Env, "TERM=xterm") + return nil +} + +// WithHostNamespace allows a task to run inside the host's linux namespace +func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts { + return func(s *specs.Spec) error { + for i, n := range s.Linux.Namespaces { + if n.Type == ns { + s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...) + return nil + } + } + return nil + } +} + +// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the +// spec, the existing namespace is replaced by the one provided. +func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts { + return func(s *specs.Spec) error { + for i, n := range s.Linux.Namespaces { + if n.Type == ns.Type { + before := s.Linux.Namespaces[:i] + after := s.Linux.Namespaces[i+1:] + s.Linux.Namespaces = append(before, ns) + s.Linux.Namespaces = append(s.Linux.Namespaces, after...) + return nil + } + } + s.Linux.Namespaces = append(s.Linux.Namespaces, ns) + return nil + } +} + +// WithImageConfig configures the spec to from the configuration of an Image +func WithImageConfig(ctx context.Context, i Image) SpecOpts { + return func(s *specs.Spec) error { + var ( + image = i.(*image) + store = image.client.ContentStore() + ) + ic, err := image.i.Config(ctx, store) + if err != nil { + return err + } + var ( + ociimage v1.Image + config v1.ImageConfig + ) + switch ic.MediaType { + case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: + r, err := store.Reader(ctx, ic.Digest) + if err != nil { + return err + } + if err := json.NewDecoder(r).Decode(&ociimage); err != nil { + r.Close() + return err + } + r.Close() + config = ociimage.Config + default: + return fmt.Errorf("unknown image config media type %s", ic.MediaType) + } + s.Process.Env = append(s.Process.Env, config.Env...) + var ( + uid, gid uint32 + ) + cmd := config.Cmd + s.Process.Args = append(config.Entrypoint, cmd...) + if config.User != "" { + parts := strings.Split(config.User, ":") + switch len(parts) { + case 1: + v, err := strconv.ParseUint(parts[0], 0, 10) + if err != nil { + return err + } + uid, gid = uint32(v), uint32(v) + case 2: + v, err := strconv.ParseUint(parts[0], 0, 10) + if err != nil { + return err + } + uid = uint32(v) + if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil { + return err + } + gid = uint32(v) + default: + return fmt.Errorf("invalid USER value %s", config.User) + } + } + s.Process.User.UID, s.Process.User.GID = uid, gid + cwd := config.WorkingDir + if cwd == "" { + cwd = "/" + } + s.Process.Cwd = cwd + return nil + } +} + +// WithRootFSPath specifies unmanaged rootfs path. +func WithRootFSPath(path string, readonly bool) SpecOpts { + return func(s *specs.Spec) error { + s.Root = &specs.Root{ + Path: path, + Readonly: readonly, + } + // Entrypoint is not set here (it's up to caller) + return nil + } +} + +// WithSpec sets the provided spec for a new container +func WithSpec(spec *specs.Spec) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + any, err := typeurl.MarshalAny(spec) + if err != nil { + return err + } + c.Spec = any + return nil + } +} + +// WithResources sets the provided resources on the spec for task updates +func WithResources(resources *specs.LinuxResources) UpdateTaskOpts { + return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { + r.Resources = resources + return nil + } +} + +// WithNoNewPrivileges sets no_new_privileges on the process for the container +func WithNoNewPrivileges(s *specs.Spec) error { + s.Process.NoNewPrivileges = true + return nil +} + +// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly +func WithHostHostsFile(s *specs.Spec) error { + s.Mounts = append(s.Mounts, specs.Mount{ + Destination: "/etc/hosts", + Type: "bind", + Source: "/etc/hosts", + Options: []string{"rbind", "ro"}, + }) + return nil +} + +// WithHostResoveconf bind-mounts the host's /etc/resolv.conf into the container as readonly +func WithHostResoveconf(s *specs.Spec) error { + s.Mounts = append(s.Mounts, specs.Mount{ + Destination: "/etc/resolv.conf", + Type: "bind", + Source: "/etc/resolv.conf", + Options: []string{"rbind", "ro"}, + }) + return nil +} + +// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly +func WithHostLocaltime(s *specs.Spec) error { + s.Mounts = append(s.Mounts, specs.Mount{ + Destination: "/etc/localtime", + Type: "bind", + Source: "/etc/localtime", + Options: []string{"rbind", "ro"}, + }) + return nil +} + +// WithUserNamespace sets the uid and gid mappings for the task +// this can be called multiple times to add more mappings to the generated spec +func WithUserNamespace(container, host, size uint32) SpecOpts { + return func(s *specs.Spec) error { + var hasUserns bool + for _, ns := range s.Linux.Namespaces { + if ns.Type == specs.UserNamespace { + hasUserns = true + break + } + } + if !hasUserns { + s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{ + Type: specs.UserNamespace, + }) + } + mapping := specs.LinuxIDMapping{ + ContainerID: container, + HostID: host, + Size: size, + } + s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping) + s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping) + return nil + } +} + +// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the +// filesystem to be used by a container with user namespaces +func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + if err != nil { + return err + } + var ( + snapshotter = client.SnapshotService(c.Snapshotter) + parent = identity.ChainID(diffIDs).String() + usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid) + ) + if _, err := snapshotter.Stat(ctx, usernsID); err == nil { + if _, err := snapshotter.Prepare(ctx, id, usernsID); err != nil { + return err + } + c.RootFS = id + c.Image = i.Name() + return nil + } + mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent) + if err != nil { + return err + } + if err := remapRootFS(mounts, uid, gid); err != nil { + snapshotter.Remove(ctx, usernsID) + return err + } + if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil { + return err + } + if _, err := snapshotter.Prepare(ctx, id, usernsID); err != nil { + return err + } + c.RootFS = id + c.Image = i.Name() + return nil + } +} diff --git a/spec_opts_windows.go b/spec_opts_windows.go new file mode 100644 index 000000000..861bdb1d8 --- /dev/null +++ b/spec_opts_windows.go @@ -0,0 +1,80 @@ +// +build windows + +package containerd + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/typeurl" + "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +func WithImageConfig(ctx context.Context, i Image) SpecOpts { + return func(s *specs.Spec) error { + var ( + image = i.(*image) + store = image.client.ContentStore() + ) + ic, err := image.i.Config(ctx, store) + if err != nil { + return err + } + var ( + ociimage v1.Image + config v1.ImageConfig + ) + switch ic.MediaType { + case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: + r, err := store.Reader(ctx, ic.Digest) + if err != nil { + return err + } + if err := json.NewDecoder(r).Decode(&ociimage); err != nil { + r.Close() + return err + } + r.Close() + config = ociimage.Config + default: + return fmt.Errorf("unknown image config media type %s", ic.MediaType) + } + s.Process.Env = config.Env + s.Process.Args = append(config.Entrypoint, config.Cmd...) + s.Process.User = specs.User{ + Username: config.User, + } + return nil + } +} + +func WithTTY(width, height int) SpecOpts { + return func(s *specs.Spec) error { + s.Process.Terminal = true + s.Process.ConsoleSize.Width = uint(width) + s.Process.ConsoleSize.Height = uint(height) + return nil + } +} + +func WithSpec(spec *specs.Spec) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + any, err := typeurl.MarshalAny(spec) + if err != nil { + return err + } + c.Spec = any + return nil + } +} + +func WithResources(resources *specs.WindowsResources) UpdateTaskOpts { + return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { + r.Resources = resources + return nil + } +} diff --git a/spec_unix.go b/spec_unix.go index e57ec18fa..79d3fc9fe 100644 --- a/spec_unix.go +++ b/spec_unix.go @@ -3,25 +3,16 @@ package containerd import ( - "context" - "encoding/json" - "fmt" "io/ioutil" "os" "path/filepath" - "strconv" "strings" "syscall" "golang.org/x/sys/unix" - "github.com/containerd/containerd/containers" "github.com/containerd/containerd/fs" - "github.com/containerd/containerd/images" "github.com/containerd/containerd/mount" - "github.com/containerd/containerd/typeurl" - "github.com/opencontainers/image-spec/identity" - "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/runtime-spec/specs-go" ) @@ -162,253 +153,6 @@ func createDefaultSpec() (*specs.Spec, error) { return s, nil } -// WithTTY sets the information on the spec as well as the environment variables for -// using a TTY -func WithTTY(s *specs.Spec) error { - s.Process.Terminal = true - s.Process.Env = append(s.Process.Env, "TERM=xterm") - return nil -} - -// WithHostNamespace allows a task to run inside the host's linux namespace -func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts { - return func(s *specs.Spec) error { - for i, n := range s.Linux.Namespaces { - if n.Type == ns { - s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...) - return nil - } - } - return nil - } -} - -// WithLinuxNamespace uses the passed in namespace for the spec. If a namespace of the same type already exists in the -// spec, the existing namespace is replaced by the one provided. -func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts { - return func(s *specs.Spec) error { - for i, n := range s.Linux.Namespaces { - if n.Type == ns.Type { - before := s.Linux.Namespaces[:i] - after := s.Linux.Namespaces[i+1:] - s.Linux.Namespaces = append(before, ns) - s.Linux.Namespaces = append(s.Linux.Namespaces, after...) - return nil - } - } - s.Linux.Namespaces = append(s.Linux.Namespaces, ns) - return nil - } -} - -// WithImageConfig configures the spec to from the configuration of an Image -func WithImageConfig(ctx context.Context, i Image) SpecOpts { - return func(s *specs.Spec) error { - var ( - image = i.(*image) - store = image.client.ContentStore() - ) - ic, err := image.i.Config(ctx, store) - if err != nil { - return err - } - var ( - ociimage v1.Image - config v1.ImageConfig - ) - switch ic.MediaType { - case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: - r, err := store.Reader(ctx, ic.Digest) - if err != nil { - return err - } - if err := json.NewDecoder(r).Decode(&ociimage); err != nil { - r.Close() - return err - } - r.Close() - config = ociimage.Config - default: - return fmt.Errorf("unknown image config media type %s", ic.MediaType) - } - s.Process.Env = append(s.Process.Env, config.Env...) - var ( - uid, gid uint32 - ) - cmd := config.Cmd - s.Process.Args = append(config.Entrypoint, cmd...) - if config.User != "" { - parts := strings.Split(config.User, ":") - switch len(parts) { - case 1: - v, err := strconv.ParseUint(parts[0], 0, 10) - if err != nil { - return err - } - uid, gid = uint32(v), uint32(v) - case 2: - v, err := strconv.ParseUint(parts[0], 0, 10) - if err != nil { - return err - } - uid = uint32(v) - if v, err = strconv.ParseUint(parts[1], 0, 10); err != nil { - return err - } - gid = uint32(v) - default: - return fmt.Errorf("invalid USER value %s", config.User) - } - } - s.Process.User.UID, s.Process.User.GID = uid, gid - cwd := config.WorkingDir - if cwd == "" { - cwd = "/" - } - s.Process.Cwd = cwd - return nil - } -} - -// WithRootFSPath specifies unmanaged rootfs path. -func WithRootFSPath(path string, readonly bool) SpecOpts { - return func(s *specs.Spec) error { - s.Root = &specs.Root{ - Path: path, - Readonly: readonly, - } - // Entrypoint is not set here (it's up to caller) - return nil - } -} - -// WithSpec sets the provided spec for a new container -func WithSpec(spec *specs.Spec) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - any, err := typeurl.MarshalAny(spec) - if err != nil { - return err - } - c.Spec = any - return nil - } -} - -// WithResources sets the provided resources on the spec for task updates -func WithResources(resources *specs.LinuxResources) UpdateTaskOpts { - return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { - r.Resources = resources - return nil - } -} - -// WithNoNewPrivileges sets no_new_privileges on the process for the container -func WithNoNewPrivileges(s *specs.Spec) error { - s.Process.NoNewPrivileges = true - return nil -} - -// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly -func WithHostHostsFile(s *specs.Spec) error { - s.Mounts = append(s.Mounts, specs.Mount{ - Destination: "/etc/hosts", - Type: "bind", - Source: "/etc/hosts", - Options: []string{"rbind", "ro"}, - }) - return nil -} - -// WithHostResoveconf bind-mounts the host's /etc/resolv.conf into the container as readonly -func WithHostResoveconf(s *specs.Spec) error { - s.Mounts = append(s.Mounts, specs.Mount{ - Destination: "/etc/resolv.conf", - Type: "bind", - Source: "/etc/resolv.conf", - Options: []string{"rbind", "ro"}, - }) - return nil -} - -// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly -func WithHostLocaltime(s *specs.Spec) error { - s.Mounts = append(s.Mounts, specs.Mount{ - Destination: "/etc/localtime", - Type: "bind", - Source: "/etc/localtime", - Options: []string{"rbind", "ro"}, - }) - return nil -} - -// WithUserNamespace sets the uid and gid mappings for the task -// this can be called multiple times to add more mappings to the generated spec -func WithUserNamespace(container, host, size uint32) SpecOpts { - return func(s *specs.Spec) error { - var hasUserns bool - for _, ns := range s.Linux.Namespaces { - if ns.Type == specs.UserNamespace { - hasUserns = true - break - } - } - if !hasUserns { - s.Linux.Namespaces = append(s.Linux.Namespaces, specs.LinuxNamespace{ - Type: specs.UserNamespace, - }) - } - mapping := specs.LinuxIDMapping{ - ContainerID: container, - HostID: host, - Size: size, - } - s.Linux.UIDMappings = append(s.Linux.UIDMappings, mapping) - s.Linux.GIDMappings = append(s.Linux.GIDMappings, mapping) - return nil - } -} - -// WithRemappedSnapshot creates a new snapshot and remaps the uid/gid for the -// filesystem to be used by a container with user namespaces -func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) - if err != nil { - return err - } - var ( - snapshotter = client.SnapshotService(c.Snapshotter) - parent = identity.ChainID(diffIDs).String() - usernsID = fmt.Sprintf("%s-%d-%d", parent, uid, gid) - ) - if _, err := snapshotter.Stat(ctx, usernsID); err == nil { - if _, err := snapshotter.Prepare(ctx, id, usernsID); err != nil { - return err - } - c.RootFS = id - c.Image = i.Name() - return nil - } - mounts, err := snapshotter.Prepare(ctx, usernsID+"-remap", parent) - if err != nil { - return err - } - if err := remapRootFS(mounts, uid, gid); err != nil { - snapshotter.Remove(ctx, usernsID) - return err - } - if err := snapshotter.Commit(ctx, usernsID, usernsID+"-remap"); err != nil { - return err - } - if _, err := snapshotter.Prepare(ctx, id, usernsID); err != nil { - return err - } - c.RootFS = id - c.Image = i.Name() - return nil - } -} - func remapRootFS(mounts []mount.Mount, uid, gid uint32) error { root, err := ioutil.TempDir("", "ctd-remap") if err != nil { diff --git a/spec_windows.go b/spec_windows.go index 7e0f5cfd8..8fa1305c9 100644 --- a/spec_windows.go +++ b/spec_windows.go @@ -1,16 +1,6 @@ package containerd -import ( - "context" - "encoding/json" - "fmt" - - "github.com/containerd/containerd/containers" - "github.com/containerd/containerd/images" - "github.com/containerd/containerd/typeurl" - "github.com/opencontainers/image-spec/specs-go/v1" - specs "github.com/opencontainers/runtime-spec/specs-go" -) +import specs "github.com/opencontainers/runtime-spec/specs-go" func createDefaultSpec() (*specs.Spec, error) { return &specs.Spec{ @@ -31,68 +21,3 @@ func createDefaultSpec() (*specs.Spec, error) { }, }, nil } - -func WithImageConfig(ctx context.Context, i Image) SpecOpts { - return func(s *specs.Spec) error { - var ( - image = i.(*image) - store = image.client.ContentStore() - ) - ic, err := image.i.Config(ctx, store) - if err != nil { - return err - } - var ( - ociimage v1.Image - config v1.ImageConfig - ) - switch ic.MediaType { - case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: - r, err := store.Reader(ctx, ic.Digest) - if err != nil { - return err - } - if err := json.NewDecoder(r).Decode(&ociimage); err != nil { - r.Close() - return err - } - r.Close() - config = ociimage.Config - default: - return fmt.Errorf("unknown image config media type %s", ic.MediaType) - } - s.Process.Env = config.Env - s.Process.Args = append(config.Entrypoint, config.Cmd...) - s.Process.User = specs.User{ - Username: config.User, - } - return nil - } -} - -func WithTTY(width, height int) SpecOpts { - return func(s *specs.Spec) error { - s.Process.Terminal = true - s.Process.ConsoleSize.Width = uint(width) - s.Process.ConsoleSize.Height = uint(height) - return nil - } -} - -func WithSpec(spec *specs.Spec) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - any, err := typeurl.MarshalAny(spec) - if err != nil { - return err - } - c.Spec = any - return nil - } -} - -func WithResources(resources *specs.WindowsResources) UpdateTaskOpts { - return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { - r.Resources = resources - return nil - } -} diff --git a/task.go b/task.go index 88a362fb5..130d79513 100644 --- a/task.go +++ b/task.go @@ -16,7 +16,6 @@ import ( "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" - "github.com/containerd/containerd/linux/runcopts" "github.com/containerd/containerd/mount" "github.com/containerd/containerd/rootfs" "github.com/containerd/containerd/runtime" @@ -467,11 +466,3 @@ func writeContent(ctx context.Context, store content.Store, mediaType, ref strin Size: size, }, nil } - -// WithExit causes the task to exit after a successful checkpoint -func WithExit(r *CheckpointTaskInfo) error { - r.Options = &runcopts.CheckpointOptions{ - Exit: true, - } - return nil -} diff --git a/task_opts.go b/task_opts.go new file mode 100644 index 000000000..40b178c3c --- /dev/null +++ b/task_opts.go @@ -0,0 +1,27 @@ +package containerd + +import ( + "context" + + "github.com/containerd/containerd/linux/runcopts" + "github.com/containerd/containerd/mount" +) + +// NewTaskOpts allows the caller to set options on a new task +type NewTaskOpts func(context.Context, *Client, *TaskInfo) error + +// WithRootFS allows a task to be created without a snapshot being allocated to its container +func WithRootFS(mounts []mount.Mount) NewTaskOpts { + return func(ctx context.Context, c *Client, ti *TaskInfo) error { + ti.RootFS = mounts + return nil + } +} + +// WithExit causes the task to exit after a successful checkpoint +func WithExit(r *CheckpointTaskInfo) error { + r.Options = &runcopts.CheckpointOptions{ + Exit: true, + } + return nil +}