diff --git a/README.md b/README.md index 5d4c507fc..6e59def11 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ containerd fully supports the OCI runtime specification for running containers. You can specify options when creating a container about how to modify the specification. ```go -redis, err := client.NewContainer(context, "redis-master", containerd.WithNewSpec(containerd.WithImageConfig(image))) +redis, err := client.NewContainer(context, "redis-master", containerd.WithNewSpec(oci.WithImageConfig(image))) ``` ### Root Filesystems @@ -92,7 +92,7 @@ image, err := client.Pull(context, "docker.io/library/redis:latest", containerd. // allocate a new RW root filesystem for a container based on the image redis, err := client.NewContainer(context, "redis-master", containerd.WithNewSnapshot("redis-rootfs", image), - containerd.WithNewSpec(containerd.WithImageConfig(image)), + containerd.WithNewSpec(oci.WithImageConfig(image)), ) @@ -101,7 +101,7 @@ for i := 0; i < 10; i++ { id := fmt.Sprintf("id-%s", i) container, err := client.NewContainer(ctx, id, containerd.WithNewSnapshotView(id, image), - containerd.WithNewSpec(containerd.WithImageConfig(image)), + containerd.WithNewSpec(oci.WithImageConfig(image)), ) } ``` diff --git a/benchmark_test.go b/benchmark_test.go index e2d81ed8a..174465982 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" ) func BenchmarkContainerCreate(b *testing.B) { @@ -22,7 +23,7 @@ func BenchmarkContainerCreate(b *testing.B) { b.Error(err) return } - spec, err := GenerateSpec(ctx, client, &containers.Container{ID: b.Name()}, WithImageConfig(image), withTrue()) + spec, err := oci.GenerateSpec(ctx, client, &containers.Container{ID: b.Name()}, oci.WithImageConfig(image), withTrue()) if err != nil { b.Error(err) return @@ -65,7 +66,7 @@ func BenchmarkContainerStart(b *testing.B) { b.Error(err) return } - spec, err := GenerateSpec(ctx, client, &containers.Container{ID: b.Name()}, WithImageConfig(image), withTrue()) + spec, err := oci.GenerateSpec(ctx, client, &containers.Container{ID: b.Name()}, oci.WithImageConfig(image), withTrue()) if err != nil { b.Error(err) return diff --git a/cmd/containerd-stress/main.go b/cmd/containerd-stress/main.go index 05ed47391..05864a424 100644 --- a/cmd/containerd-stress/main.go +++ b/cmd/containerd-stress/main.go @@ -16,6 +16,7 @@ import ( "github.com/containerd/containerd/cio" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" "github.com/urfave/cli" @@ -116,7 +117,10 @@ func test(c config) error { logrus.Info("starting stress test run...") for i := 0; i < c.Concurrency; i++ { wg.Add(1) - spec, err := containerd.GenerateSpec(ctx, client, &containers.Container{ID: ""}, containerd.WithImageConfig(image), containerd.WithProcessArgs("true")) + spec, err := oci.GenerateSpec(ctx, client, + &containers.Container{}, + oci.WithImageConfig(image), + oci.WithProcessArgs("true")) if err != nil { return err } diff --git a/cmd/ctr/commands/run/run.go b/cmd/ctr/commands/run/run.go index 184f6540a..fdfc895ec 100644 --- a/cmd/ctr/commands/run/run.go +++ b/cmd/ctr/commands/run/run.go @@ -12,14 +12,15 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/cmd/ctr/commands/tasks" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) -func withEnv(context *cli.Context) containerd.SpecOpts { - return func(_ gocontext.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error { +func withEnv(context *cli.Context) oci.SpecOpts { + return func(_ gocontext.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { env := context.StringSlice("env") if len(env) > 0 { s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, env) @@ -28,8 +29,8 @@ func withEnv(context *cli.Context) containerd.SpecOpts { } } -func withMounts(context *cli.Context) containerd.SpecOpts { - return func(_ gocontext.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error { +func withMounts(context *cli.Context) oci.SpecOpts { + return func(_ gocontext.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { for _, mount := range context.StringSlice("mount") { m, err := parseMountFlag(mount) if err != nil { diff --git a/cmd/ctr/commands/run/run_unix.go b/cmd/ctr/commands/run/run_unix.go index ee518abd5..3facf7ad5 100644 --- a/cmd/ctr/commands/run/run_unix.go +++ b/cmd/ctr/commands/run/run_unix.go @@ -7,6 +7,7 @@ import ( "github.com/containerd/containerd" "github.com/containerd/containerd/cmd/ctr/commands" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/urfave/cli" ) @@ -18,14 +19,6 @@ func init() { }) } -func withTTY() containerd.SpecOpts { - return containerd.WithTTY -} - -func setHostNetworking() containerd.SpecOpts { - return containerd.WithHostNamespace(specs.NetworkNamespace) -} - func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) { var ( ref = context.Args().First() @@ -42,18 +35,18 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli } var ( - opts []containerd.SpecOpts + opts []oci.SpecOpts cOpts []containerd.NewContainerOpts ) cOpts = append(cOpts, containerd.WithContainerLabels(commands.LabelArgs(context.StringSlice("label")))) if context.Bool("rootfs") { - opts = append(opts, containerd.WithRootFSPath(ref)) + opts = append(opts, oci.WithRootFSPath(ref)) } else { image, err := client.GetImage(ctx, ref) if err != nil { return nil, err } - opts = append(opts, containerd.WithImageConfig(image)) + opts = append(opts, oci.WithImageConfig(image)) cOpts = append(cOpts, containerd.WithImage(image)) cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter"))) // Even when "readonly" is set, we don't use KindView snapshot here. (#1495) @@ -62,22 +55,22 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli cOpts = append(cOpts, containerd.WithNewSnapshot(id, image)) } if context.Bool("readonly") { - opts = append(opts, containerd.WithRootFSReadonly()) + opts = append(opts, oci.WithRootFSReadonly()) } cOpts = append(cOpts, containerd.WithRuntime(context.String("runtime"), nil)) opts = append(opts, withEnv(context), withMounts(context)) if len(args) > 0 { - opts = append(opts, containerd.WithProcessArgs(args...)) + opts = append(opts, oci.WithProcessArgs(args...)) } if cwd := context.String("cwd"); cwd != "" { - opts = append(opts, containerd.WithProcessCwd(cwd)) + opts = append(opts, oci.WithProcessCwd(cwd)) } if context.Bool("tty") { - opts = append(opts, withTTY()) + opts = append(opts, oci.WithTTY) } if context.Bool("net-host") { - opts = append(opts, setHostNetworking(), containerd.WithHostHostsFile, containerd.WithHostResolvconf) + opts = append(opts, oci.WithHostNamespace(specs.NetworkNamespace), oci.WithHostHostsFile, oci.WithHostResolvconf) } cOpts = append([]containerd.NewContainerOpts{containerd.WithNewSpec(opts...)}, cOpts...) return client.NewContainer(ctx, id, cOpts...) diff --git a/cmd/ctr/commands/run/run_windows.go b/cmd/ctr/commands/run/run_windows.go index be7e3f6ad..a85d16a4f 100644 --- a/cmd/ctr/commands/run/run_windows.go +++ b/cmd/ctr/commands/run/run_windows.go @@ -8,6 +8,7 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -21,8 +22,8 @@ func init() { }) } -func withLayers(context *cli.Context) containerd.SpecOpts { - return func(ctx gocontext.Context, client *containerd.Client, c *containers.Container, s *specs.Spec) error { +func withLayers(context *cli.Context) oci.SpecOpts { + return func(ctx gocontext.Context, client oci.Client, c *containers.Container, s *specs.Spec) error { l := context.StringSlice("layer") if l == nil { return errors.Wrap(errdefs.ErrInvalidArgument, "base layers must be specified with `--layer`") @@ -32,9 +33,9 @@ func withLayers(context *cli.Context) containerd.SpecOpts { } } -func withTTY(terminal bool) containerd.SpecOpts { +func withTTY(terminal bool) oci.SpecOpts { if !terminal { - return func(ctx gocontext.Context, client *containerd.Client, c *containers.Container, s *specs.Spec) error { + return func(ctx gocontext.Context, client oci.Client, c *containers.Container, s *specs.Spec) error { s.Process.Terminal = false return nil } @@ -45,7 +46,7 @@ func withTTY(terminal bool) containerd.SpecOpts { if err != nil { logrus.WithError(err).Error("console size") } - return containerd.WithTTY(int(size.Width), int(size.Height)) + return oci.WithTTY(int(size.Width), int(size.Height)) } func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) { @@ -61,18 +62,18 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli // TODO(mlaventure): get base image once we have a snapshotter - opts := []containerd.SpecOpts{ - // TODO(mlaventure): use containerd.WithImageConfig once we have a snapshotter + opts := []oci.SpecOpts{ + // TODO(mlaventure): use oci.WithImageConfig once we have a snapshotter withLayers(context), withEnv(context), withMounts(context), withTTY(tty), } if len(args) > 0 { - opts = append(opts, containerd.WithProcessArgs(args...)) + opts = append(opts, oci.WithProcessArgs(args...)) } if cwd := context.String("cwd"); cwd != "" { - opts = append(opts, containerd.WithProcessCwd(cwd)) + opts = append(opts, oci.WithProcessCwd(cwd)) } return client.NewContainer(ctx, id, containerd.WithNewSpec(opts...), diff --git a/container_checkpoint_test.go b/container_checkpoint_test.go index d7636b2fb..6144ca075 100644 --- a/container_checkpoint_test.go +++ b/container_checkpoint_test.go @@ -5,6 +5,8 @@ package containerd import ( "syscall" "testing" + + "github.com/containerd/containerd/oci" ) func TestCheckpointRestore(t *testing.T) { @@ -28,7 +30,7 @@ func TestCheckpointRestore(t *testing.T) { t.Error(err) return } - container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Error(err) return @@ -108,7 +110,7 @@ func TestCheckpointRestoreNewContainer(t *testing.T) { t.Error(err) return } - container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Error(err) return @@ -201,7 +203,7 @@ func TestCheckpointLeaveRunning(t *testing.T) { t.Error(err) return } - container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image)) if err != nil { t.Error(err) return diff --git a/container_linux_test.go b/container_linux_test.go index cbdebab99..85e934f52 100644 --- a/container_linux_test.go +++ b/container_linux_test.go @@ -20,6 +20,7 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/linux/runctypes" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "golang.org/x/sys/unix" @@ -46,13 +47,15 @@ func TestTaskUpdate(t *testing.T) { return } limit := int64(32 * 1024 * 1024) - memory := func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + memory := func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { s.Linux.Resources.Memory = &specs.LinuxMemory{ Limit: &limit, } return nil } - container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), withProcessArgs("sleep", "30"), memory), WithNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, + WithNewSpec(oci.WithImageConfig(image), withProcessArgs("sleep", "30"), memory), + WithNewSnapshot(id, image)) if err != nil { t.Error(err) return @@ -131,7 +134,7 @@ func TestShimInCgroup(t *testing.T) { t.Error(err) return } - container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "30")), WithNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "30")), WithNewSnapshot(id, image)) if err != nil { t.Error(err) return @@ -409,7 +412,7 @@ func TestContainerUsername(t *testing.T) { // squid user in the alpine image has a uid of 31 container, err := client.NewContainer(ctx, id, withNewSnapshot(id, image), - WithNewSpec(withImageConfig(image), WithUsername("squid"), WithProcessArgs("id", "-u")), + WithNewSpec(withImageConfig(image), oci.WithUsername("squid"), oci.WithProcessArgs("id", "-u")), ) if err != nil { t.Error(err) @@ -618,7 +621,7 @@ func TestContainerUserID(t *testing.T) { // adm user in the alpine image has a uid of 3 and gid of 4. container, err := client.NewContainer(ctx, id, withNewSnapshot(id, image), - WithNewSpec(withImageConfig(image), WithUserID(3), WithProcessArgs("sh", "-c", "echo $(id -u):$(id -g)")), + WithNewSpec(withImageConfig(image), oci.WithUserID(3), oci.WithProcessArgs("sh", "-c", "echo $(id -u):$(id -g)")), ) if err != nil { t.Error(err) @@ -679,7 +682,7 @@ func TestContainerKillAll(t *testing.T) { withNewSnapshot(id, image), WithNewSpec(withImageConfig(image), withProcessArgs("sh", "-c", "top"), - WithHostNamespace(specs.PIDNamespace), + oci.WithHostNamespace(specs.PIDNamespace), ), ) if err != nil { @@ -737,7 +740,7 @@ func TestShimSigkilled(t *testing.T) { if err != nil { t.Fatal(err) } - container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image)), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image)), withNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -801,7 +804,7 @@ func TestDaemonRestartWithRunningShim(t *testing.T) { t.Error(err) return } - container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "100")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, WithNewSpec(oci.WithImageConfig(image), oci.WithProcessArgs("sleep", "100")), withNewSnapshot(id, image)) if err != nil { t.Fatal(err) } @@ -931,7 +934,7 @@ func TestContainerKillInitPidHost(t *testing.T) { withNewSnapshot(id, image), WithNewSpec(withImageConfig(image), withProcessArgs("sh", "-c", "sleep 42; echo hi"), - WithHostNamespace(specs.PIDNamespace), + oci.WithHostNamespace(specs.PIDNamespace), ), ) if err != nil { @@ -1025,7 +1028,7 @@ func testUserNamespaces(t *testing.T, readonlyRootFS bool) { opts := []NewContainerOpts{WithNewSpec(withImageConfig(image), withExitStatus(7), - WithUserNamespace(0, 1000, 10000), + oci.WithUserNamespace(0, 1000, 10000), )} if readonlyRootFS { opts = append(opts, WithRemappedSnapshotView(id, image, 1000, 1000)) diff --git a/container_opts.go b/container_opts.go index 4c534fe12..fb22a9096 100644 --- a/container_opts.go +++ b/container_opts.go @@ -5,10 +5,12 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/oci" "github.com/containerd/containerd/platforms" "github.com/containerd/typeurl" "github.com/gogo/protobuf/types" "github.com/opencontainers/image-spec/identity" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) @@ -164,3 +166,29 @@ func WithContainerExtension(name string, extension interface{}) NewContainerOpts return nil } } + +// WithNewSpec generates a new spec for a new container +func WithNewSpec(opts ...oci.SpecOpts) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + s, err := oci.GenerateSpec(ctx, client, c, opts...) + if err != nil { + return err + } + c.Spec, err = typeurl.MarshalAny(s) + return err + } +} + +// WithSpec sets the provided spec on the container +func WithSpec(s *specs.Spec, opts ...oci.SpecOpts) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + for _, o := range opts { + if err := o(ctx, client, c, s); err != nil { + return err + } + } + var err error + c.Spec, err = typeurl.MarshalAny(s) + return err + } +} diff --git a/container_opts_unix.go b/container_opts_unix.go index e2cd7c939..bb431e51f 100644 --- a/container_opts_unix.go +++ b/container_opts_unix.go @@ -6,12 +6,17 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" + "os" + "path/filepath" + "syscall" "github.com/containerd/containerd/api/types" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/mount" "github.com/containerd/containerd/platforms" "github.com/gogo/protobuf/proto" protobuf "github.com/gogo/protobuf/types" @@ -19,6 +24,7 @@ import ( "github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" + "golang.org/x/sys/unix" ) // WithCheckpoint allows a container to be created from the checkpointed information @@ -122,3 +128,91 @@ func decodeIndex(ctx context.Context, store content.Store, id digest.Digest) (*v return &index, 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 withRemappedSnapshotBase(id, i, uid, gid, false) +} + +// WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only. +func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerOpts { + return withRemappedSnapshotBase(id, i, uid, gid, true) +} + +func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts { + return func(ctx context.Context, client *Client, c *containers.Container) error { + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default()) + if err != nil { + return err + } + + setSnapshotterIfEmpty(c) + + 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 { + c.SnapshotKey = id + c.Image = i.Name() + return nil + } else if !errdefs.IsNotFound(err) { + return err + } + } + 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 readonly { + _, err = snapshotter.View(ctx, id, usernsID) + } else { + _, err = snapshotter.Prepare(ctx, id, usernsID) + } + if err != nil { + return err + } + c.SnapshotKey = 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 { + return err + } + defer os.RemoveAll(root) + for _, m := range mounts { + if err := m.Mount(root); err != nil { + return err + } + } + defer unix.Unmount(root, 0) + return filepath.Walk(root, incrementFS(root, uid, gid)) +} + +func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc { + return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + var ( + stat = info.Sys().(*syscall.Stat_t) + u, g = int(stat.Uid + uidInc), int(stat.Gid + gidInc) + ) + // be sure the lchown the path as to not de-reference the symlink to a host file + return os.Lchown(path, u, g) + } +} diff --git a/container_test.go b/container_test.go index b4ad7315d..167386d35 100644 --- a/container_test.go +++ b/container_test.go @@ -14,6 +14,7 @@ import ( // Register the typeurl "github.com/containerd/containerd/cio" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" _ "github.com/containerd/containerd/runtime" "github.com/containerd/typeurl" @@ -621,7 +622,9 @@ func TestContainerNoBinaryExists(t *testing.T) { } } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), WithProcessArgs("nothing")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, + WithNewSpec(withImageConfig(image), oci.WithProcessArgs("nothing")), + withNewSnapshot(id, image)) if err != nil { t.Error(err) return @@ -1044,7 +1047,7 @@ func TestContainerHostname(t *testing.T) { container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("hostname"), - WithHostname(expected), + oci.WithHostname(expected), ), withNewSnapshot(id, image)) if err != nil { @@ -1265,7 +1268,9 @@ func TestContainerMetrics(t *testing.T) { return } } - container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), WithProcessArgs("sleep", "30")), withNewSnapshot(id, image)) + container, err := client.NewContainer(ctx, id, + WithNewSpec(withImageConfig(image), oci.WithProcessArgs("sleep", "30")), + withNewSnapshot(id, image)) if err != nil { t.Error(err) return diff --git a/contrib/apparmor/apparmor.go b/contrib/apparmor/apparmor.go index 1a8f002ee..33d325e4b 100644 --- a/contrib/apparmor/apparmor.go +++ b/contrib/apparmor/apparmor.go @@ -7,15 +7,15 @@ import ( "io/ioutil" "os" - "github.com/containerd/containerd" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" ) // WithProfile sets the provided apparmor profile to the spec -func WithProfile(profile string) containerd.SpecOpts { - return func(_ context.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error { +func WithProfile(profile string) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { s.Process.ApparmorProfile = profile return nil } @@ -23,8 +23,8 @@ func WithProfile(profile string) containerd.SpecOpts { // WithDefaultProfile will generate a default apparmor profile under the provided name // for the container. It is only generated if a profile under that name does not exist. -func WithDefaultProfile(name string) containerd.SpecOpts { - return func(_ context.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error { +func WithDefaultProfile(name string) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { yes, err := isLoaded(name) if err != nil { return err diff --git a/contrib/seccomp/seccomp.go b/contrib/seccomp/seccomp.go index 6d4e99e49..1d4b1bfba 100644 --- a/contrib/seccomp/seccomp.go +++ b/contrib/seccomp/seccomp.go @@ -8,16 +8,16 @@ import ( "fmt" "io/ioutil" - "github.com/containerd/containerd" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" "github.com/opencontainers/runtime-spec/specs-go" ) // WithProfile receives the name of a file stored on disk comprising a json // formated seccomp profile, as specified by the opencontainers/runtime-spec. // The profile is read from the file, unmarshaled, and set to the spec. -func WithProfile(profile string) containerd.SpecOpts { - return func(_ context.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error { +func WithProfile(profile string) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { s.Linux.Seccomp = &specs.LinuxSeccomp{} f, err := ioutil.ReadFile(profile) if err != nil { @@ -32,8 +32,8 @@ func WithProfile(profile string) containerd.SpecOpts { // WithDefaultProfile sets the default seccomp profile to the spec. // Note: must follow the setting of process capabilities -func WithDefaultProfile() containerd.SpecOpts { - return func(_ context.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error { +func WithDefaultProfile() oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { s.Linux.Seccomp = DefaultProfile(s) return nil } diff --git a/docs/client-opts.md b/docs/client-opts.md index bd790e44b..fbcf0ed93 100644 --- a/docs/client-opts.md +++ b/docs/client-opts.md @@ -65,20 +65,20 @@ If we want to make a `SpecOpt` to setup a container to monitor the host system w package monitor import ( - "github.com/containerd/containerd" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" ) // WithHtop configures a container to monitor the host system via `htop` func WithHtop(s *specs.Spec) error { // make sure we are in the host pid namespace - if err := containerd.WithHostNamespace(specs.PIDNamespace)(s); err != nil { + if err := oci.WithHostNamespace(specs.PIDNamespace)(s); err != nil { return err } // make sure we set htop as our arg s.Process.Args = []string{"htop"} // make sure we have a tty set for htop - if err := containerd.WithTTY(s); err != nil { + if err := oci.WithTTY(s); err != nil { return err } return nil @@ -91,7 +91,7 @@ Adding your new option to spec generation is as easy as importing your new packa import "github.com/crosbymichael/monitor" container, err := client.NewContainer(ctx, id, - containerd.WithNewSpec(containerd.WithImageConfig(image), monitor.WithHtop), + containerd.WithNewSpec(oci.WithImageConfig(image), monitor.WithHtop), ) ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index 9502c5c8b..eab15269b 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -149,7 +149,7 @@ The container will be based off of the image, use the runtime information in the ctx, "redis-server", containerd.WithNewSnapshot("redis-server-snapshot", image), - containerd.WithNewSpec(containerd.WithImageConfig(image)), + containerd.WithNewSpec(oci.WithImageConfig(image)), ) if err != nil { return err @@ -173,6 +173,7 @@ import ( "log" "github.com/containerd/containerd" + "github.com/containerd/containerd/oci" "github.com/containerd/containerd/namespaces" ) @@ -200,7 +201,7 @@ func redisExample() error { ctx, "redis-server", containerd.WithNewSnapshot("redis-server-snapshot", image), - containerd.WithNewSpec(containerd.WithImageConfig(image)), + containerd.WithNewSpec(oci.WithImageConfig(image)), ) if err != nil { return err @@ -317,6 +318,7 @@ import ( "time" "github.com/containerd/containerd" + "github.com/containerd/containerd/oci" "github.com/containerd/containerd/namespaces" ) @@ -349,7 +351,7 @@ func redisExample() error { "redis-server", containerd.WithImage(image), containerd.WithNewSnapshot("redis-server-snapshot", image), - containerd.WithNewSpec(containerd.WithImageConfig(image)), + containerd.WithNewSpec(oci.WithImageConfig(image)), ) if err != nil { return err diff --git a/helpers_unix_test.go b/helpers_unix_test.go index 089be2620..51c034e10 100644 --- a/helpers_unix_test.go +++ b/helpers_unix_test.go @@ -7,28 +7,29 @@ import ( "fmt" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" ) const newLine = "\n" -func withExitStatus(es int) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func withExitStatus(es int) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { s.Process.Args = []string{"sh", "-c", fmt.Sprintf("exit %d", es)} return nil } } -func withProcessArgs(args ...string) SpecOpts { - return WithProcessArgs(args...) +func withProcessArgs(args ...string) oci.SpecOpts { + return oci.WithProcessArgs(args...) } -func withCat() SpecOpts { - return WithProcessArgs("cat") +func withCat() oci.SpecOpts { + return oci.WithProcessArgs("cat") } -func withTrue() SpecOpts { - return WithProcessArgs("true") +func withTrue() oci.SpecOpts { + return oci.WithProcessArgs("true") } func withExecExitStatus(s *specs.Process, es int) { @@ -41,5 +42,5 @@ func withExecArgs(s *specs.Process, args ...string) { var ( withNewSnapshot = WithNewSnapshot - withImageConfig = WithImageConfig + withImageConfig = oci.WithImageConfig ) diff --git a/helpers_windows_test.go b/helpers_windows_test.go index 3ad33f8f8..a3b4768f6 100644 --- a/helpers_windows_test.go +++ b/helpers_windows_test.go @@ -7,28 +7,29 @@ import ( "strconv" "github.com/containerd/containerd/containers" + "github.com/containerd/containerd/oci" specs "github.com/opencontainers/runtime-spec/specs-go" ) const newLine = "\r\n" -func withExitStatus(es int) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func withExitStatus(es int) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { s.Process.Args = []string{"powershell", "-noprofile", "exit", strconv.Itoa(es)} return nil } } -func withProcessArgs(args ...string) SpecOpts { - return WithProcessArgs(append([]string{"powershell", "-noprofile"}, args...)...) +func withProcessArgs(args ...string) oci.SpecOpts { + return oci.WithProcessArgs(append([]string{"powershell", "-noprofile"}, args...)...) } -func withCat() SpecOpts { - return WithProcessArgs("cmd", "/c", "more") +func withCat() oci.SpecOpts { + return oci.WithProcessArgs("cmd", "/c", "more") } -func withTrue() SpecOpts { - return WithProcessArgs("cmd", "/c") +func withTrue() oci.SpecOpts { + return oci.WithProcessArgs("cmd", "/c") } func withExecExitStatus(s *specs.Process, es int) { @@ -39,8 +40,8 @@ func withExecArgs(s *specs.Process, args ...string) { s.Args = append([]string{"powershell", "-noprofile"}, args...) } -func withImageConfig(i Image) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func withImageConfig(i Image) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error { s.Windows.LayerFolders = dockerLayerFolders return nil } diff --git a/image.go b/image.go index 8eaf3d264..53b5e2486 100644 --- a/image.go +++ b/image.go @@ -32,6 +32,8 @@ type Image interface { Config(ctx context.Context) (ocispec.Descriptor, error) // IsUnpacked returns whether or not an image is unpacked. IsUnpacked(context.Context, string) (bool, error) + // ContentStore provides a content store which contains image blob data + ContentStore() content.Store } var _ = (Image)(&image{}) @@ -166,3 +168,7 @@ func (i *image) getLayers(ctx context.Context, platform string) ([]rootfs.Layer, } return layers, nil } + +func (i *image) ContentStore() content.Store { + return i.client.ContentStore() +} diff --git a/oci/client.go b/oci/client.go new file mode 100644 index 000000000..7b7283867 --- /dev/null +++ b/oci/client.go @@ -0,0 +1,22 @@ +package oci + +import ( + "context" + + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/snapshot" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" +) + +// Client interface used by SpecOpt +type Client interface { + SnapshotService(snapshotterName string) snapshot.Snapshotter +} + +// Image interface used by some SpecOpt to query image configuration +type Image interface { + // Config descriptor for the image. + Config(ctx context.Context) (ocispec.Descriptor, error) + // ContentStore provides a content store which contains image blob data + ContentStore() content.Store +} diff --git a/spec.go b/oci/spec.go similarity index 75% rename from spec.go rename to oci/spec.go index 850f470a0..558a3570f 100644 --- a/spec.go +++ b/oci/spec.go @@ -1,4 +1,4 @@ -package containerd +package oci import ( "context" @@ -9,7 +9,7 @@ import ( // GenerateSpec will generate a default spec from the provided image // for use as a containerd container -func GenerateSpec(ctx context.Context, client *Client, c *containers.Container, opts ...SpecOpts) (*specs.Spec, error) { +func GenerateSpec(ctx context.Context, client Client, c *containers.Container, opts ...SpecOpts) (*specs.Spec, error) { s, err := createDefaultSpec(ctx, c.ID) if err != nil { return nil, err diff --git a/oci/spec_opts.go b/oci/spec_opts.go new file mode 100644 index 000000000..c940c7aab --- /dev/null +++ b/oci/spec_opts.go @@ -0,0 +1,35 @@ +package oci + +import ( + "context" + + "github.com/containerd/containerd/containers" + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +// SpecOpts sets spec specific information to a newly generated OCI spec +type SpecOpts func(context.Context, Client, *containers.Container, *specs.Spec) error + +// WithProcessArgs replaces the args on the generated spec +func WithProcessArgs(args ...string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { + s.Process.Args = args + return nil + } +} + +// WithProcessCwd replaces the current working directory on the generated spec +func WithProcessCwd(cwd string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { + s.Process.Cwd = cwd + return nil + } +} + +// WithHostname sets the container's hostname +func WithHostname(name string) SpecOpts { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { + s.Hostname = name + return nil + } +} diff --git a/spec_opts_unix.go b/oci/spec_opts_unix.go similarity index 69% rename from spec_opts_unix.go rename to oci/spec_opts_unix.go index 01d5121d4..5ca2f0f0a 100644 --- a/spec_opts_unix.go +++ b/oci/spec_opts_unix.go @@ -1,6 +1,6 @@ // +build !windows -package containerd +package oci import ( "context" @@ -16,12 +16,9 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" - "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/fs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" - "github.com/containerd/containerd/platforms" - "github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runc/libcontainer/user" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -30,7 +27,7 @@ import ( // WithTTY sets the information on the spec as well as the environment variables for // using a TTY -func WithTTY(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func WithTTY(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { s.Process.Terminal = true s.Process.Env = append(s.Process.Env, "TERM=xterm") return nil @@ -38,7 +35,7 @@ func WithTTY(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spe // WithHostNamespace allows a task to run inside the host's linux namespace func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, 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:]...) @@ -52,7 +49,7 @@ func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts { // 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(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { for i, n := range s.Linux.Namespaces { if n.Type == ns.Type { before := s.Linux.Namespaces[:i] @@ -68,13 +65,9 @@ func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts { } // WithImageConfig configures the spec to from the configuration of an Image -func WithImageConfig(i Image) SpecOpts { - return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error { - var ( - image = i.(*image) - store = client.ContentStore() - ) - ic, err := image.i.Config(ctx, store, platforms.Default()) +func WithImageConfig(image Image) SpecOpts { + return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) error { + ic, err := image.Config(ctx) if err != nil { return err } @@ -84,7 +77,7 @@ func WithImageConfig(i Image) SpecOpts { ) switch ic.MediaType { case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: - p, err := content.ReadBlob(ctx, store, ic.Digest) + p, err := content.ReadBlob(ctx, image.ContentStore(), ic.Digest) if err != nil { return err } @@ -140,7 +133,7 @@ func WithImageConfig(i Image) SpecOpts { // WithRootFSPath specifies unmanaged rootfs path. func WithRootFSPath(path string) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { if s.Root == nil { s.Root = &specs.Root{} } @@ -152,7 +145,7 @@ func WithRootFSPath(path string) SpecOpts { // WithRootFSReadonly sets specs.Root.Readonly to true func WithRootFSReadonly() SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { if s.Root == nil { s.Root = &specs.Root{} } @@ -161,22 +154,14 @@ func WithRootFSReadonly() SpecOpts { } } -// 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(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func WithNoNewPrivileges(_ context.Context, _ Client, _ *containers.Container, 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(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func WithHostHostsFile(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { s.Mounts = append(s.Mounts, specs.Mount{ Destination: "/etc/hosts", Type: "bind", @@ -187,7 +172,7 @@ func WithHostHostsFile(_ context.Context, _ *Client, _ *containers.Container, s } // WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly -func WithHostResolvconf(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func WithHostResolvconf(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { s.Mounts = append(s.Mounts, specs.Mount{ Destination: "/etc/resolv.conf", Type: "bind", @@ -198,7 +183,7 @@ func WithHostResolvconf(_ context.Context, _ *Client, _ *containers.Container, s } // WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly -func WithHostLocaltime(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { +func WithHostLocaltime(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { s.Mounts = append(s.Mounts, specs.Mount{ Destination: "/etc/localtime", Type: "bind", @@ -211,7 +196,7 @@ func WithHostLocaltime(_ context.Context, _ *Client, _ *containers.Container, s // 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(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { var hasUserns bool for _, ns := range s.Linux.Namespaces { if ns.Type == specs.UserNamespace { @@ -235,68 +220,9 @@ func WithUserNamespace(container, host, size uint32) SpecOpts { } } -// 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 withRemappedSnapshotBase(id, i, uid, gid, false) -} - -// WithRemappedSnapshotView is similar to WithRemappedSnapshot but rootfs is mounted as read-only. -func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerOpts { - return withRemappedSnapshotBase(id, i, uid, gid, true) -} - -func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Default()) - if err != nil { - return err - } - - setSnapshotterIfEmpty(c) - - 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 { - c.SnapshotKey = id - c.Image = i.Name() - return nil - } else if !errdefs.IsNotFound(err) { - return err - } - } - 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 readonly { - _, err = snapshotter.View(ctx, id, usernsID) - } else { - _, err = snapshotter.Prepare(ctx, id, usernsID) - } - if err != nil { - return err - } - c.SnapshotKey = id - c.Image = i.Name() - return nil - } -} - // WithCgroup sets the container's cgroup path func WithCgroup(path string) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { s.Linux.CgroupsPath = path return nil } @@ -305,7 +231,7 @@ func WithCgroup(path string) SpecOpts { // WithNamespacedCgroup uses the namespace set on the context to create a // root directory for containers in the cgroup with the id as the subcgroup func WithNamespacedCgroup() SpecOpts { - return func(ctx context.Context, _ *Client, c *containers.Container, s *specs.Spec) error { + return func(ctx context.Context, _ Client, c *containers.Container, s *specs.Spec) error { namespace, err := namespaces.NamespaceRequired(ctx) if err != nil { return err @@ -317,7 +243,7 @@ func WithNamespacedCgroup() SpecOpts { // WithUIDGID allows the UID and GID for the Process to be set func WithUIDGID(uid, gid uint32) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { s.Process.User.UID = uid s.Process.User.GID = gid return nil @@ -329,7 +255,7 @@ func WithUIDGID(uid, gid uint32) SpecOpts { // or uid is not found in /etc/passwd, it sets gid to be the same with // uid, and not returns error. func WithUserID(uid uint32) SpecOpts { - return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error { + return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) error { if c.Snapshotter == "" { return errors.Errorf("no snapshotter set for container") } @@ -386,7 +312,7 @@ func WithUserID(uid uint32) SpecOpts { // does not exist, or the username is not found in /etc/passwd, // it returns error. func WithUsername(username string) SpecOpts { - return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error { + return func(ctx context.Context, client Client, c *containers.Container, s *specs.Spec) error { if c.Snapshotter == "" { return errors.Errorf("no snapshotter set for container") } diff --git a/spec_opts_windows.go b/oci/spec_opts_windows.go similarity index 64% rename from spec_opts_windows.go rename to oci/spec_opts_windows.go index 1fc5d5e37..3605f8e48 100644 --- a/spec_opts_windows.go +++ b/oci/spec_opts_windows.go @@ -1,6 +1,6 @@ // +build windows -package containerd +package oci import ( "context" @@ -10,19 +10,14 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" - "github.com/containerd/containerd/platforms" "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/runtime-spec/specs-go" ) // WithImageConfig configures the spec to from the configuration of an Image -func WithImageConfig(i Image) SpecOpts { - return func(ctx context.Context, client *Client, _ *containers.Container, s *specs.Spec) error { - var ( - image = i.(*image) - store = client.ContentStore() - ) - ic, err := image.i.Config(ctx, store, platforms.Default()) +func WithImageConfig(image Image) SpecOpts { + return func(ctx context.Context, client Client, _ *containers.Container, s *specs.Spec) error { + ic, err := image.Config(ctx) if err != nil { return err } @@ -32,7 +27,7 @@ func WithImageConfig(i Image) SpecOpts { ) switch ic.MediaType { case v1.MediaTypeImageConfig, images.MediaTypeDockerSchema2Config: - p, err := content.ReadBlob(ctx, store, ic.Digest) + p, err := content.ReadBlob(ctx, image.ContentStore(), ic.Digest) if err != nil { return err } @@ -55,7 +50,7 @@ func WithImageConfig(i Image) SpecOpts { // WithTTY sets the information on the spec as well as the environment variables for // using a TTY func WithTTY(width, height int) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { + return func(_ context.Context, _ Client, _ *containers.Container, s *specs.Spec) error { s.Process.Terminal = true if s.Process.ConsoleSize == nil { s.Process.ConsoleSize = &specs.Box{} @@ -65,11 +60,3 @@ func WithTTY(width, height int) SpecOpts { return nil } } - -// WithResources sets the provided resources on the spec for task updates -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/oci/spec_unix.go similarity index 79% rename from spec_unix.go rename to oci/spec_unix.go index 00e814962..c8f3b37af 100644 --- a/spec_unix.go +++ b/oci/spec_unix.go @@ -1,17 +1,11 @@ // +build !windows -package containerd +package oci import ( "context" - "io/ioutil" - "os" "path/filepath" - "syscall" - "golang.org/x/sys/unix" - - "github.com/containerd/containerd/mount" "github.com/containerd/containerd/namespaces" specs "github.com/opencontainers/runtime-spec/specs-go" ) @@ -173,32 +167,3 @@ func createDefaultSpec(ctx context.Context, id string) (*specs.Spec, error) { } return s, nil } - -func remapRootFS(mounts []mount.Mount, uid, gid uint32) error { - root, err := ioutil.TempDir("", "ctd-remap") - if err != nil { - return err - } - defer os.RemoveAll(root) - for _, m := range mounts { - if err := m.Mount(root); err != nil { - return err - } - } - defer unix.Unmount(root, 0) - return filepath.Walk(root, incrementFS(root, uid, gid)) -} - -func incrementFS(root string, uidInc, gidInc uint32) filepath.WalkFunc { - return func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - var ( - stat = info.Sys().(*syscall.Stat_t) - u, g = int(stat.Uid + uidInc), int(stat.Gid + gidInc) - ) - // be sure the lchown the path as to not de-reference the symlink to a host file - return os.Lchown(path, u, g) - } -} diff --git a/spec_unix_test.go b/oci/spec_unix_test.go similarity index 99% rename from spec_unix_test.go rename to oci/spec_unix_test.go index 053831658..7532d1913 100644 --- a/spec_unix_test.go +++ b/oci/spec_unix_test.go @@ -1,6 +1,6 @@ // +build !windows -package containerd +package oci import ( "context" diff --git a/spec_windows.go b/oci/spec_windows.go similarity index 96% rename from spec_windows.go rename to oci/spec_windows.go index 16a58b446..64c228883 100644 --- a/spec_windows.go +++ b/oci/spec_windows.go @@ -1,4 +1,4 @@ -package containerd +package oci import ( "context" diff --git a/spec_opts.go b/spec_opts.go deleted file mode 100644 index 2dbf8214a..000000000 --- a/spec_opts.go +++ /dev/null @@ -1,74 +0,0 @@ -package containerd - -import ( - "context" - - "github.com/containerd/containerd/containers" - "github.com/containerd/typeurl" - specs "github.com/opencontainers/runtime-spec/specs-go" -) - -// SpecOpts sets spec specific information to a newly generated OCI spec -type SpecOpts func(context.Context, *Client, *containers.Container, *specs.Spec) error - -// WithProcessArgs replaces the args on the generated spec -func WithProcessArgs(args ...string) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { - s.Process.Args = args - return nil - } -} - -// WithProcessCwd replaces the current working directory on the generated spec -func WithProcessCwd(cwd string) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { - s.Process.Cwd = cwd - return nil - } -} - -// WithHostname sets the container's hostname -func WithHostname(name string) SpecOpts { - return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error { - s.Hostname = name - return nil - } -} - -// WithNewSpec generates a new spec for a new container -func WithNewSpec(opts ...SpecOpts) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - s, err := createDefaultSpec(ctx, c.ID) - if err != nil { - return err - } - for _, o := range opts { - if err := o(ctx, client, c, s); err != nil { - return err - } - } - any, err := typeurl.MarshalAny(s) - if err != nil { - return err - } - c.Spec = any - return nil - } -} - -// WithSpec sets the provided spec on the container -func WithSpec(s *specs.Spec, opts ...SpecOpts) NewContainerOpts { - return func(ctx context.Context, client *Client, c *containers.Container) error { - for _, o := range opts { - if err := o(ctx, client, c, s); err != nil { - return err - } - } - any, err := typeurl.MarshalAny(s) - if err != nil { - return err - } - c.Spec = any - return nil - } -} diff --git a/task_opts_linux.go b/task_opts_linux.go new file mode 100644 index 000000000..5b91cb548 --- /dev/null +++ b/task_opts_linux.go @@ -0,0 +1,15 @@ +package containerd + +import ( + "context" + + "github.com/opencontainers/runtime-spec/specs-go" +) + +// WithResources sets the provided resources for task updates +func WithResources(resources *specs.LinuxResources) UpdateTaskOpts { + return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { + r.Resources = resources + return nil + } +} diff --git a/task_opts_windows.go b/task_opts_windows.go new file mode 100644 index 000000000..d77402c67 --- /dev/null +++ b/task_opts_windows.go @@ -0,0 +1,15 @@ +package containerd + +import ( + "context" + + specs "github.com/opencontainers/runtime-spec/specs-go" +) + +// WithResources sets the provided resources on the spec for task updates +func WithResources(resources *specs.WindowsResources) UpdateTaskOpts { + return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error { + r.Resources = resources + return nil + } +}