diff --git a/container_test.go b/container_test.go index 8a9ff7ebd..e9ecb9e2f 100644 --- a/container_test.go +++ b/container_test.go @@ -1262,3 +1262,83 @@ func TestProcessForceDelete(t *testing.T) { } <-statusC } + +func TestContainerHostname(t *testing.T) { + client, err := newClient(t, address) + if err != nil { + t.Fatal(err) + } + defer client.Close() + + var ( + image Image + ctx, cancel = testContext() + id = t.Name() + expected = "myhostname" + ) + defer cancel() + + if runtime.GOOS != "windows" { + image, err = client.GetImage(ctx, testImage) + if err != nil { + t.Error(err) + return + } + } + + spec, err := generateSpec( + withImageConfig(ctx, image), + withProcessArgs("hostname"), + WithHostname(expected), + ) + if err != nil { + t.Error(err) + return + } + container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image)) + if err != nil { + t.Error(err) + return + } + defer container.Delete(ctx, WithSnapshotCleanup) + + stdout := bytes.NewBuffer(nil) + task, err := container.NewTask(ctx, NewIO(bytes.NewBuffer(nil), stdout, bytes.NewBuffer(nil))) + if err != nil { + t.Error(err) + return + } + defer task.Delete(ctx) + + statusC := make(chan uint32, 1) + go func() { + status, err := task.Wait(ctx) + if err != nil { + t.Error(err) + } + statusC <- status + }() + + if err := task.Start(ctx); err != nil { + t.Error(err) + return + } + + status := <-statusC + if status != 0 { + t.Errorf("expected status 0 but received %d", status) + } + if _, err := task.Delete(ctx); err != nil { + t.Error(err) + return + } + cutset := "\n" + if runtime.GOOS == "windows" { + cutset = "\r\n" + } + + actual := strings.TrimSuffix(stdout.String(), cutset) + if actual != expected { + t.Errorf("expected output %q but received %q", expected, actual) + } +} diff --git a/spec_opts.go b/spec_opts.go index 8a22ed1af..66cf5b3d7 100644 --- a/spec_opts.go +++ b/spec_opts.go @@ -12,3 +12,11 @@ func WithProcessArgs(args ...string) SpecOpts { return nil } } + +// WithHostname sets the container's hostname +func WithHostname(name string) SpecOpts { + return func(s *specs.Spec) error { + s.Hostname = name + return nil + } +} diff --git a/spec_opts_unix.go b/spec_opts_unix.go index 7ee366718..e556d46b4 100644 --- a/spec_opts_unix.go +++ b/spec_opts_unix.go @@ -6,11 +6,13 @@ import ( "context" "encoding/json" "fmt" + "path/filepath" "strconv" "strings" "github.com/containerd/containerd/containers" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/namespaces" "github.com/containerd/containerd/typeurl" "github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/specs-go/v1" @@ -263,3 +265,24 @@ func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts return nil } } + +// WithCgroup sets the container's cgroup path +func WithCgroup(path string) SpecOpts { + return func(s *specs.Spec) error { + s.Linux.CgroupsPath = path + return nil + } +} + +// 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(ctx context.Context, id string) SpecOpts { + return func(s *specs.Spec) error { + namespace, err := namespaces.NamespaceRequired(ctx) + if err != nil { + return err + } + s.Linux.CgroupsPath = filepath.Join("/", namespace, id) + return nil + } +}