Merge pull request #805 from alam0rt/add-v3-containerd-config

Add support for containerd v3 configs
This commit is contained in:
Evan Lezar 2025-01-15 10:46:30 +01:00 committed by GitHub
commit 11acbe7ca2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 362 additions and 185 deletions

View File

@ -18,12 +18,13 @@ package engine
// Interface defines the API for a runtime config updater. // Interface defines the API for a runtime config updater.
type Interface interface { type Interface interface {
DefaultRuntime() string
AddRuntime(string, string, bool) error AddRuntime(string, string, bool) error
Set(string, interface{}) DefaultRuntime() string
GetRuntimeConfig(string) (RuntimeConfig, error)
RemoveRuntime(string) error RemoveRuntime(string) error
Save(string) (int64, error) Save(string) (int64, error)
GetRuntimeConfig(string) (RuntimeConfig, error) Set(string, interface{})
String() string
} }
// RuntimeConfig defines the interface to query container runtime handler configuration // RuntimeConfig defines the interface to query container runtime handler configuration

View File

@ -30,40 +30,40 @@ func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error {
} }
config := *c.Tree config := *c.Tree
config.Set("version", int64(2)) config.Set("version", c.Version)
runtimeNamesForConfig := engine.GetLowLevelRuntimes(c) runtimeNamesForConfig := engine.GetLowLevelRuntimes(c)
for _, r := range runtimeNamesForConfig { for _, r := range runtimeNamesForConfig {
options := config.GetSubtreeByPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", r}) options := config.GetSubtreeByPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", r})
if options == nil { if options == nil {
continue continue
} }
c.Logger.Debugf("using options from runtime %v: %v", r, options) c.Logger.Debugf("using options from runtime %v: %v", r, options)
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}, options.Copy()) config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name}, options.Copy())
break break
} }
if config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}) == nil { if config.GetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name}) == nil {
c.Logger.Warningf("could not infer options from runtimes %v; using defaults", runtimeNamesForConfig) c.Logger.Warningf("could not infer options from runtimes %v; using defaults", runtimeNamesForConfig)
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_type"}, c.RuntimeType) config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "runtime_type"}, c.RuntimeType)
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_root"}, "") config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "runtime_root"}, "")
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "runtime_engine"}, "") config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "runtime_engine"}, "")
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "privileged_without_host_devices"}, false) config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "privileged_without_host_devices"}, false)
} }
if len(c.ContainerAnnotations) > 0 { if len(c.ContainerAnnotations) > 0 {
annotations, err := c.getRuntimeAnnotations([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "container_annotations"}) annotations, err := c.getRuntimeAnnotations([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "container_annotations"})
if err != nil { if err != nil {
return err return err
} }
annotations = append(c.ContainerAnnotations, annotations...) annotations = append(c.ContainerAnnotations, annotations...)
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "container_annotations"}, annotations) config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "container_annotations"}, annotations)
} }
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name, "options", "BinaryName"}, path) config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name, "options", "BinaryName"}, path)
if setAsDefault { if setAsDefault {
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}, name) config.SetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"}, name)
} }
*c.Tree = config *c.Tree = config
@ -99,13 +99,13 @@ func (c *Config) getRuntimeAnnotations(path []string) ([]string, error) {
// Set sets the specified containerd option. // Set sets the specified containerd option.
func (c *Config) Set(key string, value interface{}) { func (c *Config) Set(key string, value interface{}) {
config := *c.Tree config := *c.Tree
config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", key}, value) config.SetPath([]string{"plugins", c.CRIRuntimePluginName, key}, value)
*c.Tree = config *c.Tree = config
} }
// DefaultRuntime returns the default runtime for the cri-o config // DefaultRuntime returns the default runtime for the cri-o config
func (c Config) DefaultRuntime() string { func (c Config) DefaultRuntime() string {
if runtime, ok := c.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}).(string); ok { if runtime, ok := c.GetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"}).(string); ok {
return runtime return runtime
} }
return "" return ""
@ -119,14 +119,14 @@ func (c *Config) RemoveRuntime(name string) error {
config := *c.Tree config := *c.Tree
config.DeletePath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}) config.DeletePath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name})
if runtime, ok := config.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}).(string); ok { if runtime, ok := config.GetPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"}).(string); ok {
if runtime == name { if runtime == name {
config.DeletePath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}) config.DeletePath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "default_runtime_name"})
} }
} }
runtimePath := []string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name} runtimePath := []string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name}
for i := 0; i < len(runtimePath); i++ { for i := 0; i < len(runtimePath); i++ {
if runtimes, ok := config.GetPath(runtimePath[:len(runtimePath)-i]).(*toml.Tree); ok { if runtimes, ok := config.GetPath(runtimePath[:len(runtimePath)-i]).(*toml.Tree); ok {
if len(runtimes.Keys()) == 0 { if len(runtimes.Keys()) == 0 {

View File

@ -46,7 +46,7 @@ func TestAddRuntime(t *testing.T) {
privileged_without_host_devices = false privileged_without_host_devices = false
runtime_engine = "" runtime_engine = ""
runtime_root = "" runtime_root = ""
runtime_type = "" runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test.options] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test.options]
BinaryName = "/usr/bin/test" BinaryName = "/usr/bin/test"
`, `,
@ -195,24 +195,85 @@ func TestAddRuntime(t *testing.T) {
SystemdCgroup = false SystemdCgroup = false
`, `,
}, },
{
description: "empty v3 spec is supported",
config: `
version = 3
`,
expectedConfig: `
version = 3
[plugins]
[plugins."io.containerd.cri.v1.runtime"]
[plugins."io.containerd.cri.v1.runtime".containerd]
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test]
privileged_without_host_devices = false
runtime_engine = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test.options]
BinaryName = "/usr/bin/test"
`,
expectedError: nil,
},
{
description: "v3 spec is supported",
config: `
version = 3
[plugins]
[plugins."io.containerd.cri.v1.runtime"]
[plugins."io.containerd.cri.v1.runtime".containerd]
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc]
privileged_without_host_devices = true
runtime_engine = "engine"
runtime_root = "root"
runtime_type = "type"
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc.options]
BinaryName = "/usr/bin/runc"
SystemdCgroup = true
`,
expectedConfig: `
version = 3
[plugins]
[plugins."io.containerd.cri.v1.runtime"]
[plugins."io.containerd.cri.v1.runtime".containerd]
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes]
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc]
privileged_without_host_devices = true
runtime_engine = "engine"
runtime_root = "root"
runtime_type = "type"
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc.options]
BinaryName = "/usr/bin/runc"
SystemdCgroup = true
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test]
privileged_without_host_devices = true
runtime_engine = "engine"
runtime_root = "root"
runtime_type = "type"
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.test.options]
BinaryName = "/usr/bin/test"
SystemdCgroup = true
`,
},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
cfg, err := toml.Load(tc.config)
require.NoError(t, err)
expectedConfig, err := toml.Load(tc.expectedConfig) expectedConfig, err := toml.Load(tc.expectedConfig)
require.NoError(t, err) require.NoError(t, err)
c := &Config{ c, err := New(
Logger: logger, WithLogger(logger),
Tree: cfg, WithConfigSource(toml.FromString(tc.config)),
} )
require.NoError(t, err)
err = c.AddRuntime("test", "/usr/bin/test", tc.setAsDefault) err = c.AddRuntime("test", "/usr/bin/test", tc.setAsDefault)
require.NoError(t, err) require.NoError(t, err)
require.EqualValues(t, expectedConfig.String(), cfg.String()) require.EqualValues(t, expectedConfig.String(), c.String())
}) })
} }
} }
@ -299,13 +360,13 @@ func TestGetRuntimeConfig(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
cfg, err := toml.Load(config)
c, err := New(
WithLogger(logger),
WithConfigSource(toml.FromString(config)),
)
require.NoError(t, err) require.NoError(t, err)
c := &Config{
Logger: logger,
Tree: cfg,
}
rc, err := c.GetRuntimeConfig(tc.runtime) rc, err := c.GetRuntimeConfig(tc.runtime)
require.Equal(t, tc.expectedError, err) require.Equal(t, tc.expectedError, err)
require.Equal(t, tc.expected, rc.GetBinaryPath()) require.Equal(t, tc.expected, rc.GetBinaryPath())

View File

@ -70,18 +70,20 @@ func (c *ConfigV1) AddRuntime(name string, path string, setAsDefault bool) error
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "BinaryName"}, path) config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "BinaryName"}, path)
config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "Runtime"}, path) config.SetPath([]string{"plugins", "cri", "containerd", "runtimes", name, "options", "Runtime"}, path)
if setAsDefault && c.UseDefaultRuntimeName { if setAsDefault {
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"}, name) if !c.UseLegacyConfig {
} else if setAsDefault { config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"}, name)
// Note: This is deprecated in containerd 1.4.0 and will be removed in 1.5.0 } else {
if config.GetPath([]string{"plugins", "cri", "containerd", "default_runtime"}) == nil { // Note: This is deprecated in containerd 1.4.0 and will be removed in 1.5.0
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_type"}, c.RuntimeType) if config.GetPath([]string{"plugins", "cri", "containerd", "default_runtime"}) == nil {
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_root"}, "") config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_type"}, c.RuntimeType)
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_engine"}, "") config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_root"}, "")
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "privileged_without_host_devices"}, false) config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "runtime_engine"}, "")
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "privileged_without_host_devices"}, false)
}
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "BinaryName"}, path)
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "Runtime"}, path)
} }
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "BinaryName"}, path)
config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "Runtime"}, path)
} }
*c.Tree = config *c.Tree = config

View File

@ -200,20 +200,21 @@ func TestAddRuntimeV1(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) { t.Run(tc.description, func(t *testing.T) {
cfg, err := toml.Load(tc.config)
require.NoError(t, err)
expectedConfig, err := toml.Load(tc.expectedConfig) expectedConfig, err := toml.Load(tc.expectedConfig)
require.NoError(t, err) require.NoError(t, err)
c := &ConfigV1{ c, err := New(
Logger: logger, WithLogger(logger),
Tree: cfg, WithConfigSource(toml.FromString(tc.config)),
} WithUseLegacyConfig(true),
WithRuntimeType(""),
)
require.NoError(t, err)
err = c.AddRuntime("test", "/usr/bin/test", tc.setAsDefault) err = c.AddRuntime("test", "/usr/bin/test", tc.setAsDefault)
require.NoError(t, err) require.NoError(t, err)
require.EqualValues(t, expectedConfig.String(), cfg.String()) require.EqualValues(t, expectedConfig.String(), c.String())
}) })
} }
} }

View File

@ -24,13 +24,28 @@ import (
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml" "github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
) )
const (
defaultConfigVersion = 2
defaultRuntimeType = "io.containerd.runc.v2"
)
// Config represents the containerd config // Config represents the containerd config
type Config struct { type Config struct {
*toml.Tree *toml.Tree
Logger logger.Interface Version int64
RuntimeType string Logger logger.Interface
UseDefaultRuntimeName bool RuntimeType string
ContainerAnnotations []string ContainerAnnotations []string
// UseLegacyConfig indicates whether a config file pre v1.3 should be generated.
// For version 1 config prior to containerd v1.4 the default runtime was
// specified in a containerd.runtimes.default_runtime section.
// This was deprecated in v1.4 in favour of containerd.default_runtime_name.
// Support for this section has been removed in v2.0.
UseLegacyConfig bool
// CRIRuntimePluginName represents the fully qualified name of the containerd plugin
// for the CRI runtime service. The name of this plugin was changed in v3 of the
// containerd configuration file.
CRIRuntimePluginName string
} }
var _ engine.Interface = (*Config)(nil) var _ engine.Interface = (*Config)(nil)
@ -55,7 +70,8 @@ func (c *containerdCfgRuntime) GetBinaryPath() string {
// New creates a containerd config with the specified options // New creates a containerd config with the specified options
func New(opts ...Option) (engine.Interface, error) { func New(opts ...Option) (engine.Interface, error) {
b := &builder{ b := &builder{
runtimeType: defaultRuntimeType, configVersion: defaultConfigVersion,
runtimeType: defaultRuntimeType,
} }
for _, opt := range opts { for _, opt := range opts {
opt(b) opt(b)
@ -72,55 +88,74 @@ func New(opts ...Option) (engine.Interface, error) {
return nil, fmt.Errorf("failed to load config: %v", err) return nil, fmt.Errorf("failed to load config: %v", err)
} }
configVersion, err := b.parseVersion(tomlConfig)
if err != nil {
return nil, fmt.Errorf("failed to parse config version: %w", err)
}
b.logger.Infof("Using config version %v", configVersion)
criRuntimePluginName, err := b.criRuntimePluginName(configVersion)
if err != nil {
return nil, fmt.Errorf("failed to get CRI runtime plugin name: %w", err)
}
b.logger.Infof("Using CRI runtime plugin name %q", criRuntimePluginName)
cfg := &Config{ cfg := &Config{
Tree: tomlConfig, Tree: tomlConfig,
Logger: b.logger, Version: configVersion,
RuntimeType: b.runtimeType, CRIRuntimePluginName: criRuntimePluginName,
UseDefaultRuntimeName: b.useLegacyConfig, Logger: b.logger,
ContainerAnnotations: b.containerAnnotations, RuntimeType: b.runtimeType,
UseLegacyConfig: b.useLegacyConfig,
ContainerAnnotations: b.containerAnnotations,
} }
version, err := cfg.parseVersion(b.useLegacyConfig) switch configVersion {
if err != nil {
return nil, fmt.Errorf("failed to parse config version: %v", err)
}
switch version {
case 1: case 1:
return (*ConfigV1)(cfg), nil return (*ConfigV1)(cfg), nil
case 2: default:
return cfg, nil return cfg, nil
} }
return nil, fmt.Errorf("unsupported config version: %v", version)
} }
// parseVersion returns the version of the config // parseVersion returns the version of the config
func (c *Config) parseVersion(useLegacyConfig bool) (int, error) { func (b *builder) parseVersion(c *toml.Tree) (int64, error) {
defaultVersion := 2 if c == nil || len(c.Keys()) == 0 {
if useLegacyConfig { // No config exists, or the config file is empty.
defaultVersion = 1 if b.useLegacyConfig {
// If a legacy config is explicitly requested, we default to a v1 config.
return 1, nil
}
// Use the requested version.
return int64(b.configVersion), nil
} }
switch v := c.Get("version").(type) { switch v := c.Get("version").(type) {
case nil: case nil:
switch len(c.Keys()) { return 1, nil
case 0: // No config exists, or the config file is empty, use version inferred from containerd
return defaultVersion, nil
default: // A config file exists, has content, and no version is set
return 1, nil
}
case int64: case int64:
return int(v), nil return v, nil
default: default:
return -1, fmt.Errorf("unsupported type for version field: %v", v) return -1, fmt.Errorf("unsupported type for version field: %v", v)
} }
} }
func (b *builder) criRuntimePluginName(configVersion int64) (string, error) {
switch configVersion {
case 1:
return "cri", nil
case 2:
return "io.containerd.grpc.v1.cri", nil
default:
return "io.containerd.cri.v1.runtime", nil
}
}
func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) { func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
if c == nil || c.Tree == nil { if c == nil || c.Tree == nil {
return nil, fmt.Errorf("config is nil") return nil, fmt.Errorf("config is nil")
} }
runtimeData := c.GetSubtreeByPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name}) runtimeData := c.GetSubtreeByPath([]string{"plugins", c.CRIRuntimePluginName, "containerd", "runtimes", name})
return &containerdCfgRuntime{ return &containerdCfgRuntime{
tree: runtimeData, tree: runtimeData,
}, nil }, nil

View File

@ -21,16 +21,13 @@ import (
"github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml" "github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml"
) )
const (
defaultRuntimeType = "io.containerd.runc.v2"
)
type builder struct { type builder struct {
logger logger.Interface logger logger.Interface
configSource toml.Loader configSource toml.Loader
configVersion int
useLegacyConfig bool
path string path string
runtimeType string runtimeType string
useLegacyConfig bool
containerAnnotations []string containerAnnotations []string
} }
@ -65,13 +62,20 @@ func WithRuntimeType(runtimeType string) Option {
} }
} }
// WithUseLegacyConfig sets the useLegacyConfig flag for the config builder // WithUseLegacyConfig sets the useLegacyConfig flag for the config builder.
func WithUseLegacyConfig(useLegacyConfig bool) Option { func WithUseLegacyConfig(useLegacyConfig bool) Option {
return func(b *builder) { return func(b *builder) {
b.useLegacyConfig = useLegacyConfig b.useLegacyConfig = useLegacyConfig
} }
} }
// WithConfigVersion sets the config version for the config builder
func WithConfigVersion(configVersion int) Option {
return func(b *builder) {
b.configVersion = configVersion
}
}
// WithContainerAnnotations sets the container annotations for the config builder // WithContainerAnnotations sets the container annotations for the config builder
func WithContainerAnnotations(containerAnnotations ...string) Option { func WithContainerAnnotations(containerAnnotations ...string) Option {
return func(b *builder) { return func(b *builder) {

View File

@ -166,3 +166,13 @@ func (c *Config) GetRuntimeConfig(name string) (engine.RuntimeConfig, error) {
} }
return &dockerRuntime{}, nil return &dockerRuntime{}, nil
} }
// String returns the string representation of the JSON config.
func (c Config) String() string {
output, err := json.MarshalIndent(c, "", " ")
if err != nil {
return fmt.Sprintf("invalid JSON: %v", err)
}
return string(output)
}

View File

@ -0,0 +1,26 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 toml
type tomlMap map[string]interface{}
var _ Loader = (*tomlFile)(nil)
// Load loads the contents of the specified TOML file as a map.
func (l tomlMap) Load() (*Tree, error) {
return LoadMap(l)
}

View File

@ -0,0 +1,26 @@
/**
# Copyright 2024 NVIDIA CORPORATION
#
# 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 toml
type tomlString string
var _ Loader = (*tomlFile)(nil)
// Load loads the contents of the specified TOML file as a map.
func (l tomlString) Load() (*Tree, error) {
return Load(string(l))
}

View File

@ -25,15 +25,6 @@ type Loader interface {
Load() (*Tree, error) Load() (*Tree, error)
} }
// FromFile creates a TOML source from the specified file.
// If an empty string is passed an empty toml config is used.
func FromFile(path string) Loader {
if path == "" {
return Empty
}
return tomlFile(path)
}
// FromCommandLine creates a TOML source from the output of a shell command and its corresponding args. // FromCommandLine creates a TOML source from the output of a shell command and its corresponding args.
// If the command is empty, an empty config is returned. // If the command is empty, an empty config is returned.
func FromCommandLine(cmds ...string) Loader { func FromCommandLine(cmds ...string) Loader {
@ -45,3 +36,30 @@ func FromCommandLine(cmds ...string) Loader {
args: cmds[1:], args: cmds[1:],
} }
} }
// FromFile creates a TOML source from the specified file.
// If an empty string is passed an empty toml config is used.
func FromFile(path string) Loader {
if path == "" {
return Empty
}
return tomlFile(path)
}
// FromMap creates a TOML source for the specified map.
// If an empty map is passed and empty tomly config is used.
func FromMap(m map[string]interface{}) Loader {
if m == nil {
return Empty
}
return tomlMap(m)
}
// FromString creates a TOML source for the specified contents.
// If an empty string is passed an empty toml config is used.
func FromString(contents string) Loader {
if contents == "" {
return Empty
}
return tomlString(contents)
}

View File

@ -88,37 +88,36 @@ func TestUpdateV1ConfigDefaultRuntime(t *testing.T) {
SetAsDefault: tc.setAsDefault, SetAsDefault: tc.setAsDefault,
} }
cfg, err := toml.Empty.Load() v1, err := containerd.New(
require.NoError(t, err, "%d: %v", i, tc) containerd.WithLogger(logger),
containerd.WithConfigSource(toml.Empty),
v1 := &containerd.ConfigV1{ containerd.WithRuntimeType(runtimeType),
Logger: logger, containerd.WithUseLegacyConfig(tc.legacyConfig),
Tree: cfg, containerd.WithConfigVersion(1),
UseDefaultRuntimeName: !tc.legacyConfig, )
RuntimeType: runtimeType, require.NoError(t, err)
}
err = o.UpdateConfig(v1) err = o.UpdateConfig(v1)
require.NoError(t, err, "%d: %v", i, tc) require.NoError(t, err)
defaultRuntimeName := v1.GetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"}) cfg := v1.(*containerd.ConfigV1)
require.EqualValues(t, tc.expectedDefaultRuntimeName, defaultRuntimeName, "%d: %v", i, tc) defaultRuntimeName := cfg.GetPath([]string{"plugins", "cri", "containerd", "default_runtime_name"})
require.EqualValues(t, tc.expectedDefaultRuntimeName, defaultRuntimeName)
defaultRuntime := v1.GetPath([]string{"plugins", "cri", "containerd", "default_runtime"}) defaultRuntime := cfg.GetPath([]string{"plugins", "cri", "containerd", "default_runtime"})
if tc.expectedDefaultRuntimeBinary == nil { if tc.expectedDefaultRuntimeBinary == nil {
require.Nil(t, defaultRuntime, "%d: %v", i, tc) require.Nil(t, defaultRuntime)
} else { } else {
require.NotNil(t, defaultRuntime) require.NotNil(t, defaultRuntime)
expected, err := defaultRuntimeTomlConfigV1(tc.expectedDefaultRuntimeBinary.(string)) expected, err := defaultRuntimeTomlConfigV1(tc.expectedDefaultRuntimeBinary.(string))
require.NoError(t, err, "%d: %v", i, tc) require.NoError(t, err)
configContents, _ := toml.Marshal(defaultRuntime.(*toml.Tree)) configContents, _ := toml.Marshal(defaultRuntime.(*toml.Tree))
expectedContents, _ := toml.Marshal(expected) expectedContents, _ := toml.Marshal(expected)
require.Equal(t, string(expectedContents), string(configContents), "%d: %v: %v", i, tc) require.Equal(t, string(expectedContents), string(configContents), "%d: %v: %v", i, tc)
} }
}) })
} }
} }
@ -234,24 +233,22 @@ func TestUpdateV1Config(t *testing.T) {
RuntimeDir: runtimeDir, RuntimeDir: runtimeDir,
} }
cfg, err := toml.Empty.Load() v1, err := containerd.New(
containerd.WithLogger(logger),
containerd.WithConfigSource(toml.Empty),
containerd.WithRuntimeType(runtimeType),
containerd.WithConfigVersion(1),
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
)
require.NoError(t, err) require.NoError(t, err)
v1 := &containerd.ConfigV1{
Logger: logger,
Tree: cfg,
UseDefaultRuntimeName: true,
RuntimeType: runtimeType,
ContainerAnnotations: []string{"cdi.k8s.io/*"},
}
err = o.UpdateConfig(v1) err = o.UpdateConfig(v1)
require.NoError(t, err) require.NoError(t, err)
expected, err := toml.TreeFromMap(tc.expectedConfig) expected, err := toml.TreeFromMap(tc.expectedConfig)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected.String(), cfg.String()) require.Equal(t, expected.String(), v1.String())
}) })
} }
} }
@ -393,29 +390,28 @@ func TestUpdateV1ConfigWithRuncPresent(t *testing.T) {
RuntimeDir: runtimeDir, RuntimeDir: runtimeDir,
} }
cfg, err := toml.TreeFromMap(runcConfigMapV1("/runc-binary")) v1, err := containerd.New(
containerd.WithLogger(logger),
containerd.WithConfigSource(toml.FromMap(runcConfigMapV1("/runc-binary"))),
containerd.WithRuntimeType(runtimeType),
containerd.WithConfigVersion(1),
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
)
require.NoError(t, err) require.NoError(t, err)
v1 := &containerd.ConfigV1{
Logger: logger,
Tree: cfg,
UseDefaultRuntimeName: true,
RuntimeType: runtimeType,
ContainerAnnotations: []string{"cdi.k8s.io/*"},
}
err = o.UpdateConfig(v1) err = o.UpdateConfig(v1)
require.NoError(t, err) require.NoError(t, err)
expected, err := toml.TreeFromMap(tc.expectedConfig) expected, err := toml.TreeFromMap(tc.expectedConfig)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected.String(), cfg.String()) require.Equal(t, expected.String(), v1.String())
}) })
} }
} }
func TestRevertV1Config(t *testing.T) { func TestRevertV1Config(t *testing.T) {
logger, _ := testlog.NewNullLogger()
testCases := []struct { testCases := []struct {
config map[string]interface { config map[string]interface {
} }
@ -469,25 +465,22 @@ func TestRevertV1Config(t *testing.T) {
RuntimeName: "nvidia", RuntimeName: "nvidia",
} }
cfg, err := toml.LoadMap(tc.config)
require.NoError(t, err, "%d: %v", i, tc)
expected, err := toml.TreeFromMap(tc.expected) expected, err := toml.TreeFromMap(tc.expected)
require.NoError(t, err, "%d: %v", i, tc) require.NoError(t, err)
v1 := &containerd.ConfigV1{ v1, err := containerd.New(
Tree: cfg, containerd.WithLogger(logger),
UseDefaultRuntimeName: true, containerd.WithConfigSource(toml.FromMap(tc.config)),
RuntimeType: runtimeType, containerd.WithRuntimeType(runtimeType),
}
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
)
require.NoError(t, err)
err = o.RevertConfig(v1) err = o.RevertConfig(v1)
require.NoError(t, err, "%d: %v", i, tc) require.NoError(t, err)
configContents, _ := toml.Marshal(cfg) require.Equal(t, expected.String(), v1.String())
expectedContents, _ := toml.Marshal(expected)
require.Equal(t, string(expectedContents), string(configContents), "%d: %v", i, tc)
}) })
} }
} }

View File

@ -72,18 +72,19 @@ func TestUpdateV2ConfigDefaultRuntime(t *testing.T) {
SetAsDefault: tc.setAsDefault, SetAsDefault: tc.setAsDefault,
} }
cfg, err := toml.LoadMap(map[string]interface{}{}) v2, err := containerd.New(
containerd.WithLogger(logger),
containerd.WithConfigSource(toml.Empty),
containerd.WithRuntimeType(runtimeType),
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
)
require.NoError(t, err) require.NoError(t, err)
v2 := &containerd.Config{
Logger: logger,
Tree: cfg,
RuntimeType: runtimeType,
}
err = o.UpdateConfig(v2) err = o.UpdateConfig(v2)
require.NoError(t, err) require.NoError(t, err)
cfg := v2.(*containerd.Config)
defaultRuntimeName := cfg.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}) defaultRuntimeName := cfg.GetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"})
require.EqualValues(t, tc.expectedDefaultRuntimeName, defaultRuntimeName) require.EqualValues(t, tc.expectedDefaultRuntimeName, defaultRuntimeName)
}) })
@ -195,23 +196,21 @@ func TestUpdateV2Config(t *testing.T) {
RuntimeDir: runtimeDir, RuntimeDir: runtimeDir,
} }
cfg, err := toml.LoadMap(map[string]interface{}{}) v2, err := containerd.New(
containerd.WithLogger(logger),
containerd.WithConfigSource(toml.Empty),
containerd.WithRuntimeType(runtimeType),
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
)
require.NoError(t, err) require.NoError(t, err)
v2 := &containerd.Config{
Logger: logger,
Tree: cfg,
RuntimeType: runtimeType,
ContainerAnnotations: []string{"cdi.k8s.io/*"},
}
err = o.UpdateConfig(v2) err = o.UpdateConfig(v2)
require.NoError(t, err) require.NoError(t, err)
expected, err := toml.TreeFromMap(tc.expectedConfig) expected, err := toml.TreeFromMap(tc.expectedConfig)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected.String(), cfg.String()) require.Equal(t, expected.String(), v2.String())
}) })
} }
@ -348,28 +347,28 @@ func TestUpdateV2ConfigWithRuncPresent(t *testing.T) {
RuntimeDir: runtimeDir, RuntimeDir: runtimeDir,
} }
cfg, err := toml.LoadMap(runcConfigMapV2("/runc-binary")) v2, err := containerd.New(
containerd.WithLogger(logger),
containerd.WithConfigSource(toml.FromMap(runcConfigMapV2("/runc-binary"))),
containerd.WithRuntimeType(runtimeType),
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
)
require.NoError(t, err) require.NoError(t, err)
v2 := &containerd.Config{
Logger: logger,
Tree: cfg,
RuntimeType: runtimeType,
ContainerAnnotations: []string{"cdi.k8s.io/*"},
}
err = o.UpdateConfig(v2) err = o.UpdateConfig(v2)
require.NoError(t, err) require.NoError(t, err)
expected, err := toml.TreeFromMap(tc.expectedConfig) expected, err := toml.TreeFromMap(tc.expectedConfig)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected.String(), cfg.String()) require.Equal(t, expected.String(), v2.String())
}) })
} }
} }
func TestRevertV2Config(t *testing.T) { func TestRevertV2Config(t *testing.T) {
logger, _ := testlog.NewNullLogger()
testCases := []struct { testCases := []struct {
config map[string]interface { config map[string]interface {
} }
@ -418,24 +417,21 @@ func TestRevertV2Config(t *testing.T) {
RuntimeName: "nvidia", RuntimeName: "nvidia",
} }
cfg, err := toml.LoadMap(tc.config)
require.NoError(t, err)
expected, err := toml.TreeFromMap(tc.expected) expected, err := toml.TreeFromMap(tc.expected)
require.NoError(t, err) require.NoError(t, err)
v2 := &containerd.Config{ v2, err := containerd.New(
Tree: cfg, containerd.WithLogger(logger),
RuntimeType: runtimeType, containerd.WithConfigSource(toml.FromMap(tc.config)),
} containerd.WithRuntimeType(runtimeType),
containerd.WithContainerAnnotations("cdi.k8s.io/*"),
)
require.NoError(t, err)
err = o.RevertConfig(v2) err = o.RevertConfig(v2)
require.NoError(t, err) require.NoError(t, err)
configContents, _ := toml.Marshal(cfg) require.Equal(t, expected.String(), v2.String())
expectedContents, _ := toml.Marshal(expected)
require.Equal(t, string(expectedContents), string(configContents))
}) })
} }
} }
@ -454,6 +450,7 @@ func runtimeMapV2(binary string) map[string]interface{} {
func runcConfigMapV2(binary string) map[string]interface{} { func runcConfigMapV2(binary string) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"version": 2,
"plugins": map[string]interface{}{ "plugins": map[string]interface{}{
"io.containerd.grpc.v1.cri": map[string]interface{}{ "io.containerd.grpc.v1.cri": map[string]interface{}{
"containerd": map[string]interface{}{ "containerd": map[string]interface{}{

View File

@ -52,8 +52,11 @@ type Options struct {
func Flags(opts *Options) []cli.Flag { func Flags(opts *Options) []cli.Flag {
flags := []cli.Flag{ flags := []cli.Flag{
&cli.BoolFlag{ &cli.BoolFlag{
Name: "use-legacy-config", Name: "use-legacy-config",
Usage: "Specify whether a legacy (pre v1.3) config should be used", Usage: "Specify whether a legacy (pre v1.3) config should be used. " +
"This ensures that a version 1 container config is created by default and that the " +
"containerd.runtimes.default_runtime config section is used to define the default " +
"runtime instead of container.default_runtime_name.",
Destination: &opts.useLegacyConfig, Destination: &opts.useLegacyConfig,
EnvVars: []string{"CONTAINERD_USE_LEGACY_CONFIG"}, EnvVars: []string{"CONTAINERD_USE_LEGACY_CONFIG"},
}, },