diff --git a/pkg/config/engine/api.go b/pkg/config/engine/api.go index b074dadf..78b1549c 100644 --- a/pkg/config/engine/api.go +++ b/pkg/config/engine/api.go @@ -19,7 +19,7 @@ package engine // Interface defines the API for a runtime config updater. type Interface interface { DefaultRuntime() string - AddRuntime(string, string, bool) error + AddRuntime(string, string, bool, ...map[string]interface{}) error Set(string, interface{}) RemoveRuntime(string) error Save(string) (int64, error) diff --git a/pkg/config/engine/containerd/config_v1.go b/pkg/config/engine/containerd/config_v1.go index e9afda95..10e80b58 100644 --- a/pkg/config/engine/containerd/config_v1.go +++ b/pkg/config/engine/containerd/config_v1.go @@ -30,7 +30,7 @@ type ConfigV1 Config var _ engine.Interface = (*ConfigV1)(nil) // AddRuntime adds a runtime to the containerd config -func (c *ConfigV1) AddRuntime(name string, path string, setAsDefault bool) error { +func (c *ConfigV1) AddRuntime(name string, path string, setAsDefault bool, configOverrides ...map[string]interface{}) error { if c == nil || c.Tree == nil { return fmt.Errorf("config is nil") } @@ -75,6 +75,16 @@ func (c *ConfigV1) AddRuntime(name string, path string, setAsDefault bool) error } config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "BinaryName"}, path) config.SetPath([]string{"plugins", "cri", "containerd", "default_runtime", "options", "Runtime"}, path) + + defaultRuntimeSubtree := subtreeAtPath(config, "plugins", "cri", "containerd", "default_runtime") + if err := defaultRuntimeSubtree.applyOverrides(configOverrides...); err != nil { + return fmt.Errorf("failed to apply config overrides to default_runtime: %w", err) + } + } + + runtimeSubtree := subtreeAtPath(config, "plugins", "cri", "containerd", "runtimes", name) + if err := runtimeSubtree.applyOverrides(configOverrides...); err != nil { + return fmt.Errorf("failed to apply config overrides: %w", err) } *c.Tree = config diff --git a/pkg/config/engine/containerd/config_v2.go b/pkg/config/engine/containerd/config_v2.go index 52ea54e2..e8818988 100644 --- a/pkg/config/engine/containerd/config_v2.go +++ b/pkg/config/engine/containerd/config_v2.go @@ -25,7 +25,7 @@ import ( ) // AddRuntime adds a runtime to the containerd config -func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error { +func (c *Config) AddRuntime(name string, path string, setAsDefault bool, configOverrides ...map[string]interface{}) error { if c == nil || c.Tree == nil { return fmt.Errorf("config is nil") } @@ -60,6 +60,11 @@ func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error { config.SetPath([]string{"plugins", "io.containerd.grpc.v1.cri", "containerd", "default_runtime_name"}, name) } + runtimeSubtree := subtreeAtPath(config, "plugins", "io.containerd.grpc.v1.cri", "containerd", "runtimes", name) + if err := runtimeSubtree.applyOverrides(configOverrides...); err != nil { + return fmt.Errorf("failed to apply config overrides: %w", err) + } + *c.Tree = config return nil } diff --git a/pkg/config/engine/containerd/config_v2_test.go b/pkg/config/engine/containerd/config_v2_test.go new file mode 100644 index 00000000..1ab6cc1e --- /dev/null +++ b/pkg/config/engine/containerd/config_v2_test.go @@ -0,0 +1,97 @@ +/** +# 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 containerd + +import ( + "testing" + + "github.com/pelletier/go-toml" + "github.com/stretchr/testify/require" +) + +func TestAddRuntime(t *testing.T) { + testCases := []struct { + description string + config string + setAsDefault bool + configOverrides []map[string]interface{} + expectedConfig string + expectedError error + }{ + { + description: "empty config not default runtime", + expectedConfig: ` + version = 2 + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".containerd] + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test] + privileged_without_host_devices = false + runtime_engine = "" + runtime_root = "" + runtime_type = "" + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test.options] + BinaryName = "/usr/bin/test" + `, + expectedError: nil, + }, + { + description: "empty config not default runtime with overrides", + configOverrides: []map[string]interface{}{ + { + "options": map[string]interface{}{ + "SystemdCgroup": true, + }, + }, + }, + expectedConfig: ` + version = 2 + [plugins] + [plugins."io.containerd.grpc.v1.cri"] + [plugins."io.containerd.grpc.v1.cri".containerd] + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test] + privileged_without_host_devices = false + runtime_engine = "" + runtime_root = "" + runtime_type = "" + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.test.options] + BinaryName = "/usr/bin/test" + SystemdCgroup = true + `, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + config, err := toml.Load(tc.config) + require.NoError(t, err) + expectedConfig, err := toml.Load(tc.expectedConfig) + require.NoError(t, err) + + c := &Config{ + Tree: config, + } + + err = c.AddRuntime("test", "/usr/bin/test", tc.setAsDefault, tc.configOverrides...) + require.NoError(t, err) + + require.EqualValues(t, expectedConfig.String(), config.String()) + }) + } +} diff --git a/pkg/config/engine/containerd/toml.go b/pkg/config/engine/containerd/toml.go new file mode 100644 index 00000000..85c20d0e --- /dev/null +++ b/pkg/config/engine/containerd/toml.go @@ -0,0 +1,56 @@ +/** +# 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 containerd + +import ( + "fmt" + + "github.com/pelletier/go-toml" +) + +// tomlTree is an alias for toml.Tree that allows for extensions. +type tomlTree toml.Tree + +func subtreeAtPath(c toml.Tree, path ...string) *tomlTree { + tree := c.GetPath(path).(*toml.Tree) + return (*tomlTree)(tree) +} + +func (t *tomlTree) insert(other map[string]interface{}) error { + + for key, value := range other { + if insertsubtree, ok := value.(map[string]interface{}); ok { + subtree := (*toml.Tree)(t).Get(key).(*toml.Tree) + return (*tomlTree)(subtree).insert(insertsubtree) + } + (*toml.Tree)(t).Set(key, value) + } + return nil +} + +func (t *tomlTree) applyOverrides(overrides ...map[string]interface{}) error { + for _, override := range overrides { + subconfig, err := toml.TreeFromMap(override) + if err != nil { + return fmt.Errorf("invalid toml config: %w", err) + } + if err := t.insert(subconfig.ToMap()); err != nil { + return err + } + } + return nil +} diff --git a/pkg/config/engine/crio/crio.go b/pkg/config/engine/crio/crio.go index 46ef72b7..4fa9795e 100644 --- a/pkg/config/engine/crio/crio.go +++ b/pkg/config/engine/crio/crio.go @@ -40,7 +40,7 @@ func New(opts ...Option) (engine.Interface, error) { } // AddRuntime adds a new runtime to the crio config -func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error { +func (c *Config) AddRuntime(name string, path string, setAsDefault bool, _ ...map[string]interface{}) error { if c == nil { return fmt.Errorf("config is nil") } diff --git a/pkg/config/engine/docker/docker.go b/pkg/config/engine/docker/docker.go index 831360f6..e6edbe81 100644 --- a/pkg/config/engine/docker/docker.go +++ b/pkg/config/engine/docker/docker.go @@ -49,7 +49,7 @@ func New(opts ...Option) (engine.Interface, error) { } // AddRuntime adds a new runtime to the docker config -func (c *Config) AddRuntime(name string, path string, setAsDefault bool) error { +func (c *Config) AddRuntime(name string, path string, setAsDefault bool, _ ...map[string]interface{}) error { if c == nil { return fmt.Errorf("config is nil") }