diff --git a/pkg/component/worker/containerd.go b/pkg/component/worker/containerd.go index 13765010a027..377678e533f4 100644 --- a/pkg/component/worker/containerd.go +++ b/pkg/component/worker/containerd.go @@ -197,19 +197,34 @@ func (c *Containerd) setupConfig() error { } containerdConfigurer := containerd.NewConfigurer(c.Profile.PauseImage, filepath.Join(c.importsPath, "*.toml")) - imports, err := containerdConfigurer.HandleImports() + criConfig, err := containerdConfigurer.HandleImports() if err != nil { return fmt.Errorf("can't handle imports: %w", err) } + + criConfigPath := filepath.Join(c.K0sVars.RunDir, "containerd-cri.toml") + err = file.WriteContentAtomically(criConfigPath, []byte(criConfig.Config), 0644) + if err != nil { + return fmt.Errorf("can't create containerd CRI config: %w", err) + } + + var data struct{ Imports []string } + data.Imports = append(criConfig.ImportPaths, criConfigPath) + + // double escape for windows because containerd expects + // double backslash in the configuration but golang templates + // unescape double slash to a single slash + if runtime.GOOS == "windows" { + for i := range data.Imports { + data.Imports[i] = strings.ReplaceAll(data.Imports[i], "\\", "\\\\") + } + } + output := bytes.NewBuffer([]byte{}) tw := templatewriter.TemplateWriter{ Name: "containerdconfig", Template: confTmpl, - Data: struct { - Imports []string - }{ - Imports: imports, - }, + Data: data, } if err := tw.WriteToBuffer(output); err != nil { return fmt.Errorf("can't create containerd config: %w", err) diff --git a/pkg/component/worker/containerd/criconfig.go b/pkg/component/worker/containerd/criconfig.go index 209916dea9d9..9f5f6bb1112c 100644 --- a/pkg/component/worker/containerd/criconfig.go +++ b/pkg/component/worker/containerd/criconfig.go @@ -23,7 +23,6 @@ import ( "os" "path/filepath" "runtime" - "strings" "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" "github.com/mesosphere/toml-merge/pkg/patch" @@ -33,39 +32,32 @@ import ( criconfig "github.com/containerd/containerd/pkg/cri/config" ) -// TODO: move to K0sVars -const containerdCRIConfigPathPosix = "/run/k0s/containerd-cri.toml" -const containerdCRIConfigPathWindows = "C:\\var\\lib\\k0s\\run\\containerd-cri.toml" +type CRIConfig struct { + Config string + ImportPaths []string +} type CRIConfigurer struct { - loadPath string - pauseImage string - criRuntimePath string + loadPath string + pauseImage string log *logrus.Entry } func NewConfigurer(pauseImage *v1beta1.ImageSpec, importsPath string) *CRIConfigurer { - c := &CRIConfigurer{ + return &CRIConfigurer{ loadPath: importsPath, pauseImage: pauseImage.URI(), log: logrus.WithField("component", "containerd"), } - if runtime.GOOS == "windows" { - c.criRuntimePath = containerdCRIConfigPathWindows - - } else { - c.criRuntimePath = containerdCRIConfigPathPosix - } - return c } // HandleImports Resolves containerd imports from the import glob path. // If the partial config has CRI plugin enabled, it will add to the runc CRI config (single file). // if no CRI plugin is found, it will add the file as-is to imports list returned. // Once all files are processed the concatenated CRI config file is written and added to the imports list. -func (c *CRIConfigurer) HandleImports() ([]string, error) { - var imports []string +func (c *CRIConfigurer) HandleImports() (*CRIConfig, error) { + var importPaths []string var criConfigBuffer bytes.Buffer // Add default runc based CRI config @@ -74,54 +66,38 @@ func (c *CRIConfigurer) HandleImports() ([]string, error) { return nil, err } - files, err := filepath.Glob(c.loadPath) - c.log.Debugf("found containerd config files: %v", files) + filePaths, err := filepath.Glob(c.loadPath) + c.log.Debugf("found containerd config files: %v", filePaths) if err != nil { return nil, err } finalConfig := criConfigBuffer.String() - for _, file := range files { - data, err := os.ReadFile(file) + for _, filePath := range filePaths { + data, err := os.ReadFile(filePath) if err != nil { return nil, err } - c.log.Debugf("parsing containerd config file: %s", file) + c.log.Debugf("parsing containerd config file: %s", filePath) hasCRI, err := hasCRIPluginConfig(data) if err != nil { return nil, err } if hasCRI { - c.log.Infof("found CRI plugin config in %s, merging to CRI config", file) + c.log.Infof("found CRI plugin config in %s, merging to CRI config", filePath) // Merge to the existing CRI config - finalConfig, err = patch.TOMLString(finalConfig, patch.FilePatches(file)) + finalConfig, err = patch.TOMLString(finalConfig, patch.FilePatches(filePath)) if err != nil { - return nil, fmt.Errorf("failed to merge CRI config from %s: %w", file, err) + return nil, fmt.Errorf("failed to merge CRI config from %s: %w", filePath, err) } } else { - c.log.Debugf("adding %s as-is to imports", file) + c.log.Debugf("adding %s as-is to imports", filePath) // Add file to imports - imports = append(imports, escapedPath(file)) + importPaths = append(importPaths, filePath) } } - // Write the CRI config to a file and add it to imports - err = os.WriteFile(c.criRuntimePath, []byte(finalConfig), 0644) - if err != nil { - return nil, err - } - imports = append(imports, escapedPath(c.criRuntimePath)) - - return imports, nil -} -func escapedPath(s string) string { - // double escape for windows because containerd expects - // double backslash in the configuration but golang templates - // unescape double slash to a single slash - if runtime.GOOS == "windows" { - return strings.ReplaceAll(s, "\\", "\\\\") - } - return s + return &CRIConfig{Config: finalConfig, ImportPaths: importPaths}, nil } // We need to use custom struct so we can unmarshal the CRI plugin config only diff --git a/pkg/component/worker/containerd/criconfig_test.go b/pkg/component/worker/containerd/criconfig_test.go index 509fe1d2398b..7f8e18abd86c 100644 --- a/pkg/component/worker/containerd/criconfig_test.go +++ b/pkg/component/worker/containerd/criconfig_test.go @@ -21,9 +21,10 @@ import ( "path/filepath" "testing" + "github.com/k0sproject/k0s/pkg/apis/k0s/v1beta1" + serverconfig "github.com/containerd/containerd/services/server/config" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -54,7 +55,6 @@ version = 2 func TestCRIConfigurer_HandleImports(t *testing.T) { t.Run("should merge CRI config snippets", func(t *testing.T) { importsPath := t.TempDir() - criRuntimePath := filepath.Join(t.TempDir(), "cri.toml") criRuntimeConfig := ` [plugins] [plugins."io.containerd.grpc.v1.cri".containerd] @@ -62,75 +62,35 @@ func TestCRIConfigurer_HandleImports(t *testing.T) { ` err := os.WriteFile(filepath.Join(importsPath, "foo.toml"), []byte(criRuntimeConfig), 0644) require.NoError(t, err) - c := CRIConfigurer{ - loadPath: filepath.Join(importsPath, "*.toml"), - criRuntimePath: criRuntimePath, - log: logrus.New().WithField("test", t.Name()), - } - _, err = c.HandleImports() + c := NewConfigurer(&v1beta1.ImageSpec{}, filepath.Join(importsPath, "*.toml")) + criConfig, err := c.HandleImports() require.NoError(t, err) + assert.Empty(t, criConfig.ImportPaths, "CRI config snippets should be merged, not imported") // Dump the config for inspection - b, _ := os.ReadFile(criRuntimePath) - t.Logf("CRI config:\n%s", string(b)) + t.Logf("CRI config:\n%s", criConfig.Config) + + criConfigPath := filepath.Join(t.TempDir(), "cri.toml") + require.NoError(t, os.WriteFile(criConfigPath, []byte(criConfig.Config), 0644)) // Load the criRuntimeConfig and verify the settings are correct - containerdConfig := &serverconfig.Config{} - err = serverconfig.LoadConfig(criRuntimePath, containerdConfig) - require.NoError(t, err) + var containerdConfig serverconfig.Config + require.NoError(t, serverconfig.LoadConfig(criConfigPath, &containerdConfig)) - criConfig := containerdConfig.Plugins["io.containerd.grpc.v1.cri"] - snapshotter := criConfig.GetPath([]string{"containerd", "snapshotter"}) + criPlugins := containerdConfig.Plugins["io.containerd.grpc.v1.cri"] + snapshotter := criPlugins.GetPath([]string{"containerd", "snapshotter"}) require.Equal(t, "zfs", snapshotter) }) - t.Run("should have single import for CRI if there's nothing in imports dir", func(t *testing.T) { - criRuntimePath := filepath.Join(t.TempDir(), "cri.toml") - c := CRIConfigurer{ - loadPath: filepath.Join(t.TempDir(), "*.toml"), - criRuntimePath: criRuntimePath, - log: logrus.New().WithField("test", t.Name()), - } - imports, err := c.HandleImports() + t.Run("should have no imports if imports dir is empty", func(t *testing.T) { + c := NewConfigurer(&v1beta1.ImageSpec{}, filepath.Join(t.TempDir(), "*.toml")) + criConfig, err := c.HandleImports() assert.NoError(t, err) - if assert.Len(t, imports, 1) { - assert.Equal(t, escapedPath(criRuntimePath), imports[0]) - } - }) - - t.Run("should have single import for all CRI configs", func(t *testing.T) { - importsPath := t.TempDir() - criRuntimePath := filepath.Join(t.TempDir(), "cri.toml") - criRuntimeConfig := ` -[plugins."io.containerd.grpc.v1.cri".registry.mirrors] - [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"] - endpoint = ["https://registry-1.docker.io"] -` - err := os.WriteFile(filepath.Join(importsPath, "foo.toml"), []byte(criRuntimeConfig), 0644) - require.NoError(t, err) - c := CRIConfigurer{ - loadPath: filepath.Join(importsPath, "*.toml"), - criRuntimePath: criRuntimePath, - log: logrus.New().WithField("test", t.Name()), - } - imports, err := c.HandleImports() - require.NoError(t, err) - require.Len(t, imports, 1) - require.Contains(t, imports, escapedPath(criRuntimePath)) - - // Load the criRuntimeConfig and verify the settings are correct - containerdConfig := &serverconfig.Config{} - err = serverconfig.LoadConfig(criRuntimePath, containerdConfig) - require.NoError(t, err) - - criConfig := containerdConfig.Plugins["io.containerd.grpc.v1.cri"] - ep := criConfig.GetPath([]string{"registry", "mirrors", "docker.io", "endpoint"}) - assert.Equal(t, []any{"https://registry-1.docker.io"}, ep) + assert.Empty(t, criConfig.ImportPaths) }) - t.Run("should have two imports when one non CRI snippet", func(t *testing.T) { + t.Run("should import non-CRI config snippets", func(t *testing.T) { importsPath := t.TempDir() - criRuntimePath := filepath.Join(t.TempDir(), "cri.toml") criRuntimeConfig := ` foo = "bar" version = 2 @@ -138,16 +98,9 @@ version = 2 nonCriConfigPath := filepath.Join(importsPath, "foo.toml") err := os.WriteFile(nonCriConfigPath, []byte(criRuntimeConfig), 0644) require.NoError(t, err) - c := CRIConfigurer{ - loadPath: filepath.Join(importsPath, "*.toml"), - criRuntimePath: criRuntimePath, - log: logrus.New().WithField("test", t.Name()), - } - imports, err := c.HandleImports() + c := NewConfigurer(&v1beta1.ImageSpec{}, filepath.Join(importsPath, "*.toml")) + criConfig, err := c.HandleImports() assert.NoError(t, err) - if assert.Len(t, imports, 2) { - assert.Contains(t, imports, escapedPath(criRuntimePath)) - assert.Contains(t, imports, escapedPath(nonCriConfigPath)) - } + assert.Equal(t, []string{nonCriConfigPath}, criConfig.ImportPaths) }) }