Bladeren bron

daemon: allow shimv2 runtimes to be configured

Kubernetes only permits RuntimeClass values which are valid lowercase
RFC 1123 labels, which disallows the period character. This prevents
cri-dockerd from being able to support configuring alternative shimv2
runtimes for a pod as shimv2 runtime names must contain at least one
period character. Add support for configuring named shimv2 runtimes in
daemon.json so that runtime names can be aliased to
Kubernetes-compatible names.

Allow options to be set on shimv2 runtimes in daemon.json.

The names of the new daemon runtime config fields have been selected to
correspond with the equivalent field names in cri-containerd's
configuration so that users can more easily follow documentation from
the runtime vendor written for cri-containerd and apply it to
daemon.json.

Signed-off-by: Cory Snider <csnider@mirantis.com>
Cory Snider 2 jaren geleden
bovenliggende
commit
b0eed5ade6

+ 9 - 3
api/types/types.go

@@ -653,12 +653,18 @@ type Checkpoint struct {
 
 
 // Runtime describes an OCI runtime
 // Runtime describes an OCI runtime
 type Runtime struct {
 type Runtime struct {
-	Path string   `json:"path"`
+	// "Legacy" runtime configuration for runc-compatible runtimes.
+
+	Path string   `json:"path,omitempty"`
 	Args []string `json:"runtimeArgs,omitempty"`
 	Args []string `json:"runtimeArgs,omitempty"`
 
 
+	// Shimv2 runtime configuration. Mutually exclusive with the legacy config above.
+
+	Type    string                 `json:"runtimeType,omitempty"`
+	Options map[string]interface{} `json:"options,omitempty"`
+
 	// This is exposed here only for internal use
 	// This is exposed here only for internal use
-	// It is not currently supported to specify custom shim configs
-	Shim *ShimConfig `json:"-"`
+	ShimConfig *ShimConfig `json:"-"`
 }
 }
 
 
 // ShimConfig is used by runtime to configure containerd shims
 // ShimConfig is used by runtime to configure containerd shims

+ 2 - 4
daemon/container_unix_test.go

@@ -6,7 +6,6 @@ package daemon
 import (
 import (
 	"testing"
 	"testing"
 
 
-	"github.com/docker/docker/api/types"
 	containertypes "github.com/docker/docker/api/types/container"
 	containertypes "github.com/docker/docker/api/types/container"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/go-connections/nat"
 	"github.com/docker/go-connections/nat"
@@ -33,9 +32,8 @@ func TestContainerWarningHostAndPublishPorts(t *testing.T) {
 			NetworkMode:  "host",
 			NetworkMode:  "host",
 			PortBindings: tc.ports,
 			PortBindings: tc.ports,
 		}
 		}
-		cs := &config.Config{
-			Runtimes: map[string]types.Runtime{"runc": {}},
-		}
+		cs := &config.Config{}
+		configureRuntimes(cs)
 		d := &Daemon{configStore: cs}
 		d := &Daemon{configStore: cs}
 		wrns, err := d.verifyContainerSettings(hostConfig, &containertypes.Config{}, false)
 		wrns, err := d.verifyContainerSettings(hostConfig, &containertypes.Config{}, false)
 		assert.NilError(t, err)
 		assert.NilError(t, err)

+ 6 - 4
daemon/daemon.go

@@ -908,15 +908,17 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S
 			}
 			}
 		}
 		}
 
 
-		var rt types.Runtime
+		var (
+			shim     string
+			shimOpts interface{}
+		)
 		if runtime.GOOS != "windows" {
 		if runtime.GOOS != "windows" {
-			rtPtr, err := d.getRuntime(config.GetDefaultRuntimeName())
+			shim, shimOpts, err = d.getRuntime(config.GetDefaultRuntimeName())
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}
-			rt = *rtPtr
 		}
 		}
-		return pluginexec.New(ctx, getPluginExecRoot(config), pluginCli, config.ContainerdPluginNamespace, m, rt)
+		return pluginexec.New(ctx, getPluginExecRoot(config), pluginCli, config.ContainerdPluginNamespace, m, shim, shimOpts)
 	}
 	}
 
 
 	// Plugin system initialization should happen before restore. Do not change order.
 	// Plugin system initialization should happen before restore. Do not change order.

+ 1 - 1
daemon/daemon_unix.go

@@ -705,7 +705,7 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *containertypes.
 		hostConfig.Runtime = daemon.configStore.GetDefaultRuntimeName()
 		hostConfig.Runtime = daemon.configStore.GetDefaultRuntimeName()
 	}
 	}
 
 
-	if _, err := daemon.getRuntime(hostConfig.Runtime); err != nil {
+	if _, _, err := daemon.getRuntime(hostConfig.Runtime); err != nil {
 		return warnings, err
 		return warnings, err
 	}
 	}
 
 

+ 54 - 29
daemon/runtime_unix.go

@@ -14,6 +14,7 @@ import (
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
+	"github.com/docker/docker/libcontainerd/shimopts"
 	"github.com/pkg/errors"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
@@ -31,7 +32,7 @@ func configureRuntimes(conf *config.Config) {
 	if conf.Runtimes == nil {
 	if conf.Runtimes == nil {
 		conf.Runtimes = make(map[string]types.Runtime)
 		conf.Runtimes = make(map[string]types.Runtime)
 	}
 	}
-	conf.Runtimes[config.LinuxV2RuntimeName] = types.Runtime{Path: defaultRuntimeName, Shim: defaultV2ShimConfig(conf, defaultRuntimeName)}
+	conf.Runtimes[config.LinuxV2RuntimeName] = types.Runtime{Path: defaultRuntimeName, ShimConfig: defaultV2ShimConfig(conf, defaultRuntimeName)}
 	conf.Runtimes[config.StockRuntimeName] = conf.Runtimes[config.LinuxV2RuntimeName]
 	conf.Runtimes[config.StockRuntimeName] = conf.Runtimes[config.LinuxV2RuntimeName]
 }
 }
 
 
@@ -88,17 +89,42 @@ func (daemon *Daemon) initRuntimes(runtimes map[string]types.Runtime) (err error
 		}
 		}
 	}()
 	}()
 
 
-	for name, rt := range runtimes {
-		if len(rt.Args) > 0 {
-			script := filepath.Join(tmpDir, name)
-			content := fmt.Sprintf("#!/bin/sh\n%s %s $@\n", rt.Path, strings.Join(rt.Args, " "))
-			if err := os.WriteFile(script, []byte(content), 0700); err != nil {
-				return err
-			}
+	for name := range runtimes {
+		rt := runtimes[name]
+		if rt.Path == "" && rt.Type == "" {
+			return errors.Errorf("runtime %s: either a runtimeType or a path must be configured", name)
 		}
 		}
-		if rt.Shim == nil {
-			rt.Shim = defaultV2ShimConfig(daemon.configStore, rt.Path)
+		if rt.Path != "" {
+			if rt.Type != "" {
+				return errors.Errorf("runtime %s: cannot configure both path and runtimeType for the same runtime", name)
+			}
+			if len(rt.Options) > 0 {
+				return errors.Errorf("runtime %s: options cannot be used with a path runtime", name)
+			}
+
+			if len(rt.Args) > 0 {
+				script := filepath.Join(tmpDir, name)
+				content := fmt.Sprintf("#!/bin/sh\n%s %s $@\n", rt.Path, strings.Join(rt.Args, " "))
+				if err := os.WriteFile(script, []byte(content), 0700); err != nil {
+					return err
+				}
+			}
+			rt.ShimConfig = defaultV2ShimConfig(daemon.configStore, daemon.rewriteRuntimePath(name, rt.Path, rt.Args))
+		} else {
+			if len(rt.Args) > 0 {
+				return errors.Errorf("runtime %s: args cannot be used with a runtimeType runtime", name)
+			}
+			// Unlike implicit runtimes, there is no restriction on configuring a shim by path.
+			rt.ShimConfig = &types.ShimConfig{Binary: rt.Type}
+			if len(rt.Options) > 0 {
+				// It has to be a pointer type or there'll be a panic in containerd/typeurl when we try to start the container.
+				rt.ShimConfig.Opts, err = shimopts.Generate(rt.Type, rt.Options)
+				if err != nil {
+					return errors.Wrapf(err, "runtime %v", name)
+				}
+			}
 		}
 		}
+		runtimes[name] = rt
 	}
 	}
 	return nil
 	return nil
 }
 }
@@ -106,40 +132,39 @@ func (daemon *Daemon) initRuntimes(runtimes map[string]types.Runtime) (err error
 // rewriteRuntimePath is used for runtimes which have custom arguments supplied.
 // rewriteRuntimePath is used for runtimes which have custom arguments supplied.
 // This is needed because the containerd API only calls the OCI runtime binary, there is no options for extra arguments.
 // This is needed because the containerd API only calls the OCI runtime binary, there is no options for extra arguments.
 // To support this case, the daemon wraps the specified runtime in a script that passes through those arguments.
 // To support this case, the daemon wraps the specified runtime in a script that passes through those arguments.
-func (daemon *Daemon) rewriteRuntimePath(name, p string, args []string) (string, error) {
+func (daemon *Daemon) rewriteRuntimePath(name, p string, args []string) string {
 	if len(args) == 0 {
 	if len(args) == 0 {
-		return p, nil
+		return p
 	}
 	}
 
 
-	// Check that the runtime path actually exists here so that we can return a well known error.
-	if _, err := exec.LookPath(p); err != nil {
-		return "", errors.Wrap(err, "error while looking up the specified runtime path")
-	}
-
-	return filepath.Join(daemon.configStore.Root, "runtimes", name), nil
+	return filepath.Join(daemon.configStore.Root, "runtimes", name)
 }
 }
 
 
-func (daemon *Daemon) getRuntime(name string) (*types.Runtime, error) {
+func (daemon *Daemon) getRuntime(name string) (shim string, opts interface{}, err error) {
 	rt := daemon.configStore.GetRuntime(name)
 	rt := daemon.configStore.GetRuntime(name)
 	if rt == nil {
 	if rt == nil {
 		if !config.IsPermissibleC8dRuntimeName(name) {
 		if !config.IsPermissibleC8dRuntimeName(name) {
-			return nil, errdefs.InvalidParameter(errors.Errorf("unknown or invalid runtime name: %s", name))
+			return "", nil, errdefs.InvalidParameter(errors.Errorf("unknown or invalid runtime name: %s", name))
 		}
 		}
-		return &types.Runtime{Shim: &types.ShimConfig{Binary: name}}, nil
+		return name, nil, nil
 	}
 	}
 
 
 	if len(rt.Args) > 0 {
 	if len(rt.Args) > 0 {
-		p, err := daemon.rewriteRuntimePath(name, rt.Path, rt.Args)
-		if err != nil {
-			return nil, err
+		// Check that the path of the runtime which the script wraps actually exists so
+		// that we can return a well known error which references the configured path
+		// instead of the wrapper script's.
+		if _, err := exec.LookPath(rt.Path); err != nil {
+			return "", nil, errors.Wrap(err, "error while looking up the specified runtime path")
 		}
 		}
-		rt.Path = p
-		rt.Args = nil
 	}
 	}
 
 
-	if rt.Shim == nil {
-		rt.Shim = defaultV2ShimConfig(daemon.configStore, rt.Path)
+	if rt.ShimConfig == nil {
+		// Should never happen as daemon.initRuntimes always sets
+		// ShimConfig and config reloading is synchronized.
+		err := errdefs.System(errors.Errorf("BUG: runtime %s: rt.ShimConfig == nil", name))
+		logrus.Error(err)
+		return "", nil, err
 	}
 	}
 
 
-	return rt, nil
+	return rt.ShimConfig.Binary, rt.ShimConfig.Opts, nil
 }
 }

+ 152 - 24
daemon/runtime_unix_test.go

@@ -8,6 +8,7 @@ import (
 	"path/filepath"
 	"path/filepath"
 	"testing"
 	"testing"
 
 
+	"github.com/containerd/containerd/plugin"
 	v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
 	v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
 	"gotest.tools/v3/assert"
 	"gotest.tools/v3/assert"
 	is "gotest.tools/v3/assert/cmp"
 	is "gotest.tools/v3/assert/cmp"
@@ -17,13 +18,110 @@ import (
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
 )
 )
 
 
+func TestInitRuntimes_InvalidConfigs(t *testing.T) {
+	cases := []struct {
+		name      string
+		runtime   types.Runtime
+		expectErr string
+	}{
+		{
+			name:      "Empty",
+			expectErr: "either a runtimeType or a path must be configured",
+		},
+		{
+			name:      "ArgsOnly",
+			runtime:   types.Runtime{Args: []string{"foo", "bar"}},
+			expectErr: "either a runtimeType or a path must be configured",
+		},
+		{
+			name:      "OptionsOnly",
+			runtime:   types.Runtime{Options: map[string]interface{}{"hello": "world"}},
+			expectErr: "either a runtimeType or a path must be configured",
+		},
+		{
+			name:      "PathAndType",
+			runtime:   types.Runtime{Path: "/bin/true", Type: "io.containerd.runsc.v1"},
+			expectErr: "cannot configure both",
+		},
+		{
+			name:      "PathAndOptions",
+			runtime:   types.Runtime{Path: "/bin/true", Options: map[string]interface{}{"a": "b"}},
+			expectErr: "options cannot be used with a path runtime",
+		},
+		{
+			name:      "TypeAndArgs",
+			runtime:   types.Runtime{Type: "io.containerd.runsc.v1", Args: []string{"--version"}},
+			expectErr: "args cannot be used with a runtimeType runtime",
+		},
+		{
+			name: "PathArgsOptions",
+			runtime: types.Runtime{
+				Path:    "/bin/true",
+				Args:    []string{"--version"},
+				Options: map[string]interface{}{"hmm": 3},
+			},
+			expectErr: "options cannot be used with a path runtime",
+		},
+		{
+			name: "TypeOptionsArgs",
+			runtime: types.Runtime{
+				Type:    "io.containerd.kata.v2",
+				Options: map[string]interface{}{"a": "b"},
+				Args:    []string{"--help"},
+			},
+			expectErr: "args cannot be used with a runtimeType runtime",
+		},
+		{
+			name: "PathArgsTypeOptions",
+			runtime: types.Runtime{
+				Path:    "/bin/true",
+				Args:    []string{"foo"},
+				Type:    "io.containerd.runsc.v1",
+				Options: map[string]interface{}{"a": "b"},
+			},
+			expectErr: "cannot configure both",
+		},
+	}
+
+	for _, tt := range cases {
+		t.Run(tt.name, func(t *testing.T) {
+			cfg, err := config.New()
+			assert.NilError(t, err)
+			d := &Daemon{configStore: cfg}
+			d.configStore.Root = t.TempDir()
+			assert.Assert(t, os.Mkdir(filepath.Join(d.configStore.Root, "runtimes"), 0700))
+
+			err = d.initRuntimes(map[string]types.Runtime{"myruntime": tt.runtime})
+			assert.Check(t, is.ErrorContains(err, tt.expectErr))
+		})
+	}
+}
+
 func TestGetRuntime(t *testing.T) {
 func TestGetRuntime(t *testing.T) {
 	// Configured runtimes can have any arbitrary name, including names
 	// Configured runtimes can have any arbitrary name, including names
 	// which would not be allowed as implicit runtime names. Explicit takes
 	// which would not be allowed as implicit runtime names. Explicit takes
 	// precedence over implicit.
 	// precedence over implicit.
-	const configuredRtName = "my/custom.shim.v1"
+	const configuredRtName = "my/custom.runtime.v1"
 	configuredRuntime := types.Runtime{Path: "/bin/true"}
 	configuredRuntime := types.Runtime{Path: "/bin/true"}
 
 
+	const rtWithArgsName = "withargs"
+	rtWithArgs := types.Runtime{
+		Path: "/bin/false",
+		Args: []string{"--version"},
+	}
+
+	const shimWithOptsName = "shimwithopts"
+	shimWithOpts := types.Runtime{
+		Type:    plugin.RuntimeRuncV2,
+		Options: map[string]interface{}{"IoUid": 42},
+	}
+
+	const shimAliasName = "wasmedge"
+	shimAlias := types.Runtime{Type: "io.containerd.wasmedge.v1"}
+
+	const configuredShimByPathName = "shimwithpath"
+	configuredShimByPath := types.Runtime{Type: "/path/to/my/shim"}
+
 	cfg, err := config.New()
 	cfg, err := config.New()
 	assert.NilError(t, err)
 	assert.NilError(t, err)
 
 
@@ -31,7 +129,11 @@ func TestGetRuntime(t *testing.T) {
 	d.configStore.Root = t.TempDir()
 	d.configStore.Root = t.TempDir()
 	assert.Assert(t, os.Mkdir(filepath.Join(d.configStore.Root, "runtimes"), 0700))
 	assert.Assert(t, os.Mkdir(filepath.Join(d.configStore.Root, "runtimes"), 0700))
 	d.configStore.Runtimes = map[string]types.Runtime{
 	d.configStore.Runtimes = map[string]types.Runtime{
-		configuredRtName: configuredRuntime,
+		configuredRtName:         configuredRuntime,
+		rtWithArgsName:           rtWithArgs,
+		shimWithOptsName:         shimWithOpts,
+		shimAliasName:            shimAlias,
+		configuredShimByPathName: configuredShimByPath,
 	}
 	}
 	configureRuntimes(d.configStore)
 	configureRuntimes(d.configStore)
 	assert.Assert(t, d.loadRuntimes())
 	assert.Assert(t, d.loadRuntimes())
@@ -39,36 +141,33 @@ func TestGetRuntime(t *testing.T) {
 	stockRuntime, ok := d.configStore.Runtimes[config.StockRuntimeName]
 	stockRuntime, ok := d.configStore.Runtimes[config.StockRuntimeName]
 	assert.Assert(t, ok, "stock runtime could not be found (test needs to be updated)")
 	assert.Assert(t, ok, "stock runtime could not be found (test needs to be updated)")
 
 
-	configdOpts := *stockRuntime.Shim.Opts.(*v2runcoptions.Options)
+	configdOpts := *stockRuntime.ShimConfig.Opts.(*v2runcoptions.Options)
 	configdOpts.BinaryName = configuredRuntime.Path
 	configdOpts.BinaryName = configuredRuntime.Path
-	wantConfigdRuntime := configuredRuntime
-	wantConfigdRuntime.Shim = &types.ShimConfig{
-		Binary: stockRuntime.Shim.Binary,
-		Opts:   &configdOpts,
-	}
 
 
 	for _, tt := range []struct {
 	for _, tt := range []struct {
 		name, runtime string
 		name, runtime string
-		want          *types.Runtime
+		wantShim      string
+		wantOpts      interface{}
 	}{
 	}{
 		{
 		{
-			name:    "StockRuntime",
-			runtime: config.StockRuntimeName,
-			want:    &stockRuntime,
+			name:     "StockRuntime",
+			runtime:  config.StockRuntimeName,
+			wantShim: stockRuntime.ShimConfig.Binary,
+			wantOpts: stockRuntime.ShimConfig.Opts,
 		},
 		},
 		{
 		{
-			name:    "ShimName",
-			runtime: "io.containerd.my-shim.v42",
-			want:    &types.Runtime{Shim: &types.ShimConfig{Binary: "io.containerd.my-shim.v42"}},
+			name:     "ShimName",
+			runtime:  "io.containerd.my-shim.v42",
+			wantShim: "io.containerd.my-shim.v42",
 		},
 		},
 		{
 		{
 			// containerd is pretty loose about the format of runtime names. Perhaps too
 			// containerd is pretty loose about the format of runtime names. Perhaps too
 			// loose. The only requirements are that the name contain a dot and (depending
 			// loose. The only requirements are that the name contain a dot and (depending
 			// on the containerd version) not start with a dot. It does not enforce any
 			// on the containerd version) not start with a dot. It does not enforce any
 			// particular format of the dot-delimited components of the name.
 			// particular format of the dot-delimited components of the name.
-			name:    "VersionlessShimName",
-			runtime: "io.containerd.my-shim",
-			want:    &types.Runtime{Shim: &types.ShimConfig{Binary: "io.containerd.my-shim"}},
+			name:     "VersionlessShimName",
+			runtime:  "io.containerd.my-shim",
+			wantShim: "io.containerd.my-shim",
 		},
 		},
 		{
 		{
 			name:    "IllformedShimName",
 			name:    "IllformedShimName",
@@ -91,16 +190,45 @@ func TestGetRuntime(t *testing.T) {
 			runtime: "my/io.containerd.runc.v2",
 			runtime: "my/io.containerd.runc.v2",
 		},
 		},
 		{
 		{
-			name:    "ConfiguredRuntime",
-			runtime: configuredRtName,
-			want:    &wantConfigdRuntime,
+			name:     "ConfiguredRuntime",
+			runtime:  configuredRtName,
+			wantShim: stockRuntime.ShimConfig.Binary,
+			wantOpts: &configdOpts,
+		},
+		{
+			name:     "RuntimeWithArgs",
+			runtime:  rtWithArgsName,
+			wantShim: stockRuntime.ShimConfig.Binary,
+			wantOpts: defaultV2ShimConfig(
+				d.configStore,
+				d.rewriteRuntimePath(
+					rtWithArgsName,
+					rtWithArgs.Path,
+					rtWithArgs.Args)).Opts,
+		},
+		{
+			name:     "ShimWithOpts",
+			runtime:  shimWithOptsName,
+			wantShim: shimWithOpts.Type,
+			wantOpts: &v2runcoptions.Options{IoUid: 42},
+		},
+		{
+			name:     "ShimAlias",
+			runtime:  shimAliasName,
+			wantShim: shimAlias.Type,
+		},
+		{
+			name:     "ConfiguredShimByPath",
+			runtime:  configuredShimByPathName,
+			wantShim: configuredShimByPath.Type,
 		},
 		},
 	} {
 	} {
 		tt := tt
 		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 		t.Run(tt.name, func(t *testing.T) {
-			got, err := d.getRuntime(tt.runtime)
-			assert.Check(t, is.DeepEqual(got, tt.want))
-			if tt.want != nil {
+			gotShim, gotOpts, err := d.getRuntime(tt.runtime)
+			assert.Check(t, is.Equal(gotShim, tt.wantShim))
+			assert.Check(t, is.DeepEqual(gotOpts, tt.wantOpts))
+			if tt.wantShim != "" {
 				assert.Check(t, err)
 				assert.Check(t, err)
 			} else {
 			} else {
 				assert.Check(t, errdefs.IsInvalidParameter(err))
 				assert.Check(t, errdefs.IsInvalidParameter(err))

+ 3 - 4
daemon/runtime_windows.go

@@ -1,10 +1,9 @@
 package daemon
 package daemon
 
 
 import (
 import (
-	"github.com/docker/docker/api/types"
-	"github.com/pkg/errors"
+	"errors"
 )
 )
 
 
-func (daemon *Daemon) getRuntime(name string) (*types.Runtime, error) {
-	return nil, errors.New("not implemented")
+func (daemon *Daemon) getRuntime(name string) (shim string, opts interface{}, err error) {
+	return "", nil, errors.New("not implemented")
 }
 }

+ 2 - 2
daemon/start_unix.go

@@ -15,10 +15,10 @@ func (daemon *Daemon) getLibcontainerdCreateOptions(container *container.Contain
 		container.CheckpointTo(daemon.containersReplica)
 		container.CheckpointTo(daemon.containersReplica)
 	}
 	}
 
 
-	rt, err := daemon.getRuntime(container.HostConfig.Runtime)
+	binary, opts, err := daemon.getRuntime(container.HostConfig.Runtime)
 	if err != nil {
 	if err != nil {
 		return "", nil, setExitCodeFromError(container.SetExitCode, err)
 		return "", nil, setExitCodeFromError(container.SetExitCode, err)
 	}
 	}
 
 
-	return rt.Shim.Binary, rt.Shim.Opts, nil
+	return binary, opts, nil
 }
 }

+ 38 - 0
libcontainerd/shimopts/convert.go

@@ -0,0 +1,38 @@
+package shimopts
+
+import (
+	runhcsoptions "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
+	runtimeoptions "github.com/containerd/containerd/pkg/runtimeoptions/v1"
+	"github.com/containerd/containerd/plugin"
+	runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
+	"github.com/pelletier/go-toml"
+)
+
+// Generate converts opts into a runtime options value for the runtimeType which
+// can be passed into containerd.
+func Generate(runtimeType string, opts map[string]interface{}) (interface{}, error) {
+	// This is horrible, but we have no other choice. The containerd client
+	// can only handle options values which can be marshaled into a
+	// typeurl.Any. And we're in good company: cri-containerd handles shim
+	// options in the same way.
+	var out interface{}
+	switch runtimeType {
+	case plugin.RuntimeRuncV1, plugin.RuntimeRuncV2:
+		out = &runcoptions.Options{}
+	case "io.containerd.runhcs.v1":
+		out = &runhcsoptions.Options{}
+	default:
+		out = &runtimeoptions.Options{}
+	}
+
+	// We can't use mergo.Map as it is too strict about type-assignability
+	// with numeric types.
+	tree, err := toml.TreeFromMap(opts)
+	if err != nil {
+		return nil, err
+	}
+	if err := tree.Unmarshal(out); err != nil {
+		return nil, err
+	}
+	return out, nil
+}

+ 6 - 5
plugin/executor/containerd/containerd.go

@@ -9,7 +9,6 @@ import (
 
 
 	"github.com/containerd/containerd"
 	"github.com/containerd/containerd"
 	"github.com/containerd/containerd/cio"
 	"github.com/containerd/containerd/cio"
-	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/libcontainerd"
 	"github.com/docker/docker/libcontainerd"
 	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
 	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
@@ -24,11 +23,12 @@ type ExitHandler interface {
 }
 }
 
 
 // New creates a new containerd plugin executor
 // New creates a new containerd plugin executor
-func New(ctx context.Context, rootDir string, cli *containerd.Client, ns string, exitHandler ExitHandler, runtime types.Runtime) (*Executor, error) {
+func New(ctx context.Context, rootDir string, cli *containerd.Client, ns string, exitHandler ExitHandler, shim string, shimOpts interface{}) (*Executor, error) {
 	e := &Executor{
 	e := &Executor{
 		rootDir:     rootDir,
 		rootDir:     rootDir,
 		exitHandler: exitHandler,
 		exitHandler: exitHandler,
-		runtime:     runtime,
+		shim:        shim,
+		shimOpts:    shimOpts,
 		plugins:     make(map[string]*c8dPlugin),
 		plugins:     make(map[string]*c8dPlugin),
 	}
 	}
 
 
@@ -45,7 +45,8 @@ type Executor struct {
 	rootDir     string
 	rootDir     string
 	client      libcontainerdtypes.Client
 	client      libcontainerdtypes.Client
 	exitHandler ExitHandler
 	exitHandler ExitHandler
-	runtime     types.Runtime
+	shim        string
+	shimOpts    interface{}
 
 
 	mu      sync.Mutex // Guards plugins map
 	mu      sync.Mutex // Guards plugins map
 	plugins map[string]*c8dPlugin
 	plugins map[string]*c8dPlugin
@@ -75,7 +76,7 @@ func (p c8dPlugin) deleteTaskAndContainer(ctx context.Context) {
 func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
 func (e *Executor) Create(id string, spec specs.Spec, stdout, stderr io.WriteCloser) error {
 	ctx := context.Background()
 	ctx := context.Background()
 	log := logrus.WithField("plugin", id)
 	log := logrus.WithField("plugin", id)
-	ctr, err := libcontainerd.ReplaceContainer(ctx, e.client, id, &spec, e.runtime.Shim.Binary, e.runtime.Shim.Opts)
+	ctr, err := libcontainerd.ReplaceContainer(ctx, e.client, id, &spec, e.shim, e.shimOpts)
 	if err != nil {
 	if err != nil {
 		return errors.Wrap(err, "error creating containerd container for plugin")
 		return errors.Wrap(err, "error creating containerd container for plugin")
 	}
 	}

+ 397 - 0
vendor/github.com/containerd/containerd/pkg/runtimeoptions/v1/api.pb.go

@@ -0,0 +1,397 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: github.com/containerd/containerd/pkg/runtimeoptions/v1/api.proto
+
+package runtimeoptions_v1
+
+import (
+	fmt "fmt"
+	_ "github.com/gogo/protobuf/gogoproto"
+	proto "github.com/gogo/protobuf/proto"
+	io "io"
+	math "math"
+	math_bits "math/bits"
+	reflect "reflect"
+	strings "strings"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
+
+type Options struct {
+	// TypeUrl specifies the type of the content inside the config file.
+	TypeUrl string `protobuf:"bytes,1,opt,name=type_url,json=typeUrl,proto3" json:"type_url,omitempty"`
+	// ConfigPath specifies the filesystem location of the config file
+	// used by the runtime.
+	ConfigPath           string   `protobuf:"bytes,2,opt,name=config_path,json=configPath,proto3" json:"config_path,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *Options) Reset()      { *m = Options{} }
+func (*Options) ProtoMessage() {}
+func (*Options) Descriptor() ([]byte, []int) {
+	return fileDescriptor_7700dd27e3487aa6, []int{0}
+}
+func (m *Options) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *Options) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_Options.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *Options) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_Options.Merge(m, src)
+}
+func (m *Options) XXX_Size() int {
+	return m.Size()
+}
+func (m *Options) XXX_DiscardUnknown() {
+	xxx_messageInfo_Options.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Options proto.InternalMessageInfo
+
+func (m *Options) GetTypeUrl() string {
+	if m != nil {
+		return m.TypeUrl
+	}
+	return ""
+}
+
+func (m *Options) GetConfigPath() string {
+	if m != nil {
+		return m.ConfigPath
+	}
+	return ""
+}
+
+func init() {
+	proto.RegisterType((*Options)(nil), "runtimeoptions.v1.Options")
+}
+
+func init() {
+	proto.RegisterFile("github.com/containerd/containerd/pkg/runtimeoptions/v1/api.proto", fileDescriptor_7700dd27e3487aa6)
+}
+
+var fileDescriptor_7700dd27e3487aa6 = []byte{
+	// 214 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x72, 0x48, 0xcf, 0x2c, 0xc9,
+	0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xce, 0xcf, 0x2b, 0x49, 0xcc, 0xcc, 0x4b, 0x2d,
+	0x4a, 0x41, 0x66, 0x16, 0x64, 0xa7, 0xeb, 0x17, 0x95, 0xe6, 0x95, 0x64, 0xe6, 0xa6, 0xe6, 0x17,
+	0x94, 0x64, 0xe6, 0xe7, 0x15, 0xeb, 0x97, 0x19, 0xea, 0x27, 0x16, 0x64, 0xea, 0x15, 0x14, 0xe5,
+	0x97, 0xe4, 0x0b, 0x09, 0xa2, 0x4a, 0xea, 0x95, 0x19, 0x4a, 0xe9, 0x22, 0x19, 0x9a, 0x9e, 0x9f,
+	0x9e, 0xaf, 0x0f, 0x56, 0x99, 0x54, 0x9a, 0x06, 0xe6, 0x81, 0x39, 0x60, 0x16, 0xc4, 0x04, 0x25,
+	0x57, 0x2e, 0x76, 0x7f, 0x88, 0x66, 0x21, 0x49, 0x2e, 0x8e, 0x92, 0xca, 0x82, 0xd4, 0xf8, 0xd2,
+	0xa2, 0x1c, 0x09, 0x46, 0x05, 0x46, 0x0d, 0xce, 0x20, 0x76, 0x10, 0x3f, 0xb4, 0x28, 0x47, 0x48,
+	0x9e, 0x8b, 0x3b, 0x39, 0x3f, 0x2f, 0x2d, 0x33, 0x3d, 0xbe, 0x20, 0xb1, 0x24, 0x43, 0x82, 0x09,
+	0x2c, 0xcb, 0x05, 0x11, 0x0a, 0x48, 0x2c, 0xc9, 0x70, 0x4a, 0x3b, 0xf1, 0x50, 0x8e, 0xf1, 0xc6,
+	0x43, 0x39, 0x86, 0x86, 0x47, 0x72, 0x8c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8,
+	0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x51, 0x1e, 0xe4, 0x79, 0xd4, 0x1a, 0x55, 0x24,
+	0xbe, 0xcc, 0x30, 0x89, 0x0d, 0xec, 0x6a, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x91, 0x3c,
+	0x3e, 0x79, 0x3b, 0x01, 0x00, 0x00,
+}
+
+func (m *Options) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Options) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *Options) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if len(m.ConfigPath) > 0 {
+		i -= len(m.ConfigPath)
+		copy(dAtA[i:], m.ConfigPath)
+		i = encodeVarintApi(dAtA, i, uint64(len(m.ConfigPath)))
+		i--
+		dAtA[i] = 0x12
+	}
+	if len(m.TypeUrl) > 0 {
+		i -= len(m.TypeUrl)
+		copy(dAtA[i:], m.TypeUrl)
+		i = encodeVarintApi(dAtA, i, uint64(len(m.TypeUrl)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintApi(dAtA []byte, offset int, v uint64) int {
+	offset -= sovApi(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *Options) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.TypeUrl)
+	if l > 0 {
+		n += 1 + l + sovApi(uint64(l))
+	}
+	l = len(m.ConfigPath)
+	if l > 0 {
+		n += 1 + l + sovApi(uint64(l))
+	}
+	return n
+}
+
+func sovApi(x uint64) (n int) {
+	return (math_bits.Len64(x|1) + 6) / 7
+}
+func sozApi(x uint64) (n int) {
+	return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (this *Options) String() string {
+	if this == nil {
+		return "nil"
+	}
+	s := strings.Join([]string{`&Options{`,
+		`TypeUrl:` + fmt.Sprintf("%v", this.TypeUrl) + `,`,
+		`ConfigPath:` + fmt.Sprintf("%v", this.ConfigPath) + `,`,
+		`}`,
+	}, "")
+	return s
+}
+func valueToStringApi(v interface{}) string {
+	rv := reflect.ValueOf(v)
+	if rv.IsNil() {
+		return "nil"
+	}
+	pv := reflect.Indirect(rv).Interface()
+	return fmt.Sprintf("*%v", pv)
+}
+func (m *Options) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowApi
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Options: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Options: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field TypeUrl", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApi
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApi
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApi
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.TypeUrl = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ConfigPath", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApi
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApi
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApi
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ConfigPath = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipApi(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthApi
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipApi(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	depth := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowApi
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowApi
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+		case 1:
+			iNdEx += 8
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowApi
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if length < 0 {
+				return 0, ErrInvalidLengthApi
+			}
+			iNdEx += length
+		case 3:
+			depth++
+		case 4:
+			if depth == 0 {
+				return 0, ErrUnexpectedEndOfGroupApi
+			}
+			depth--
+		case 5:
+			iNdEx += 4
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+		if iNdEx < 0 {
+			return 0, ErrInvalidLengthApi
+		}
+		if depth == 0 {
+			return iNdEx, nil
+		}
+	}
+	return 0, io.ErrUnexpectedEOF
+}
+
+var (
+	ErrInvalidLengthApi        = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowApi          = fmt.Errorf("proto: integer overflow")
+	ErrUnexpectedEndOfGroupApi = fmt.Errorf("proto: unexpected end of group")
+)

+ 25 - 0
vendor/github.com/containerd/containerd/pkg/runtimeoptions/v1/api.proto

@@ -0,0 +1,25 @@
+// To regenerate api.pb.go run `make protos`
+syntax = "proto3";
+
+package runtimeoptions.v1;
+
+import "github.com/gogo/protobuf/gogoproto/gogo.proto";
+
+option (gogoproto.goproto_stringer_all) = false;
+option (gogoproto.stringer_all) =  true;
+option (gogoproto.goproto_getters_all) = true;
+option (gogoproto.marshaler_all) = true;
+option (gogoproto.sizer_all) = true;
+option (gogoproto.unmarshaler_all) = true;
+option (gogoproto.goproto_unrecognized_all) = false;
+
+
+option go_package = "github.com/containerd/containerd/pkg/runtimeoptions/v1;runtimeoptions_v1";
+
+message Options {
+	// TypeUrl specifies the type of the content inside the config file.
+	string type_url = 1;
+	// ConfigPath specifies the filesystem location of the config file
+	// used by the runtime.
+	string config_path = 2;
+}

+ 17 - 0
vendor/github.com/containerd/containerd/pkg/runtimeoptions/v1/doc.go

@@ -0,0 +1,17 @@
+/*
+   Copyright The containerd Authors.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package runtimeoptions_v1 //nolint

+ 1 - 0
vendor/modules.txt

@@ -259,6 +259,7 @@ github.com/containerd/containerd/pkg/apparmor
 github.com/containerd/containerd/pkg/cap
 github.com/containerd/containerd/pkg/cap
 github.com/containerd/containerd/pkg/dialer
 github.com/containerd/containerd/pkg/dialer
 github.com/containerd/containerd/pkg/kmutex
 github.com/containerd/containerd/pkg/kmutex
+github.com/containerd/containerd/pkg/runtimeoptions/v1
 github.com/containerd/containerd/pkg/seccomp
 github.com/containerd/containerd/pkg/seccomp
 github.com/containerd/containerd/pkg/shutdown
 github.com/containerd/containerd/pkg/shutdown
 github.com/containerd/containerd/pkg/ttrpcutil
 github.com/containerd/containerd/pkg/ttrpcutil