diff --git a/cmd/nvidia-ctk/runtime/configure/configure_test.go b/cmd/nvidia-ctk/runtime/configure/configure_test.go index 32b5419e9..305d24686 100644 --- a/cmd/nvidia-ctk/runtime/configure/configure_test.go +++ b/cmd/nvidia-ctk/runtime/configure/configure_test.go @@ -43,6 +43,36 @@ func TestConfigureLifecycle(t *testing.T) { assertConditions func(*testing.T, string) error }{ // Containerd v2 test cases + { + description: "containerd: config exists with imports", + args: []string{ + "--runtime", "containerd", + "--config", "{{ .testRoot }}/etc/containerd/config.toml", + "--drop-in-config", "{{ .testRoot }}/etc/containerd/conf.d/99-nvidia.toml", + }, + prepareEnvironment: func(t *testing.T, testRoot string) error { + configPath := filepath.Join(testRoot, "etc/containerd/config.toml") + require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0755)) + + initialConfig := `version = 2 +imports = ["/foo/bar/*.toml"] +` + return os.WriteFile(configPath, []byte(initialConfig), 0600) + }, + assertConditions: func(t *testing.T, testRoot string) error { + mainConfig := filepath.Join(testRoot, "etc/containerd/config.toml") + content, err := os.ReadFile(mainConfig) + require.NoError(t, err) + + expectedTemplate := `imports = ["/foo/bar/*.toml", "{{ .testRoot }}/etc/containerd/conf.d/*.toml"] +version = 2 +` + expected := strings.ReplaceAll(expectedTemplate, "{{ .testRoot }}", testRoot) + + require.Equal(t, expected, string(content)) + return nil + }, + }, { description: "containerd: v2 config does not exist with drop-in", args: []string{ diff --git a/pkg/config/engine/containerd/config_drop_in.go b/pkg/config/engine/containerd/config_drop_in.go index 8264523e4..0f16dc8de 100644 --- a/pkg/config/engine/containerd/config_drop_in.go +++ b/pkg/config/engine/containerd/config_drop_in.go @@ -173,12 +173,35 @@ func (c *topLevelConfig) removeVersion() { c.config.Delete("version") } +func (c *topLevelConfig) getCurrentImports() []string { + rawImports := c.config.Get("imports") + if rawImports == nil { + return nil + } + + if importsStringSlice, ok := rawImports.([]string); ok { + return importsStringSlice + } + + importsAnySlice, ok := rawImports.([]any) + if !ok { + return nil + } + var importsStringSlice []string + for _, importAny := range importsAnySlice { + importString, ok := importAny.(string) + if !ok { + continue + } + importsStringSlice = append(importsStringSlice, importString) + } + + return importsStringSlice +} + func (c *topLevelConfig) ensureImports(dropInFilename string) { config := c.config.Tree - var currentImports []string - if ci, ok := c.config.Get("imports").([]string); ok { - currentImports = ci - } + currentImports := c.getCurrentImports() requiredImport := c.importPattern(dropInFilename) for _, currentImport := range currentImports { diff --git a/pkg/config/engine/containerd/config_drop_in_test.go b/pkg/config/engine/containerd/config_drop_in_test.go new file mode 100644 index 000000000..fe597ed8c --- /dev/null +++ b/pkg/config/engine/containerd/config_drop_in_test.go @@ -0,0 +1,74 @@ +/** +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# 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/stretchr/testify/require" + + "github.com/NVIDIA/nvidia-container-toolkit/pkg/config/toml" +) + +func TestEnsureImports(t *testing.T) { + testCases := []struct { + description string + configMap map[string]any + path string + expectedImports []string + }{ + { + description: "empty", + path: "/another/path/file.toml", + expectedImports: []string{"/another/path/*.toml"}, + }, + { + description: "existing imports as string slice", + configMap: map[string]any{ + "imports": []string{"/foo/bar/*.toml"}, + }, + path: "/another/path/file.toml", + expectedImports: []string{"/foo/bar/*.toml", "/another/path/*.toml"}, + }, + { + description: "existing imports as any slice", + configMap: map[string]any{ + "imports": []any{"/foo/bar/*.toml"}, + }, + path: "/another/path/file.toml", + expectedImports: []string{"/foo/bar/*.toml", "/another/path/*.toml"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + cut := topLevelConfig{ + config: &Config{ + Tree: func() *toml.Tree { + t, _ := toml.FromMap(tc.configMap).Load() + return t + }(), + }, + } + + cut.ensureImports("/another/path/file.toml") + require.EqualValues(t, tc.expectedImports, cut.config.Get("imports")) + }) + } + +}