Merge pull request #1413 from crosbymichael/user-opts
Implement WithUsername for /etc/passwd lookup
This commit is contained in:
commit
a6ce1ef2a1
27
README.md
27
README.md
@ -60,25 +60,25 @@ image, err := client.Pull(context, "docker.io/library/redis:latest")
|
|||||||
err := client.Push(context, "docker.io/library/redis:latest", image.Target())
|
err := client.Push(context, "docker.io/library/redis:latest", image.Target())
|
||||||
```
|
```
|
||||||
|
|
||||||
### OCI Runtime Specification
|
|
||||||
|
|
||||||
containerd fully supports the OCI runtime specification for running containers. We have built in functions to help you generate runtime specifications based on images as well as custom parameters.
|
|
||||||
|
|
||||||
```go
|
|
||||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(context, image))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Containers
|
### Containers
|
||||||
|
|
||||||
In containerd, a container is a metadata object. Resources such as an OCI runtime specification, image, root filesystem, and other metadata can be attached to a container.
|
In containerd, a container is a metadata object. Resources such as an OCI runtime specification, image, root filesystem, and other metadata can be attached to a container.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
redis, err := client.NewContainer(context, "redis-master",
|
redis, err := client.NewContainer(context, "redis-master")
|
||||||
containerd.WithSpec(spec),
|
|
||||||
)
|
|
||||||
defer redis.Delete(context)
|
defer redis.Delete(context)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### OCI Runtime Specification
|
||||||
|
|
||||||
|
containerd fully supports the OCI runtime specification for running containers. We have built in functions to help you generate runtime specifications based on images as well as custom parameters.
|
||||||
|
|
||||||
|
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)))
|
||||||
|
```
|
||||||
|
|
||||||
## Root Filesystems
|
## Root Filesystems
|
||||||
|
|
||||||
containerd allows you to use overlay or snapshot filesystems with your containers. It comes with builtin support for overlayfs and btrfs.
|
containerd allows you to use overlay or snapshot filesystems with your containers. It comes with builtin support for overlayfs and btrfs.
|
||||||
@ -89,16 +89,17 @@ image, err := client.Pull(context, "docker.io/library/redis:latest", containerd.
|
|||||||
|
|
||||||
// allocate a new RW root filesystem for a container based on the image
|
// allocate a new RW root filesystem for a container based on the image
|
||||||
redis, err := client.NewContainer(context, "redis-master",
|
redis, err := client.NewContainer(context, "redis-master",
|
||||||
containerd.WithSpec(spec),
|
|
||||||
containerd.WithNewSnapshot("redis-rootfs", image),
|
containerd.WithNewSnapshot("redis-rootfs", image),
|
||||||
|
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// use a readonly filesystem with multiple containers
|
// use a readonly filesystem with multiple containers
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
id := fmt.Sprintf("id-%s", i)
|
id := fmt.Sprintf("id-%s", i)
|
||||||
container, err := client.NewContainer(ctx, id,
|
container, err := client.NewContainer(ctx, id,
|
||||||
containerd.WithSpec(spec),
|
|
||||||
containerd.WithNewSnapshotView(id, image),
|
containerd.WithNewSnapshotView(id, image),
|
||||||
|
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import specs "github.com/opencontainers/runtime-spec/specs-go"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
// WithApparmor sets the provided apparmor profile to the spec
|
// WithApparmor sets the provided apparmor profile to the spec
|
||||||
func WithApparmorProfile(profile string) SpecOpts {
|
func WithApparmorProfile(profile string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.ApparmorProfile = profile
|
s.Process.ApparmorProfile = profile
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ func BenchmarkContainerCreate(b *testing.B) {
|
|||||||
b.Error(err)
|
b.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
spec, err := GenerateSpec(WithImageConfig(ctx, image), withTrue())
|
spec, err := GenerateSpec(ctx, client, nil, WithImageConfig(image), withTrue())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
return
|
return
|
||||||
@ -63,7 +63,7 @@ func BenchmarkContainerStart(b *testing.B) {
|
|||||||
b.Error(err)
|
b.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
spec, err := GenerateSpec(WithImageConfig(ctx, image), withTrue())
|
spec, err := GenerateSpec(ctx, client, nil, WithImageConfig(image), withTrue())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -98,7 +98,7 @@ func test(c config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
logrus.Info("generating spec from image")
|
logrus.Info("generating spec from image")
|
||||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(ctx, image), containerd.WithProcessArgs("true"))
|
spec, err := containerd.GenerateSpec(ctx, client, nil, containerd.WithImageConfig(image), containerd.WithProcessArgs("true"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -24,7 +25,7 @@ type killer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withEnv(context *cli.Context) containerd.SpecOpts {
|
func withEnv(context *cli.Context) containerd.SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ gocontext.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
env := context.StringSlice("env")
|
env := context.StringSlice("env")
|
||||||
if len(env) > 0 {
|
if len(env) > 0 {
|
||||||
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, env)
|
s.Process.Env = replaceOrAppendEnvValues(s.Process.Env, env)
|
||||||
@ -34,7 +35,7 @@ func withEnv(context *cli.Context) containerd.SpecOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withMounts(context *cli.Context) containerd.SpecOpts {
|
func withMounts(context *cli.Context) containerd.SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ gocontext.Context, _ *containerd.Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
for _, mount := range context.StringSlice("mount") {
|
for _, mount := range context.StringSlice("mount") {
|
||||||
m, err := parseMountFlag(mount)
|
m, err := parseMountFlag(mount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -90,7 +90,7 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
opts = append(opts, containerd.WithImageConfig(ctx, image))
|
opts = append(opts, containerd.WithImageConfig(image))
|
||||||
cOpts = append(cOpts, containerd.WithImage(image))
|
cOpts = append(cOpts, containerd.WithImage(image))
|
||||||
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
|
cOpts = append(cOpts, containerd.WithSnapshotter(context.String("snapshotter")))
|
||||||
if context.Bool("readonly") {
|
if context.Bool("readonly") {
|
||||||
@ -111,11 +111,7 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
|
|||||||
if context.Bool("net-host") {
|
if context.Bool("net-host") {
|
||||||
opts = append(opts, setHostNetworking())
|
opts = append(opts, setHostNetworking())
|
||||||
}
|
}
|
||||||
spec, err := containerd.GenerateSpec(opts...)
|
cOpts = append([]containerd.NewContainerOpts{containerd.WithNewSpec(opts...)}, cOpts...)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cOpts = append([]containerd.NewContainerOpts{containerd.WithSpec(spec)}, cOpts...)
|
|
||||||
return client.NewContainer(ctx, id, cOpts...)
|
return client.NewContainer(ctx, id, cOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
digest "github.com/opencontainers/go-digest"
|
digest "github.com/opencontainers/go-digest"
|
||||||
@ -25,7 +26,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withLayers(context *cli.Context) containerd.SpecOpts {
|
func withLayers(context *cli.Context) containerd.SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx gocontext.Context, client *containerd.Client, c *containers.Container, s *specs.Spec) error {
|
||||||
l := context.StringSlice("layer")
|
l := context.StringSlice("layer")
|
||||||
if l == nil {
|
if l == nil {
|
||||||
return errors.Wrap(errdefs.ErrInvalidArgument, "base layers must be specified with `--layer`")
|
return errors.Wrap(errdefs.ErrInvalidArgument, "base layers must be specified with `--layer`")
|
||||||
@ -65,7 +66,7 @@ func handleConsoleResize(ctx gocontext.Context, task resizer, con console.Consol
|
|||||||
|
|
||||||
func withTTY(terminal bool) containerd.SpecOpts {
|
func withTTY(terminal bool) containerd.SpecOpts {
|
||||||
if !terminal {
|
if !terminal {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx gocontext.Context, client *containerd.Client, c *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Terminal = false
|
s.Process.Terminal = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -85,8 +86,6 @@ func setHostNetworking() containerd.SpecOpts {
|
|||||||
|
|
||||||
func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
|
func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
|
||||||
|
|
||||||
// ref = context.Args().First()
|
// ref = context.Args().First()
|
||||||
id = context.Args().Get(1)
|
id = context.Args().Get(1)
|
||||||
args = context.Args()[2:]
|
args = context.Args()[2:]
|
||||||
@ -109,13 +108,8 @@ func newContainer(ctx gocontext.Context, client *containerd.Client, context *cli
|
|||||||
opts = append(opts, containerd.WithProcessArgs(args...))
|
opts = append(opts, containerd.WithProcessArgs(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := containerd.GenerateSpec(opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.NewContainer(ctx, id,
|
return client.NewContainer(ctx, id,
|
||||||
containerd.WithSpec(spec),
|
containerd.WithNewSpec(opts...),
|
||||||
containerd.WithContainerLabels(labels),
|
containerd.WithContainerLabels(labels),
|
||||||
containerd.WithRuntime(context.String("runtime")),
|
containerd.WithRuntime(context.String("runtime")),
|
||||||
// TODO(mlaventure): containerd.WithImage(image),
|
// TODO(mlaventure): containerd.WithImage(image),
|
||||||
|
@ -28,12 +28,7 @@ func TestCheckpointRestore(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -113,12 +108,7 @@ func TestCheckpointRestoreNewContainer(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -211,12 +201,7 @@ func TestCheckpointLeaveRunning(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "100")), WithNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
|
@ -8,12 +8,14 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/containerd/cgroups"
|
"github.com/containerd/cgroups"
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/linux/runcopts"
|
"github.com/containerd/containerd/linux/runcopts"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -39,16 +41,14 @@ func TestContainerUpdate(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
spec, err := generateSpec(WithImageConfig(ctx, image), withProcessArgs("sleep", "30"))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
limit := int64(32 * 1024 * 1024)
|
limit := int64(32 * 1024 * 1024)
|
||||||
spec.Linux.Resources.Memory = &specs.LinuxMemory{
|
memory := func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
|
s.Linux.Resources.Memory = &specs.LinuxMemory{
|
||||||
Limit: &limit,
|
Limit: &limit,
|
||||||
}
|
}
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewSnapshot(id, image))
|
return nil
|
||||||
|
}
|
||||||
|
container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), withProcessArgs("sleep", "30"), memory), WithNewSnapshot(id, image))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -127,12 +127,7 @@ func TestShimInCgroup(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
spec, err := GenerateSpec(WithImageConfig(ctx, image), WithProcessArgs("sleep", "30"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(WithImageConfig(image), WithProcessArgs("sleep", "30")), WithNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -202,12 +197,7 @@ func TestDaemonRestart(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "30"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "30")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -293,12 +283,7 @@ func TestContainerAttach(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withCat())
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withCat()), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -380,3 +365,80 @@ func TestContainerAttach(t *testing.T) {
|
|||||||
t.Errorf("expected output %q but received %q", expected, output)
|
t.Errorf("expected output %q but received %q", expected, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContainerUsername(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
client, err := newClient(t, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
var (
|
||||||
|
image Image
|
||||||
|
ctx, cancel = testContext()
|
||||||
|
id = t.Name()
|
||||||
|
)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
image, err = client.GetImage(ctx, testImage)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
direct, err := NewDirectIO(ctx, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer direct.Delete()
|
||||||
|
var (
|
||||||
|
wg sync.WaitGroup
|
||||||
|
buf = bytes.NewBuffer(nil)
|
||||||
|
)
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
io.Copy(buf, direct.Stdout)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 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")),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer container.Delete(ctx, WithSnapshotCleanup)
|
||||||
|
|
||||||
|
task, err := container.NewTask(ctx, direct.IOCreate)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer task.Delete(ctx)
|
||||||
|
|
||||||
|
statusC, err := task.Wait(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := task.Start(ctx); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
<-statusC
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
output := strings.TrimSuffix(buf.String(), "\n")
|
||||||
|
if output != "31" {
|
||||||
|
t.Errorf("expected squid uid to be 31 but received %q", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -55,16 +55,10 @@ func TestNewContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
spec, err := generateSpec()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := testContext()
|
ctx, cancel := testContext()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec))
|
container, err := client.NewContainer(ctx, id, WithNewSpec())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -106,13 +100,7 @@ func TestContainerStart(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withExitStatus(7)), withNewSnapshot(id, image))
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withExitStatus(7))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -184,13 +172,7 @@ func TestContainerOutput(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("echo", expected)), withNewSnapshot(id, image))
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("echo", expected))
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -258,12 +240,7 @@ func TestContainerExec(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -287,6 +264,11 @@ func TestContainerExec(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
spec, err := container.Spec()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// start an exec process without running the original container process info
|
// start an exec process without running the original container process info
|
||||||
processSpec := spec.Process
|
processSpec := spec.Process
|
||||||
@ -357,12 +339,7 @@ func TestContainerPids(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -435,12 +412,7 @@ func TestContainerCloseIO(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withCat())
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withCat()), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -505,12 +477,7 @@ func TestDeleteRunningContainer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -573,12 +540,7 @@ func TestContainerKill(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "10"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "10")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -642,12 +604,7 @@ func TestContainerNoBinaryExists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), WithProcessArgs("nothing"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), WithProcessArgs("nothing")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -696,12 +653,7 @@ func TestContainerExecNoBinaryExists(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -723,6 +675,11 @@ func TestContainerExecNoBinaryExists(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
spec, err := container.Spec()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// start an exec process without running the original container process
|
// start an exec process without running the original container process
|
||||||
processSpec := spec.Process
|
processSpec := spec.Process
|
||||||
@ -769,17 +726,11 @@ func TestUserNamespaces(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(
|
container, err := client.NewContainer(ctx, id,
|
||||||
withImageConfig(ctx, image),
|
WithNewSpec(withImageConfig(image),
|
||||||
withExitStatus(7),
|
withExitStatus(7),
|
||||||
withUserNamespace(0, 1000, 10000),
|
withUserNamespace(0, 1000, 10000),
|
||||||
)
|
),
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id,
|
|
||||||
WithSpec(spec),
|
|
||||||
withRemappedSnapshot(id, image, 1000, 1000),
|
withRemappedSnapshot(id, image, 1000, 1000),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -852,12 +803,7 @@ func TestWaitStoppedTask(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withExitStatus(7))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withExitStatus(7)), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -928,12 +874,7 @@ func TestWaitStoppedProcess(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -956,6 +897,11 @@ func TestWaitStoppedProcess(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
spec, err := container.Spec()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// start an exec process without running the original container process info
|
// start an exec process without running the original container process info
|
||||||
processSpec := spec.Process
|
processSpec := spec.Process
|
||||||
@ -1027,12 +973,7 @@ func TestTaskForceDelete(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "30"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "30")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -1080,12 +1021,7 @@ func TestProcessForceDelete(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "30"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "30")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -1110,6 +1046,12 @@ func TestProcessForceDelete(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
spec, err := container.Spec()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
processSpec := spec.Process
|
processSpec := spec.Process
|
||||||
withExecArgs(processSpec, "sleep", "20")
|
withExecArgs(processSpec, "sleep", "20")
|
||||||
execID := t.Name() + "_exec"
|
execID := t.Name() + "_exec"
|
||||||
@ -1160,16 +1102,11 @@ func TestContainerHostname(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image),
|
||||||
withImageConfig(ctx, image),
|
|
||||||
withProcessArgs("hostname"),
|
withProcessArgs("hostname"),
|
||||||
WithHostname(expected),
|
WithHostname(expected),
|
||||||
)
|
),
|
||||||
if err != nil {
|
withNewSnapshot(id, image))
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -1243,12 +1180,7 @@ func TestContainerExitedAtSet(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withTrue())
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withTrue()), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -1315,12 +1247,7 @@ func TestDeleteContainerExecCreated(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spec, err := generateSpec(withImageConfig(ctx, image), withProcessArgs("sleep", "100"))
|
container, err := client.NewContainer(ctx, id, WithNewSpec(withImageConfig(image), withProcessArgs("sleep", "100")), withNewSnapshot(id, image))
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), withNewSnapshot(id, image))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
@ -1343,6 +1270,11 @@ func TestDeleteContainerExecCreated(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
spec, err := container.Spec()
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// start an exec process without running the original container process info
|
// start an exec process without running the original container process info
|
||||||
processSpec := spec.Process
|
processSpec := spec.Process
|
||||||
|
@ -90,9 +90,8 @@ Adding your new option to spec generation is as easy as importing your new packa
|
|||||||
```go
|
```go
|
||||||
import "github.com/crosbymichael/monitor"
|
import "github.com/crosbymichael/monitor"
|
||||||
|
|
||||||
spec, err := containerd.GenerateSpec(
|
container, err := client.NewContainer(ctx, id,
|
||||||
containerd.WithImageConfig(ctx, image),
|
containerd.WithNewSpec(containerd.WithImageConfig(image), monitor.WithHtop),
|
||||||
monitor.WithHtop,
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -90,28 +90,19 @@ We use the `containerd.WithPullUnpack` so that we not only fetch and download th
|
|||||||
|
|
||||||
## Creating an OCI Spec and Container
|
## Creating an OCI Spec and Container
|
||||||
|
|
||||||
Now that we have an image to base our container off of, we need to generate an OCI runtime specification that the container can be based off of.
|
Now that we have an image to base our container off of, we need to generate an OCI runtime specification that the container can be based off of as well as the new container.
|
||||||
|
|
||||||
containerd provides reasonable defaults for generating OCI runtime specs.
|
containerd provides reasonable defaults for generating OCI runtime specs.
|
||||||
There is also an `Opt` for modifying the default config based on the image that we pulled.
|
There is also an `Opt` for modifying the default config based on the image that we pulled.
|
||||||
|
|
||||||
```go
|
|
||||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(ctx, image))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
After we have a spec generated we need to create a container.
|
|
||||||
The container will be based off of the image, use the runtime information in the spec that was just created, and we will allocate a new read-write snapshot so the container can store any persistent information.
|
The container will be based off of the image, use the runtime information in the spec that was just created, and we will allocate a new read-write snapshot so the container can store any persistent information.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
container, err := client.NewContainer(
|
container, err := client.NewContainer(
|
||||||
ctx,
|
ctx,
|
||||||
"redis-server",
|
"redis-server",
|
||||||
containerd.WithSpec(spec),
|
|
||||||
containerd.WithImage(image),
|
|
||||||
containerd.WithNewSnapshot("redis-server-snapshot", image),
|
containerd.WithNewSnapshot("redis-server-snapshot", image),
|
||||||
|
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -119,6 +110,8 @@ The container will be based off of the image, use the runtime information in the
|
|||||||
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
|
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you have an existing OCI specification created you can use `containerd.WithSpec(spec)` to set it on the container.
|
||||||
|
|
||||||
When creating a new snapshot for the container we need to provide a snapshot ID as well as the Image that the container will be based on.
|
When creating a new snapshot for the container we need to provide a snapshot ID as well as the Image that the container will be based on.
|
||||||
By providing a separate snapshot ID than the container ID we can easily reuse, existing snapshots across different containers.
|
By providing a separate snapshot ID than the container ID we can easily reuse, existing snapshots across different containers.
|
||||||
|
|
||||||
@ -244,19 +237,13 @@ func redisExample() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate an OCI runtime spec using the Args, Env, etc from the redis image that we pulled
|
|
||||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(ctx, image))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a container
|
// create a container
|
||||||
container, err := client.NewContainer(
|
container, err := client.NewContainer(
|
||||||
ctx,
|
ctx,
|
||||||
"redis-server",
|
"redis-server",
|
||||||
containerd.WithSpec(spec),
|
|
||||||
containerd.WithImage(image),
|
containerd.WithImage(image),
|
||||||
containerd.WithNewSnapshot("redis-server-snapshot", image),
|
containerd.WithNewSnapshot("redis-server-snapshot", image),
|
||||||
|
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -6,17 +6,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const newLine = "\n"
|
const newLine = "\n"
|
||||||
|
|
||||||
func generateSpec(opts ...SpecOpts) (*specs.Spec, error) {
|
|
||||||
return GenerateSpec(opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func withExitStatus(es int) SpecOpts {
|
func withExitStatus(es int) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Args = []string{"sh", "-c", fmt.Sprintf("exit %d", es)}
|
s.Process.Args = []string{"sh", "-c", fmt.Sprintf("exit %d", es)}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -42,14 +39,9 @@ func withExecArgs(s *specs.Process, args ...string) {
|
|||||||
s.Args = args
|
s.Args = args
|
||||||
}
|
}
|
||||||
|
|
||||||
func withImageConfig(ctx context.Context, i Image) SpecOpts {
|
var (
|
||||||
return WithImageConfig(ctx, i)
|
withUserNamespace = WithUserNamespace
|
||||||
}
|
withRemappedSnapshot = WithRemappedSnapshot
|
||||||
|
withNewSnapshot = WithNewSnapshot
|
||||||
func withNewSnapshot(id string, i Image) NewContainerOpts {
|
withImageConfig = WithImageConfig
|
||||||
return WithNewSnapshot(id, i)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
var withUserNamespace = WithUserNamespace
|
|
||||||
|
|
||||||
var withRemappedSnapshot = WithRemappedSnapshot
|
|
||||||
|
@ -12,19 +12,8 @@ import (
|
|||||||
|
|
||||||
const newLine = "\r\n"
|
const newLine = "\r\n"
|
||||||
|
|
||||||
func generateSpec(opts ...SpecOpts) (*specs.Spec, error) {
|
|
||||||
spec, err := GenerateSpec(opts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
spec.Windows.LayerFolders = dockerLayerFolders
|
|
||||||
|
|
||||||
return spec, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func withExitStatus(es int) SpecOpts {
|
func withExitStatus(es int) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Args = []string{"powershell", "-noprofile", "exit", strconv.Itoa(es)}
|
s.Process.Args = []string{"powershell", "-noprofile", "exit", strconv.Itoa(es)}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -50,9 +39,9 @@ func withExecArgs(s *specs.Process, args ...string) {
|
|||||||
s.Args = append([]string{"powershell", "-noprofile"}, args...)
|
s.Args = append([]string{"powershell", "-noprofile"}, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withImageConfig(ctx context.Context, i Image) SpecOpts {
|
func withImageConfig(i Image) SpecOpts {
|
||||||
// TODO: when windows has a snapshotter remove the withImageConfig helper
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
return func(s *specs.Spec) error {
|
s.Windows.LayerFolders = dockerLayerFolders
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,9 +54,7 @@ func withNewSnapshot(id string, i Image) NewContainerOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func withUserNamespace(u, g, s uint32) SpecOpts {
|
func withUserNamespace(u, g, s uint32) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return withNoop
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func withRemappedSnapshot(id string, i Image, u, g uint32) NewContainerOpts {
|
func withRemappedSnapshot(id string, i Image, u, g uint32) NewContainerOpts {
|
||||||
@ -75,3 +62,7 @@ func withRemappedSnapshot(id string, i Image, u, g uint32) NewContainerOpts {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withNoop(_ context.Context, _ *Client, _ *containers.Container, _ *specs.Spec) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
11
spec.go
11
spec.go
@ -1,16 +1,21 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import specs "github.com/opencontainers/runtime-spec/specs-go"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
// GenerateSpec will generate a default spec from the provided image
|
// GenerateSpec will generate a default spec from the provided image
|
||||||
// for use as a containerd container
|
// for use as a containerd container
|
||||||
func GenerateSpec(opts ...SpecOpts) (*specs.Spec, error) {
|
func GenerateSpec(ctx context.Context, client *Client, c *containers.Container, opts ...SpecOpts) (*specs.Spec, error) {
|
||||||
s, err := createDefaultSpec()
|
s, err := createDefaultSpec()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, o := range opts {
|
for _, o := range opts {
|
||||||
if err := o(s); err != nil {
|
if err := o(ctx, client, c, s); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
spec_opts.go
52
spec_opts.go
@ -1,13 +1,19 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import specs "github.com/opencontainers/runtime-spec/specs-go"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/containers"
|
||||||
|
"github.com/containerd/containerd/typeurl"
|
||||||
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
)
|
||||||
|
|
||||||
// SpecOpts sets spec specific information to a newly generated OCI spec
|
// SpecOpts sets spec specific information to a newly generated OCI spec
|
||||||
type SpecOpts func(s *specs.Spec) error
|
type SpecOpts func(context.Context, *Client, *containers.Container, *specs.Spec) error
|
||||||
|
|
||||||
// WithProcessArgs replaces the args on the generated spec
|
// WithProcessArgs replaces the args on the generated spec
|
||||||
func WithProcessArgs(args ...string) SpecOpts {
|
func WithProcessArgs(args ...string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Args = args
|
s.Process.Args = args
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -15,8 +21,46 @@ func WithProcessArgs(args ...string) SpecOpts {
|
|||||||
|
|
||||||
// WithHostname sets the container's hostname
|
// WithHostname sets the container's hostname
|
||||||
func WithHostname(name string) SpecOpts {
|
func WithHostname(name string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Hostname = name
|
s.Hostname = name
|
||||||
return nil
|
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()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,23 +6,28 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/typeurl"
|
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/user"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WithTTY sets the information on the spec as well as the environment variables for
|
// WithTTY sets the information on the spec as well as the environment variables for
|
||||||
// using a TTY
|
// using a TTY
|
||||||
func WithTTY(s *specs.Spec) error {
|
func WithTTY(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Terminal = true
|
s.Process.Terminal = true
|
||||||
s.Process.Env = append(s.Process.Env, "TERM=xterm")
|
s.Process.Env = append(s.Process.Env, "TERM=xterm")
|
||||||
return nil
|
return nil
|
||||||
@ -30,7 +35,7 @@ func WithTTY(s *specs.Spec) error {
|
|||||||
|
|
||||||
// WithHostNamespace allows a task to run inside the host's linux namespace
|
// WithHostNamespace allows a task to run inside the host's linux namespace
|
||||||
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
func WithHostNamespace(ns specs.LinuxNamespaceType) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
for i, n := range s.Linux.Namespaces {
|
for i, n := range s.Linux.Namespaces {
|
||||||
if n.Type == ns {
|
if n.Type == ns {
|
||||||
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
|
s.Linux.Namespaces = append(s.Linux.Namespaces[:i], s.Linux.Namespaces[i+1:]...)
|
||||||
@ -44,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
|
// 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.
|
// spec, the existing namespace is replaced by the one provided.
|
||||||
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
for i, n := range s.Linux.Namespaces {
|
for i, n := range s.Linux.Namespaces {
|
||||||
if n.Type == ns.Type {
|
if n.Type == ns.Type {
|
||||||
before := s.Linux.Namespaces[:i]
|
before := s.Linux.Namespaces[:i]
|
||||||
@ -60,11 +65,11 @@ func WithLinuxNamespace(ns specs.LinuxNamespace) SpecOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithImageConfig configures the spec to from the configuration of an Image
|
// WithImageConfig configures the spec to from the configuration of an Image
|
||||||
func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
func WithImageConfig(i Image) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error {
|
||||||
var (
|
var (
|
||||||
image = i.(*image)
|
image = i.(*image)
|
||||||
store = image.client.ContentStore()
|
store = client.ContentStore()
|
||||||
)
|
)
|
||||||
ic, err := image.i.Config(ctx, store)
|
ic, err := image.i.Config(ctx, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,6 +105,10 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
|||||||
case 1:
|
case 1:
|
||||||
v, err := strconv.ParseUint(parts[0], 0, 10)
|
v, err := strconv.ParseUint(parts[0], 0, 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// if we cannot parse as a uint they try to see if it is a username
|
||||||
|
if err := WithUsername(config.User)(ctx, client, c, s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uid, gid = uint32(v), uint32(v)
|
uid, gid = uint32(v), uint32(v)
|
||||||
@ -129,7 +138,7 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
|||||||
|
|
||||||
// WithRootFSPath specifies unmanaged rootfs path.
|
// WithRootFSPath specifies unmanaged rootfs path.
|
||||||
func WithRootFSPath(path string, readonly bool) SpecOpts {
|
func WithRootFSPath(path string, readonly bool) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Root = &specs.Root{
|
s.Root = &specs.Root{
|
||||||
Path: path,
|
Path: path,
|
||||||
Readonly: readonly,
|
Readonly: readonly,
|
||||||
@ -139,18 +148,6 @@ func WithRootFSPath(path string, readonly bool) SpecOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// WithResources sets the provided resources on the spec for task updates
|
||||||
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
||||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||||
@ -160,13 +157,13 @@ func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithNoNewPrivileges sets no_new_privileges on the process for the container
|
// WithNoNewPrivileges sets no_new_privileges on the process for the container
|
||||||
func WithNoNewPrivileges(s *specs.Spec) error {
|
func WithNoNewPrivileges(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.NoNewPrivileges = true
|
s.Process.NoNewPrivileges = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
|
// WithHostHostsFile bind-mounts the host's /etc/hosts into the container as readonly
|
||||||
func WithHostHostsFile(s *specs.Spec) error {
|
func WithHostHostsFile(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Mounts = append(s.Mounts, specs.Mount{
|
s.Mounts = append(s.Mounts, specs.Mount{
|
||||||
Destination: "/etc/hosts",
|
Destination: "/etc/hosts",
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
@ -177,7 +174,7 @@ func WithHostHostsFile(s *specs.Spec) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
|
// WithHostResolvconf bind-mounts the host's /etc/resolv.conf into the container as readonly
|
||||||
func WithHostResolvconf(s *specs.Spec) error {
|
func WithHostResolvconf(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Mounts = append(s.Mounts, specs.Mount{
|
s.Mounts = append(s.Mounts, specs.Mount{
|
||||||
Destination: "/etc/resolv.conf",
|
Destination: "/etc/resolv.conf",
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
@ -188,7 +185,7 @@ func WithHostResolvconf(s *specs.Spec) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
|
// WithHostLocaltime bind-mounts the host's /etc/localtime into the container as readonly
|
||||||
func WithHostLocaltime(s *specs.Spec) error {
|
func WithHostLocaltime(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Mounts = append(s.Mounts, specs.Mount{
|
s.Mounts = append(s.Mounts, specs.Mount{
|
||||||
Destination: "/etc/localtime",
|
Destination: "/etc/localtime",
|
||||||
Type: "bind",
|
Type: "bind",
|
||||||
@ -201,7 +198,7 @@ func WithHostLocaltime(s *specs.Spec) error {
|
|||||||
// WithUserNamespace sets the uid and gid mappings for the task
|
// WithUserNamespace sets the uid and gid mappings for the task
|
||||||
// this can be called multiple times to add more mappings to the generated spec
|
// this can be called multiple times to add more mappings to the generated spec
|
||||||
func WithUserNamespace(container, host, size uint32) SpecOpts {
|
func WithUserNamespace(container, host, size uint32) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
var hasUserns bool
|
var hasUserns bool
|
||||||
for _, ns := range s.Linux.Namespaces {
|
for _, ns := range s.Linux.Namespaces {
|
||||||
if ns.Type == specs.UserNamespace {
|
if ns.Type == specs.UserNamespace {
|
||||||
@ -271,7 +268,7 @@ func WithRemappedSnapshot(id string, i Image, uid, gid uint32) NewContainerOpts
|
|||||||
|
|
||||||
// WithCgroup sets the container's cgroup path
|
// WithCgroup sets the container's cgroup path
|
||||||
func WithCgroup(path string) SpecOpts {
|
func WithCgroup(path string) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Linux.CgroupsPath = path
|
s.Linux.CgroupsPath = path
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -279,13 +276,69 @@ func WithCgroup(path string) SpecOpts {
|
|||||||
|
|
||||||
// WithNamespacedCgroup uses the namespace set on the context to create a
|
// WithNamespacedCgroup uses the namespace set on the context to create a
|
||||||
// root directory for containers in the cgroup with the id as the subcgroup
|
// root directory for containers in the cgroup with the id as the subcgroup
|
||||||
func WithNamespacedCgroup(ctx context.Context, id string) SpecOpts {
|
func WithNamespacedCgroup() SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx context.Context, _ *Client, c *containers.Container, s *specs.Spec) error {
|
||||||
namespace, err := namespaces.NamespaceRequired(ctx)
|
namespace, err := namespaces.NamespaceRequired(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.Linux.CgroupsPath = filepath.Join("/", namespace, id)
|
s.Linux.CgroupsPath = filepath.Join("/", namespace, c.ID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUserIDs allows the UID and GID for the Process to be set
|
||||||
|
func WithUserIDs(uid, gid uint32) SpecOpts {
|
||||||
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
|
s.Process.User.UID = uid
|
||||||
|
s.Process.User.GID = gid
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUsername sets the correct UID and GID for the container
|
||||||
|
// based on the the image's /etc/passwd contents.
|
||||||
|
// id is the snapshot id that is used
|
||||||
|
func WithUsername(username string) SpecOpts {
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
if c.RootFS == "" {
|
||||||
|
return errors.Errorf("rootfs not created for container")
|
||||||
|
}
|
||||||
|
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||||
|
mounts, err := snapshotter.Mounts(ctx, c.RootFS)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
root, err := ioutil.TempDir("", "ctd-username")
|
||||||
|
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)
|
||||||
|
f, err := os.Open(filepath.Join(root, "/etc/passwd"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
users, err := user.ParsePasswdFilter(f, func(u user.User) bool {
|
||||||
|
return u.Name == username
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(users) == 0 {
|
||||||
|
return errors.Errorf("no users found for %s", username)
|
||||||
|
}
|
||||||
|
u := users[0]
|
||||||
|
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,16 +10,15 @@ import (
|
|||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/typeurl"
|
|
||||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
func WithImageConfig(i Image) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(ctx context.Context, client *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
var (
|
var (
|
||||||
image = i.(*image)
|
image = i.(*image)
|
||||||
store = image.client.ContentStore()
|
store = client.ContentStore()
|
||||||
)
|
)
|
||||||
ic, err := image.i.Config(ctx, store)
|
ic, err := image.i.Config(ctx, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -52,7 +51,7 @@ func WithImageConfig(ctx context.Context, i Image) SpecOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WithTTY(width, height int) SpecOpts {
|
func WithTTY(width, height int) SpecOpts {
|
||||||
return func(s *specs.Spec) error {
|
return func(_ context.Context, _ *Client, _ *containers.Container, s *specs.Spec) error {
|
||||||
s.Process.Terminal = true
|
s.Process.Terminal = true
|
||||||
s.Process.ConsoleSize.Width = uint(width)
|
s.Process.ConsoleSize.Width = uint(width)
|
||||||
s.Process.ConsoleSize.Height = uint(height)
|
s.Process.ConsoleSize.Height = uint(height)
|
||||||
@ -60,17 +59,6 @@ func WithTTY(width, height int) SpecOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
|
||||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||||
r.Resources = resources
|
r.Resources = resources
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
package containerd
|
package containerd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
@ -11,7 +12,7 @@ import (
|
|||||||
func TestGenerateSpec(t *testing.T) {
|
func TestGenerateSpec(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s, err := GenerateSpec()
|
s, err := GenerateSpec(context.Background(), nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -51,7 +52,7 @@ func TestGenerateSpec(t *testing.T) {
|
|||||||
func TestSpecWithTTY(t *testing.T) {
|
func TestSpecWithTTY(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
s, err := GenerateSpec(WithTTY)
|
s, err := GenerateSpec(context.Background(), nil, nil, WithTTY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -68,7 +69,7 @@ func TestWithLinuxNamespace(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
replacedNS := specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: "/var/run/netns/test"}
|
replacedNS := specs.LinuxNamespace{Type: specs.NetworkNamespace, Path: "/var/run/netns/test"}
|
||||||
s, err := GenerateSpec(WithLinuxNamespace(replacedNS))
|
s, err := GenerateSpec(context.Background(), nil, nil, WithLinuxNamespace(replacedNS))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
111
vendor/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
111
vendor/github.com/opencontainers/runc/libcontainer/user/lookup.go
generated
vendored
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// The current operating system does not provide the required data for user lookups.
|
||||||
|
ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data")
|
||||||
|
// No matching entries found in file.
|
||||||
|
ErrNoPasswdEntries = errors.New("no matching entries in passwd file")
|
||||||
|
ErrNoGroupEntries = errors.New("no matching entries in group file")
|
||||||
|
)
|
||||||
|
|
||||||
|
func lookupUser(filter func(u User) bool) (User, error) {
|
||||||
|
// Get operating system-specific passwd reader-closer.
|
||||||
|
passwd, err := GetPasswd()
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
users, err := ParsePasswdFilter(passwd, filter)
|
||||||
|
if err != nil {
|
||||||
|
return User{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(users) == 0 {
|
||||||
|
return User{}, ErrNoPasswdEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return users[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentUser looks up the current user by their user id in /etc/passwd. If the
|
||||||
|
// user cannot be found (or there is no /etc/passwd file on the filesystem),
|
||||||
|
// then CurrentUser returns an error.
|
||||||
|
func CurrentUser() (User, error) {
|
||||||
|
return LookupUid(unix.Getuid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUser looks up a user by their username in /etc/passwd. If the user
|
||||||
|
// cannot be found (or there is no /etc/passwd file on the filesystem), then
|
||||||
|
// LookupUser returns an error.
|
||||||
|
func LookupUser(username string) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Name == username
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot
|
||||||
|
// be found (or there is no /etc/passwd file on the filesystem), then LookupId
|
||||||
|
// returns an error.
|
||||||
|
func LookupUid(uid int) (User, error) {
|
||||||
|
return lookupUser(func(u User) bool {
|
||||||
|
return u.Uid == uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupGroup(filter func(g Group) bool) (Group, error) {
|
||||||
|
// Get operating system-specific group reader-closer.
|
||||||
|
group, err := GetGroup()
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
|
||||||
|
// Get the users.
|
||||||
|
groups, err := ParseGroupFilter(group, filter)
|
||||||
|
if err != nil {
|
||||||
|
return Group{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// No user entries found.
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return Group{}, ErrNoGroupEntries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the first entry is the "correct" one.
|
||||||
|
return groups[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentGroup looks up the current user's group by their primary group id's
|
||||||
|
// entry in /etc/passwd. If the group cannot be found (or there is no
|
||||||
|
// /etc/group file on the filesystem), then CurrentGroup returns an error.
|
||||||
|
func CurrentGroup() (Group, error) {
|
||||||
|
return LookupGid(unix.Getgid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGroup looks up a group by its name in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGroup
|
||||||
|
// returns an error.
|
||||||
|
func LookupGroup(groupname string) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Name == groupname
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupGid looks up a group by its group id in /etc/group. If the group cannot
|
||||||
|
// be found (or there is no /etc/group file on the filesystem), then LookupGid
|
||||||
|
// returns an error.
|
||||||
|
func LookupGid(gid int) (Group, error) {
|
||||||
|
return lookupGroup(func(g Group) bool {
|
||||||
|
return g.Gid == gid
|
||||||
|
})
|
||||||
|
}
|
30
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
30
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unix.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unix-specific path to the passwd and group formatted files.
|
||||||
|
const (
|
||||||
|
unixPasswdPath = "/etc/passwd"
|
||||||
|
unixGroupPath = "/etc/group"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return unixPasswdPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixPasswdPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return unixGroupPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return os.Open(unixGroupPath)
|
||||||
|
}
|
21
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
21
vendor/github.com/opencontainers/runc/libcontainer/user/lookup_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
func GetPasswdPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPasswd() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroupPath() (string, error) {
|
||||||
|
return "", ErrUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetGroup() (io.ReadCloser, error) {
|
||||||
|
return nil, ErrUnsupported
|
||||||
|
}
|
441
vendor/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
441
vendor/github.com/opencontainers/runc/libcontainer/user/user.go
generated
vendored
Normal file
@ -0,0 +1,441 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
minId = 0
|
||||||
|
maxId = 1<<31 - 1 //for 32-bit systems compatibility
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrRange = fmt.Errorf("uids and gids must be in range %d-%d", minId, maxId)
|
||||||
|
)
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Gecos string
|
||||||
|
Home string
|
||||||
|
Shell string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Group struct {
|
||||||
|
Name string
|
||||||
|
Pass string
|
||||||
|
Gid int
|
||||||
|
List []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLine(line string, v ...interface{}) {
|
||||||
|
if line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ":")
|
||||||
|
for i, p := range parts {
|
||||||
|
// Ignore cases where we don't have enough fields to populate the arguments.
|
||||||
|
// Some configuration files like to misbehave.
|
||||||
|
if len(v) <= i {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the type of the argument to figure out how to parse it, scanf() style.
|
||||||
|
// This is legit.
|
||||||
|
switch e := v[i].(type) {
|
||||||
|
case *string:
|
||||||
|
*e = p
|
||||||
|
case *int:
|
||||||
|
// "numbers", with conversion errors ignored because of some misbehaving configuration files.
|
||||||
|
*e, _ = strconv.Atoi(p)
|
||||||
|
case *[]string:
|
||||||
|
// Comma-separated lists.
|
||||||
|
if p != "" {
|
||||||
|
*e = strings.Split(p, ",")
|
||||||
|
} else {
|
||||||
|
*e = []string{}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Someone goof'd when writing code using this function. Scream so they can hear us.
|
||||||
|
panic(fmt.Sprintf("parseLine only accepts {*string, *int, *[]string} as arguments! %#v is not a pointer!", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFile(path string) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswd(passwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswd(passwd io.Reader) ([]User, error) {
|
||||||
|
return ParsePasswdFilter(passwd, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) {
|
||||||
|
passwd, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer passwd.Close()
|
||||||
|
return ParsePasswdFilter(passwd, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for passwd-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []User{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
line := strings.TrimSpace(s.Text())
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 passwd
|
||||||
|
// name:password:UID:GID:GECOS:directory:shell
|
||||||
|
// Name:Pass:Uid:Gid:Gecos:Home:Shell
|
||||||
|
// root:x:0:0:root:/root:/bin/bash
|
||||||
|
// adm:x:3:4:adm:/var/adm:/bin/false
|
||||||
|
p := User{}
|
||||||
|
parseLine(line, &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFile(path string) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroup(group io.Reader) ([]Group, error) {
|
||||||
|
return ParseGroupFilter(group, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) {
|
||||||
|
group, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer group.Close()
|
||||||
|
return ParseGroupFilter(group, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) {
|
||||||
|
if r == nil {
|
||||||
|
return nil, fmt.Errorf("nil source for group-formatted data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
s = bufio.NewScanner(r)
|
||||||
|
out = []Group{}
|
||||||
|
)
|
||||||
|
|
||||||
|
for s.Scan() {
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
text := s.Text()
|
||||||
|
if text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// see: man 5 group
|
||||||
|
// group_name:password:GID:user_list
|
||||||
|
// Name:Pass:Gid:List
|
||||||
|
// root:x:0:root
|
||||||
|
// adm:x:4:root,adm,daemon
|
||||||
|
p := Group{}
|
||||||
|
parseLine(text, &p.Name, &p.Pass, &p.Gid, &p.List)
|
||||||
|
|
||||||
|
if filter == nil || filter(p) {
|
||||||
|
out = append(out, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecUser struct {
|
||||||
|
Uid int
|
||||||
|
Gid int
|
||||||
|
Sgids []int
|
||||||
|
Home string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the
|
||||||
|
// given file paths and uses that data as the arguments to GetExecUser. If the
|
||||||
|
// files cannot be opened for any reason, the error is ignored and a nil
|
||||||
|
// io.Reader is passed instead.
|
||||||
|
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
|
||||||
|
var passwd, group io.Reader
|
||||||
|
|
||||||
|
if passwdFile, err := os.Open(passwdPath); err == nil {
|
||||||
|
passwd = passwdFile
|
||||||
|
defer passwdFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupFile, err := os.Open(groupPath); err == nil {
|
||||||
|
group = groupFile
|
||||||
|
defer groupFile.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetExecUser(userSpec, defaults, passwd, group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExecUser parses a user specification string (using the passwd and group
|
||||||
|
// readers as sources for /etc/passwd and /etc/group data, respectively). In
|
||||||
|
// the case of blank fields or missing data from the sources, the values in
|
||||||
|
// defaults is used.
|
||||||
|
//
|
||||||
|
// GetExecUser will return an error if a user or group literal could not be
|
||||||
|
// found in any entry in passwd and group respectively.
|
||||||
|
//
|
||||||
|
// Examples of valid user specifications are:
|
||||||
|
// * ""
|
||||||
|
// * "user"
|
||||||
|
// * "uid"
|
||||||
|
// * "user:group"
|
||||||
|
// * "uid:gid
|
||||||
|
// * "user:gid"
|
||||||
|
// * "uid:group"
|
||||||
|
//
|
||||||
|
// It should be noted that if you specify a numeric user or group id, they will
|
||||||
|
// not be evaluated as usernames (only the metadata will be filled). So attempting
|
||||||
|
// to parse a user with user.Name = "1337" will produce the user with a UID of
|
||||||
|
// 1337.
|
||||||
|
func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) {
|
||||||
|
if defaults == nil {
|
||||||
|
defaults = new(ExecUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy over defaults.
|
||||||
|
user := &ExecUser{
|
||||||
|
Uid: defaults.Uid,
|
||||||
|
Gid: defaults.Gid,
|
||||||
|
Sgids: defaults.Sgids,
|
||||||
|
Home: defaults.Home,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sgids slice *cannot* be nil.
|
||||||
|
if user.Sgids == nil {
|
||||||
|
user.Sgids = []int{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for userArg to have either "user" syntax, or optionally "user:group" syntax
|
||||||
|
var userArg, groupArg string
|
||||||
|
parseLine(userSpec, &userArg, &groupArg)
|
||||||
|
|
||||||
|
// Convert userArg and groupArg to be numeric, so we don't have to execute
|
||||||
|
// Atoi *twice* for each iteration over lines.
|
||||||
|
uidArg, uidErr := strconv.Atoi(userArg)
|
||||||
|
gidArg, gidErr := strconv.Atoi(groupArg)
|
||||||
|
|
||||||
|
// Find the matching user.
|
||||||
|
users, err := ParsePasswdFilter(passwd, func(u User) bool {
|
||||||
|
if userArg == "" {
|
||||||
|
// Default to current state of the user.
|
||||||
|
return u.Uid == user.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
if uidErr == nil {
|
||||||
|
// If the userArg is numeric, always treat it as a UID.
|
||||||
|
return uidArg == u.Uid
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.Name == userArg
|
||||||
|
})
|
||||||
|
|
||||||
|
// If we can't find the user, we have to bail.
|
||||||
|
if err != nil && passwd != nil {
|
||||||
|
if userArg == "" {
|
||||||
|
userArg = strconv.Itoa(user.Uid)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to find user %s: %v", userArg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var matchedUserName string
|
||||||
|
if len(users) > 0 {
|
||||||
|
// First match wins, even if there's more than one matching entry.
|
||||||
|
matchedUserName = users[0].Name
|
||||||
|
user.Uid = users[0].Uid
|
||||||
|
user.Gid = users[0].Gid
|
||||||
|
user.Home = users[0].Home
|
||||||
|
} else if userArg != "" {
|
||||||
|
// If we can't find a user with the given username, the only other valid
|
||||||
|
// option is if it's a numeric username with no associated entry in passwd.
|
||||||
|
|
||||||
|
if uidErr != nil {
|
||||||
|
// Not numeric.
|
||||||
|
return nil, fmt.Errorf("unable to find user %s: %v", userArg, ErrNoPasswdEntries)
|
||||||
|
}
|
||||||
|
user.Uid = uidArg
|
||||||
|
|
||||||
|
// Must be inside valid uid range.
|
||||||
|
if user.Uid < minId || user.Uid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, so it's numeric. We can just roll with this.
|
||||||
|
}
|
||||||
|
|
||||||
|
// On to the groups. If we matched a username, we need to do this because of
|
||||||
|
// the supplementary group IDs.
|
||||||
|
if groupArg != "" || matchedUserName != "" {
|
||||||
|
groups, err := ParseGroupFilter(group, func(g Group) bool {
|
||||||
|
// If the group argument isn't explicit, we'll just search for it.
|
||||||
|
if groupArg == "" {
|
||||||
|
// Check if user is a member of this group.
|
||||||
|
for _, u := range g.List {
|
||||||
|
if u == matchedUserName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if gidErr == nil {
|
||||||
|
// If the groupArg is numeric, always treat it as a GID.
|
||||||
|
return gidArg == g.Gid
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Name == groupArg
|
||||||
|
})
|
||||||
|
if err != nil && group != nil {
|
||||||
|
return nil, fmt.Errorf("unable to find groups for spec %v: %v", matchedUserName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only start modifying user.Gid if it is in explicit form.
|
||||||
|
if groupArg != "" {
|
||||||
|
if len(groups) > 0 {
|
||||||
|
// First match wins, even if there's more than one matching entry.
|
||||||
|
user.Gid = groups[0].Gid
|
||||||
|
} else {
|
||||||
|
// If we can't find a group with the given name, the only other valid
|
||||||
|
// option is if it's a numeric group name with no associated entry in group.
|
||||||
|
|
||||||
|
if gidErr != nil {
|
||||||
|
// Not numeric.
|
||||||
|
return nil, fmt.Errorf("unable to find group %s: %v", groupArg, ErrNoGroupEntries)
|
||||||
|
}
|
||||||
|
user.Gid = gidArg
|
||||||
|
|
||||||
|
// Must be inside valid gid range.
|
||||||
|
if user.Gid < minId || user.Gid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, so it's numeric. We can just roll with this.
|
||||||
|
}
|
||||||
|
} else if len(groups) > 0 && uidErr != nil {
|
||||||
|
// Supplementary group ids only make sense if in the implicit form for non-numeric users.
|
||||||
|
user.Sgids = make([]int, len(groups))
|
||||||
|
for i, group := range groups {
|
||||||
|
user.Sgids[i] = group.Gid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdditionalGroups looks up a list of groups by name or group id
|
||||||
|
// against the given /etc/group formatted data. If a group name cannot
|
||||||
|
// be found, an error will be returned. If a group id cannot be found,
|
||||||
|
// or the given group data is nil, the id will be returned as-is
|
||||||
|
// provided it is in the legal range.
|
||||||
|
func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, error) {
|
||||||
|
var groups = []Group{}
|
||||||
|
if group != nil {
|
||||||
|
var err error
|
||||||
|
groups, err = ParseGroupFilter(group, func(g Group) bool {
|
||||||
|
for _, ag := range additionalGroups {
|
||||||
|
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find additional groups %v: %v", additionalGroups, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gidMap := make(map[int]struct{})
|
||||||
|
for _, ag := range additionalGroups {
|
||||||
|
var found bool
|
||||||
|
for _, g := range groups {
|
||||||
|
// if we found a matched group either by name or gid, take the
|
||||||
|
// first matched as correct
|
||||||
|
if g.Name == ag || strconv.Itoa(g.Gid) == ag {
|
||||||
|
if _, ok := gidMap[g.Gid]; !ok {
|
||||||
|
gidMap[g.Gid] = struct{}{}
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we asked for a group but didn't find it. let's check to see
|
||||||
|
// if we wanted a numeric group
|
||||||
|
if !found {
|
||||||
|
gid, err := strconv.Atoi(ag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to find group %s", ag)
|
||||||
|
}
|
||||||
|
// Ensure gid is inside gid range.
|
||||||
|
if gid < minId || gid > maxId {
|
||||||
|
return nil, ErrRange
|
||||||
|
}
|
||||||
|
gidMap[gid] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gids := []int{}
|
||||||
|
for gid := range gidMap {
|
||||||
|
gids = append(gids, gid)
|
||||||
|
}
|
||||||
|
return gids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAdditionalGroupsPath is a wrapper around GetAdditionalGroups
|
||||||
|
// that opens the groupPath given and gives it as an argument to
|
||||||
|
// GetAdditionalGroups.
|
||||||
|
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
|
||||||
|
var group io.Reader
|
||||||
|
|
||||||
|
if groupFile, err := os.Open(groupPath); err == nil {
|
||||||
|
group = groupFile
|
||||||
|
defer groupFile.Close()
|
||||||
|
}
|
||||||
|
return GetAdditionalGroups(additionalGroups, group)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user