Преглед на файлове

Merge pull request #45032 from corhere/shim-opts

daemon: allow shimv2 runtimes to be configured
Sebastiaan van Stijn преди 2 години
родител
ревизия
11261594d8

+ 9 - 3
api/types/types.go

@@ -653,12 +653,18 @@ type Checkpoint struct {
 
 // Runtime describes an OCI runtime
 type Runtime struct {
-	Path string   `json:"path"`
+	// "Legacy" runtime configuration for runc-compatible runtimes.
+
+	Path string   `json:"path,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
-	// 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

+ 2 - 4
daemon/container_unix_test.go

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

+ 6 - 4
daemon/daemon.go

@@ -909,15 +909,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" {
-			rtPtr, err := d.getRuntime(config.GetDefaultRuntimeName())
+			shim, shimOpts, err = d.getRuntime(config.GetDefaultRuntimeName())
 			if err != nil {
 				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.

+ 1 - 1
daemon/daemon_unix.go

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

+ 54 - 29
daemon/runtime_unix.go

@@ -14,6 +14,7 @@ import (
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/daemon/config"
 	"github.com/docker/docker/errdefs"
+	"github.com/docker/docker/libcontainerd/shimopts"
 	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 )
@@ -31,7 +32,7 @@ func configureRuntimes(conf *config.Config) {
 	if conf.Runtimes == nil {
 		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]
 }
 
@@ -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
 }
@@ -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.
 // 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.
-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 {
-		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)
 	if rt == nil {
 		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 {
-		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"
 	"testing"
 
+	"github.com/containerd/containerd/plugin"
 	v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options"
 	"gotest.tools/v3/assert"
 	is "gotest.tools/v3/assert/cmp"
@@ -17,13 +18,110 @@ import (
 	"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) {
 	// Configured runtimes can have any arbitrary name, including names
 	// which would not be allowed as implicit runtime names. Explicit takes
 	// precedence over implicit.
-	const configuredRtName = "my/custom.shim.v1"
+	const configuredRtName = "my/custom.runtime.v1"
 	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()
 	assert.NilError(t, err)
 
@@ -31,7 +129,11 @@ func TestGetRuntime(t *testing.T) {
 	d.configStore.Root = t.TempDir()
 	assert.Assert(t, os.Mkdir(filepath.Join(d.configStore.Root, "runtimes"), 0700))
 	d.configStore.Runtimes = map[string]types.Runtime{
-		configuredRtName: configuredRuntime,
+		configuredRtName:         configuredRuntime,
+		rtWithArgsName:           rtWithArgs,
+		shimWithOptsName:         shimWithOpts,
+		shimAliasName:            shimAlias,
+		configuredShimByPathName: configuredShimByPath,
 	}
 	configureRuntimes(d.configStore)
 	assert.Assert(t, d.loadRuntimes())
@@ -39,36 +141,33 @@ func TestGetRuntime(t *testing.T) {
 	stockRuntime, ok := d.configStore.Runtimes[config.StockRuntimeName]
 	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
-	wantConfigdRuntime := configuredRuntime
-	wantConfigdRuntime.Shim = &types.ShimConfig{
-		Binary: stockRuntime.Shim.Binary,
-		Opts:   &configdOpts,
-	}
 
 	for _, tt := range []struct {
 		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
 			// 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
 			// 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",
@@ -91,16 +190,45 @@ func TestGetRuntime(t *testing.T) {
 			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
 		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)
 			} else {
 				assert.Check(t, errdefs.IsInvalidParameter(err))

+ 3 - 4
daemon/runtime_windows.go

@@ -1,10 +1,9 @@
 package daemon
 
 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)
 	}
 
-	rt, err := daemon.getRuntime(container.HostConfig.Runtime)
+	binary, opts, err := daemon.getRuntime(container.HostConfig.Runtime)
 	if err != nil {
 		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/cio"
-	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/libcontainerd"
 	libcontainerdtypes "github.com/docker/docker/libcontainerd/types"
@@ -24,11 +23,12 @@ type ExitHandler interface {
 }
 
 // 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{
 		rootDir:     rootDir,
 		exitHandler: exitHandler,
-		runtime:     runtime,
+		shim:        shim,
+		shimOpts:    shimOpts,
 		plugins:     make(map[string]*c8dPlugin),
 	}
 
@@ -45,7 +45,8 @@ type Executor struct {
 	rootDir     string
 	client      libcontainerdtypes.Client
 	exitHandler ExitHandler
-	runtime     types.Runtime
+	shim        string
+	shimOpts    interface{}
 
 	mu      sync.Mutex // Guards plugins map
 	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 {
 	ctx := context.Background()
 	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 {
 		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/dialer
 github.com/containerd/containerd/pkg/kmutex
+github.com/containerd/containerd/pkg/runtimeoptions/v1
 github.com/containerd/containerd/pkg/seccomp
 github.com/containerd/containerd/pkg/shutdown
 github.com/containerd/containerd/pkg/snapshotters