worker: add spec pkg for gdn->oci conversion
As we're now the ones implementing the low-level functionality of directing containerd to create a container from a very high-level description that `garden.ContainerSpec` gives, we need a way of coming up with a valid OCI runtime spec to be used by containerd. be.create(garden.ContainerSpec{}) | *--> containerd.newcontaiiner(id, oci_spec) This commit introduces the `spec` package to deal with such conversion. The details in this commit are mostly based on the current OCI spec internal conversion that Guardian (`gdn`) performs (see references), rather than taking the defaults that `containerd` comes with, as we're trying to introduce the less amount of friction possible. ref: https://github.com/opencontainers/runtime-spec/blob/master/spec.md ref: https://github.com/cloudfoundry/guardian/blob/6b021168907b2/guardiancmd/server.go ref: https://github.com/cloudfoundry/guardian/blob/0a658a3e51595/guardiancmd/command.go Signed-off-by: Ciro S. Costa <cscosta@pivotal.io>
This commit is contained in:
parent
1817c16044
commit
dd3037f07c
|
@ -0,0 +1,85 @@
|
|||
package spec
|
||||
|
||||
import "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
func OciCapabilities(privileged bool) specs.LinuxCapabilities {
|
||||
if !privileged {
|
||||
return UnprivilegedContainerCapabilities
|
||||
}
|
||||
|
||||
return PrivilegedContainerCapabilities
|
||||
}
|
||||
|
||||
var (
|
||||
PrivilegedContainerCapabilities = specs.LinuxCapabilities{
|
||||
Effective: privilegedCaps,
|
||||
Bounding: privilegedCaps,
|
||||
Inheritable: privilegedCaps,
|
||||
Permitted: privilegedCaps,
|
||||
}
|
||||
|
||||
UnprivilegedContainerCapabilities = specs.LinuxCapabilities{
|
||||
Effective: unprivilegedCaps,
|
||||
Bounding: unprivilegedCaps,
|
||||
Inheritable: unprivilegedCaps,
|
||||
Permitted: unprivilegedCaps,
|
||||
}
|
||||
|
||||
unprivilegedCaps = []string{
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_FOWNER",
|
||||
"CAP_FSETID",
|
||||
"CAP_KILL",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_SETUID",
|
||||
"CAP_SYS_CHROOT",
|
||||
}
|
||||
|
||||
privilegedCaps = []string{
|
||||
"CAP_AUDIT_CONTROL",
|
||||
"CAP_AUDIT_READ",
|
||||
"CAP_AUDIT_WRITE",
|
||||
"CAP_BLOCK_SUSPEND",
|
||||
"CAP_CHOWN",
|
||||
"CAP_DAC_OVERRIDE",
|
||||
"CAP_DAC_READ_SEARCH",
|
||||
"CAP_FOWNER",
|
||||
"CAP_FSETID",
|
||||
"CAP_IPC_LOCK",
|
||||
"CAP_IPC_OWNER",
|
||||
"CAP_KILL",
|
||||
"CAP_LEASE",
|
||||
"CAP_LINUX_IMMUTABLE",
|
||||
"CAP_MAC_ADMIN",
|
||||
"CAP_MAC_OVERRIDE",
|
||||
"CAP_MKNOD",
|
||||
"CAP_NET_ADMIN",
|
||||
"CAP_NET_BIND_SERVICE",
|
||||
"CAP_NET_BROADCAST",
|
||||
"CAP_NET_RAW",
|
||||
"CAP_SETFCAP",
|
||||
"CAP_SETGID",
|
||||
"CAP_SETPCAP",
|
||||
"CAP_SETUID",
|
||||
"CAP_SYSLOG",
|
||||
"CAP_SYS_ADMIN",
|
||||
"CAP_SYS_BOOT",
|
||||
"CAP_SYS_CHROOT",
|
||||
"CAP_SYS_MODULE",
|
||||
"CAP_SYS_NICE",
|
||||
"CAP_SYS_PACCT",
|
||||
"CAP_SYS_PTRACE",
|
||||
"CAP_SYS_RAWIO",
|
||||
"CAP_SYS_RESOURCE",
|
||||
"CAP_SYS_TIME",
|
||||
"CAP_SYS_TTY_CONFIG",
|
||||
"CAP_WAKE_ALARM",
|
||||
}
|
||||
)
|
|
@ -0,0 +1,28 @@
|
|||
package spec
|
||||
|
||||
import "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
var (
|
||||
AnyContainerDevices = []specs.LinuxDeviceCgroup{
|
||||
// runc allows these
|
||||
{Access: "m", Type: "c", Major: deviceWildcard(), Minor: deviceWildcard(), Allow: true},
|
||||
{Access: "m", Type: "b", Major: deviceWildcard(), Minor: deviceWildcard(), Allow: true},
|
||||
|
||||
{Access: "rwm", Type: "c", Major: intRef(1), Minor: intRef(3), Allow: true}, // /dev/null
|
||||
{Access: "rwm", Type: "c", Major: intRef(1), Minor: intRef(8), Allow: true}, // /dev/random
|
||||
{Access: "rwm", Type: "c", Major: intRef(1), Minor: intRef(7), Allow: true}, // /dev/full
|
||||
{Access: "rwm", Type: "c", Major: intRef(5), Minor: intRef(0), Allow: true}, // /dev/tty
|
||||
{Access: "rwm", Type: "c", Major: intRef(1), Minor: intRef(5), Allow: true}, // /dev/zero
|
||||
{Access: "rwm", Type: "c", Major: intRef(1), Minor: intRef(9), Allow: true}, // /dev/urandom
|
||||
{Access: "rwm", Type: "c", Major: intRef(5), Minor: intRef(1), Allow: true}, // /dev/console
|
||||
{Access: "rwm", Type: "c", Major: intRef(136), Minor: deviceWildcard(), Allow: true}, // /dev/pts/*
|
||||
{Access: "rwm", Type: "c", Major: intRef(5), Minor: intRef(2), Allow: true}, // /dev/ptmx
|
||||
{Access: "rwm", Type: "c", Major: intRef(10), Minor: intRef(200), Allow: true}, // /dev/net/tun
|
||||
|
||||
// we allow this
|
||||
{Access: "rwm", Type: "c", Major: intRef(10), Minor: intRef(229), Allow: true}, // /dev/fuse
|
||||
}
|
||||
)
|
||||
|
||||
func intRef(i int64) *int64 { return &i }
|
||||
func deviceWildcard() *int64 { return intRef(-1) }
|
|
@ -0,0 +1,59 @@
|
|||
package spec
|
||||
|
||||
import "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
var (
|
||||
InitMount = specs.Mount{
|
||||
Source: "/usr/local/concourse/bin/init",
|
||||
Destination: "/tmp/gdn-init",
|
||||
Type: "bind",
|
||||
Options: []string{"bind"},
|
||||
}
|
||||
|
||||
AnyContainerMounts = []specs.Mount{
|
||||
InitMount, // ours
|
||||
|
||||
{
|
||||
Destination: "/proc",
|
||||
Type: "proc",
|
||||
Source: "proc",
|
||||
Options: []string{"nosuid", "noexec", "nodev"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev",
|
||||
Type: "tmpfs",
|
||||
Source: "tmpfs",
|
||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/pts",
|
||||
Type: "devpts",
|
||||
Source: "devpts",
|
||||
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/shm",
|
||||
Type: "tmpfs",
|
||||
Source: "shm",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
|
||||
},
|
||||
{
|
||||
Destination: "/dev/mqueue",
|
||||
Type: "mqueue",
|
||||
Source: "mqueue",
|
||||
Options: []string{"nosuid", "noexec", "nodev"},
|
||||
},
|
||||
{
|
||||
Destination: "/sys",
|
||||
Type: "sysfs",
|
||||
Source: "sysfs",
|
||||
Options: []string{"nosuid", "noexec", "nodev", "ro"},
|
||||
},
|
||||
{
|
||||
Destination: "/run",
|
||||
Type: "tmpfs",
|
||||
Source: "tmpfs",
|
||||
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
|
||||
},
|
||||
}
|
||||
)
|
|
@ -0,0 +1,25 @@
|
|||
package spec
|
||||
|
||||
import "github.com/opencontainers/runtime-spec/specs-go"
|
||||
|
||||
var (
|
||||
PrivilegedContainerNamespaces = []specs.LinuxNamespace{
|
||||
{Type: specs.PIDNamespace},
|
||||
{Type: specs.IPCNamespace},
|
||||
{Type: specs.UTSNamespace},
|
||||
{Type: specs.MountNamespace},
|
||||
{Type: specs.NetworkNamespace},
|
||||
}
|
||||
|
||||
UnprivilegedContainerNamespaces = append(PrivilegedContainerNamespaces,
|
||||
specs.LinuxNamespace{Type: specs.UserNamespace},
|
||||
)
|
||||
)
|
||||
|
||||
func OciNamespaces(privileged bool) []specs.LinuxNamespace {
|
||||
if !privileged {
|
||||
return UnprivilegedContainerNamespaces
|
||||
}
|
||||
|
||||
return PrivilegedContainerNamespaces
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
package spec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"code.cloudfoundry.org/garden"
|
||||
"github.com/imdario/mergo"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
// OciSpec converts a given `garden` container specification to an OCI spec.
|
||||
//
|
||||
// TODO
|
||||
// - limits
|
||||
// - masked paths
|
||||
// - rootfs propagation
|
||||
// - seccomp
|
||||
// - user namespaces: uid/gid mappings
|
||||
// x capabilities
|
||||
// x devices
|
||||
// x env
|
||||
// x hostname
|
||||
// x mounts
|
||||
// x namespaces
|
||||
// x rootfs
|
||||
//
|
||||
//
|
||||
func OciSpec(gdn garden.ContainerSpec) (oci *specs.Spec, err error) {
|
||||
var (
|
||||
rootfs string
|
||||
mounts []specs.Mount
|
||||
)
|
||||
|
||||
if gdn.Handle == "" {
|
||||
err = fmt.Errorf("handle must be specified")
|
||||
return
|
||||
}
|
||||
|
||||
if gdn.RootFSPath == "" {
|
||||
gdn.RootFSPath = gdn.Image.URI
|
||||
}
|
||||
|
||||
rootfs, err = rootfsDir(gdn.RootFSPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
mounts, err = OciSpecBindMounts(gdn.BindMounts)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
oci = merge(defaultGardenOciSpec(gdn.Privileged), &specs.Spec{
|
||||
Version: specs.Version,
|
||||
Hostname: gdn.Handle,
|
||||
Process: &specs.Process{
|
||||
Env: gdn.Env,
|
||||
},
|
||||
Root: &specs.Root{Path: rootfs},
|
||||
Mounts: mounts,
|
||||
Annotations: map[string]string(gdn.Properties),
|
||||
// Linux: &specs.Linux{
|
||||
// Resources: &specs.LinuxResources{Memory: nil, Cpu: nil},
|
||||
// },
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// OciSpecBindMounts converts garden bindmounts to oci spec mounts.
|
||||
//
|
||||
func OciSpecBindMounts(bindMounts []garden.BindMount) (mounts []specs.Mount, err error) {
|
||||
for _, bindMount := range bindMounts {
|
||||
if bindMount.SrcPath == "" || bindMount.DstPath == "" {
|
||||
err = fmt.Errorf("src and dst must not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(bindMount.SrcPath) || !filepath.IsAbs(bindMount.DstPath) {
|
||||
err = fmt.Errorf("src and dst must be absolute")
|
||||
return
|
||||
}
|
||||
|
||||
if bindMount.Origin != garden.BindMountOriginHost {
|
||||
err = fmt.Errorf("unknown bind mount origin %d", bindMount.Origin)
|
||||
return
|
||||
}
|
||||
|
||||
mode := "ro"
|
||||
switch bindMount.Mode {
|
||||
case garden.BindMountModeRO:
|
||||
case garden.BindMountModeRW:
|
||||
mode = "rw"
|
||||
default:
|
||||
err = fmt.Errorf("unknown bind mount mode %d", bindMount.Mode)
|
||||
return
|
||||
}
|
||||
|
||||
mounts = append(mounts, specs.Mount{
|
||||
Source: bindMount.SrcPath,
|
||||
Destination: bindMount.DstPath,
|
||||
Type: "bind",
|
||||
Options: []string{"bind", mode},
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// defaultGardenOciSpec repreeseents a default set of properties necessary in
|
||||
// order to satisfy the garden interface.
|
||||
//
|
||||
// ps.: this spec is NOT complet - it must be merged with more properties to
|
||||
// form a properly working container.
|
||||
//
|
||||
func defaultGardenOciSpec(privileged bool) *specs.Spec {
|
||||
var (
|
||||
namespaces = OciNamespaces(privileged)
|
||||
capabilities = OciCapabilities(privileged)
|
||||
)
|
||||
|
||||
return &specs.Spec{
|
||||
Process: &specs.Process{
|
||||
Args: []string{"/tmp/gdn-init"},
|
||||
Capabilities: &capabilities,
|
||||
Cwd: "/",
|
||||
},
|
||||
Linux: &specs.Linux{
|
||||
Namespaces: namespaces,
|
||||
Resources: &specs.LinuxResources{
|
||||
Devices: AnyContainerDevices,
|
||||
},
|
||||
},
|
||||
Mounts: AnyContainerMounts,
|
||||
}
|
||||
}
|
||||
|
||||
// merge merges an OCI spec `dst` into `src`.
|
||||
//
|
||||
func merge(dst, src *specs.Spec) *specs.Spec {
|
||||
err := mergo.Merge(dst, src, mergo.WithAppendSlice)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf(
|
||||
"failed to merge specs %v %v - programming mistake? %w",
|
||||
dst, src, err,
|
||||
))
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// rootfsDir takes a raw rootfs uri and extracts the directory that it points to,
|
||||
// if using a valid scheme (`raw://`)
|
||||
//
|
||||
func rootfsDir(raw string) (directory string, err error) {
|
||||
if raw == "" {
|
||||
err = fmt.Errorf("rootfs must not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(raw, "://", 2)
|
||||
if len(parts) != 2 {
|
||||
err = fmt.Errorf("malformatted rootfs: must be of form 'scheme://<abs_dir>'")
|
||||
return
|
||||
}
|
||||
|
||||
var scheme string
|
||||
scheme, directory = parts[0], parts[1]
|
||||
if scheme != "raw" {
|
||||
err = fmt.Errorf("unsupported scheme '%s'", scheme)
|
||||
return
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(directory) {
|
||||
err = fmt.Errorf("directory must be an absolute path")
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,299 @@
|
|||
package spec_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.cloudfoundry.org/garden"
|
||||
"github.com/concourse/concourse/worker/backend/spec"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type Suite struct {
|
||||
suite.Suite
|
||||
*require.Assertions
|
||||
}
|
||||
|
||||
func (s *Suite) TestContainerSpecValidations() {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
spec garden.ContainerSpec
|
||||
}{
|
||||
{
|
||||
desc: "no handle specified",
|
||||
spec: garden.ContainerSpec{},
|
||||
},
|
||||
{
|
||||
desc: "rootfsPath not specified",
|
||||
spec: garden.ContainerSpec{
|
||||
Handle: "handle",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "rootfsPath without scheme",
|
||||
spec: garden.ContainerSpec{
|
||||
Handle: "handle",
|
||||
RootFSPath: "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "rootfsPath with unknown scheme",
|
||||
spec: garden.ContainerSpec{
|
||||
Handle: "handle",
|
||||
RootFSPath: "weird://foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "rootfsPath not being absolute",
|
||||
spec: garden.ContainerSpec{
|
||||
Handle: "handle",
|
||||
RootFSPath: "raw://../not/absolute/at/all",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "both rootfsPath and image specified",
|
||||
spec: garden.ContainerSpec{
|
||||
Handle: "handle",
|
||||
RootFSPath: "foo",
|
||||
Image: garden.ImageRef{URI: "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no rootfsPath, but image specified w/out scheme",
|
||||
spec: garden.ContainerSpec{
|
||||
Handle: "handle",
|
||||
Image: garden.ImageRef{URI: "bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no rootfsPath, but image specified w/ unknown scheme",
|
||||
spec: garden.ContainerSpec{
|
||||
Handle: "handle",
|
||||
Image: garden.ImageRef{URI: "weird://bar"},
|
||||
},
|
||||
},
|
||||
} {
|
||||
s.T().Run(tc.desc, func(t *testing.T) {
|
||||
_, err := spec.OciSpec(tc.spec)
|
||||
s.Error(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestOciSpecBindMounts() {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
mounts []garden.BindMount
|
||||
expected []specs.Mount
|
||||
succeeds bool
|
||||
}{
|
||||
{
|
||||
desc: "unknown mode",
|
||||
succeeds: false,
|
||||
mounts: []garden.BindMount{
|
||||
{
|
||||
SrcPath: "/a",
|
||||
DstPath: "/b",
|
||||
Mode: 123,
|
||||
Origin: garden.BindMountOriginHost,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "unknown origin",
|
||||
succeeds: false,
|
||||
mounts: []garden.BindMount{
|
||||
{
|
||||
SrcPath: "/a",
|
||||
DstPath: "/b",
|
||||
Mode: garden.BindMountModeRO,
|
||||
Origin: 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "w/out src",
|
||||
succeeds: false,
|
||||
mounts: []garden.BindMount{
|
||||
{
|
||||
DstPath: "/b",
|
||||
Mode: garden.BindMountModeRO,
|
||||
Origin: garden.BindMountOriginHost,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "non-absolute src",
|
||||
succeeds: false,
|
||||
mounts: []garden.BindMount{
|
||||
{
|
||||
DstPath: "/b",
|
||||
Mode: garden.BindMountModeRO,
|
||||
Origin: garden.BindMountOriginHost,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "w/out dest",
|
||||
succeeds: false,
|
||||
mounts: []garden.BindMount{
|
||||
{
|
||||
SrcPath: "/a",
|
||||
Mode: garden.BindMountModeRO,
|
||||
Origin: garden.BindMountOriginHost,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "non-absolute dest",
|
||||
succeeds: false,
|
||||
mounts: []garden.BindMount{
|
||||
{
|
||||
DstPath: "/b",
|
||||
Mode: garden.BindMountModeRO,
|
||||
Origin: garden.BindMountOriginHost,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
s.T().Run(tc.desc, func(t *testing.T) {
|
||||
actual, err := spec.OciSpecBindMounts(tc.mounts)
|
||||
if !tc.succeeds {
|
||||
s.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
s.NoError(err)
|
||||
s.Equal(tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestOciNamespaces() {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
privileged bool
|
||||
expected []specs.LinuxNamespace
|
||||
}{
|
||||
{
|
||||
desc: "privileged",
|
||||
privileged: true,
|
||||
expected: spec.PrivilegedContainerNamespaces,
|
||||
},
|
||||
{
|
||||
desc: "unprivileged",
|
||||
privileged: false,
|
||||
expected: spec.UnprivilegedContainerNamespaces,
|
||||
},
|
||||
} {
|
||||
s.T().Run(tc.desc, func(t *testing.T) {
|
||||
s.Equal(tc.expected, spec.OciNamespaces(tc.privileged))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestOciCapabilities() {
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
privileged bool
|
||||
expected specs.LinuxCapabilities
|
||||
}{
|
||||
{
|
||||
desc: "privileged",
|
||||
privileged: true,
|
||||
expected: spec.PrivilegedContainerCapabilities,
|
||||
},
|
||||
{
|
||||
desc: "unprivileged",
|
||||
privileged: false,
|
||||
expected: spec.UnprivilegedContainerCapabilities,
|
||||
},
|
||||
} {
|
||||
s.T().Run(tc.desc, func(t *testing.T) {
|
||||
s.Equal(tc.expected, spec.OciCapabilities(tc.privileged))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Suite) TestContainerSpec() {
|
||||
var minimalContainerSpec = garden.ContainerSpec{
|
||||
Handle: "handle", RootFSPath: "raw:///rootfs",
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
gdn garden.ContainerSpec
|
||||
check func(*specs.Spec)
|
||||
}{
|
||||
{
|
||||
desc: "defaults",
|
||||
gdn: minimalContainerSpec,
|
||||
check: func(oci *specs.Spec) {
|
||||
s.Equal("/", oci.Process.Cwd)
|
||||
s.Equal([]string{"/tmp/gdn-init"}, oci.Process.Args)
|
||||
s.Equal(oci.Mounts, spec.AnyContainerMounts)
|
||||
|
||||
s.Equal(minimalContainerSpec.Handle, oci.Hostname)
|
||||
s.Equal(spec.AnyContainerDevices, oci.Linux.Resources.Devices)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "env",
|
||||
gdn: garden.ContainerSpec{
|
||||
Handle: "handle", RootFSPath: "raw:///rootfs",
|
||||
Env: []string{"foo=bar"},
|
||||
},
|
||||
check: func(oci *specs.Spec) {
|
||||
s.Equal([]string{"foo=bar"}, oci.Process.Env)
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "mounts",
|
||||
gdn: garden.ContainerSpec{
|
||||
Handle: "handle", RootFSPath: "raw:///rootfs",
|
||||
BindMounts: []garden.BindMount{
|
||||
{ // ro mount
|
||||
SrcPath: "/a",
|
||||
DstPath: "/b",
|
||||
Mode: garden.BindMountModeRO,
|
||||
Origin: garden.BindMountOriginHost,
|
||||
},
|
||||
{ // rw mount
|
||||
SrcPath: "/a",
|
||||
DstPath: "/b",
|
||||
Mode: garden.BindMountModeRW,
|
||||
Origin: garden.BindMountOriginHost,
|
||||
},
|
||||
},
|
||||
},
|
||||
check: func(oci *specs.Spec) {
|
||||
s.Contains(oci.Mounts, specs.Mount{
|
||||
Source: "/a",
|
||||
Destination: "/b",
|
||||
Type: "bind",
|
||||
Options: []string{"bind", "ro"},
|
||||
})
|
||||
s.Contains(oci.Mounts, specs.Mount{
|
||||
Source: "/a",
|
||||
Destination: "/b",
|
||||
Type: "bind",
|
||||
Options: []string{"bind", "rw"},
|
||||
})
|
||||
},
|
||||
},
|
||||
} {
|
||||
s.T().Run(tc.desc, func(t *testing.T) {
|
||||
actual, err := spec.OciSpec(tc.gdn)
|
||||
s.NoError(err)
|
||||
|
||||
tc.check(actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSuite(t *testing.T) {
|
||||
suite.Run(t, &Suite{
|
||||
Assertions: require.New(t),
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue