diff --git a/pkg/apis/config/v1alpha4/types.go b/pkg/apis/config/v1alpha4/types.go index 33acf503fd..783dfd1423 100644 --- a/pkg/apis/config/v1alpha4/types.go +++ b/pkg/apis/config/v1alpha4/types.go @@ -75,11 +75,14 @@ type Cluster struct { // ContainerdConfigPatches are applied to every node's containerd config // in the order listed. // These should be toml stringsto be applied as merge patches + // If the version field in these patches doesn't match containerd config, it will not be a applied + // This way you can write configurations that work for both by supplying two patches ContainerdConfigPatches []string `yaml:"containerdConfigPatches,omitempty" json:"containerdConfigPatches,omitempty"` // ContainerdConfigPatchesJSON6902 are applied to every node's containerd config // in the order listed. // These should be YAML or JSON formatting RFC 6902 JSON patches + // NOTE: These are not currently version-aware. ContainerdConfigPatchesJSON6902 []string `yaml:"containerdConfigPatchesJSON6902,omitempty" json:"containerdConfigPatchesJSON6902,omitempty"` } diff --git a/pkg/build/nodeimage/containerd.go b/pkg/build/nodeimage/containerd.go index 820af82c95..a3e5e6d930 100644 --- a/pkg/build/nodeimage/containerd.go +++ b/pkg/build/nodeimage/containerd.go @@ -36,7 +36,7 @@ const containerdConfigPatchSystemdCgroupFalse = ` ` func configureContainerdSystemdCgroupFalse(containerCmdr exec.Cmder, config string) error { - patched, err := patch.TOML(config, []string{containerdConfigPatchSystemdCgroupFalse}, []string{}) + patched, err := patch.ContainerdTOML(config, []string{containerdConfigPatchSystemdCgroupFalse}, []string{}) if err != nil { return errors.Wrap(err, "failed to configure containerd SystemdCgroup=false") } diff --git a/pkg/cluster/internal/create/actions/config/config.go b/pkg/cluster/internal/create/actions/config/config.go index 1726198070..7b0ad86001 100644 --- a/pkg/cluster/internal/create/actions/config/config.go +++ b/pkg/cluster/internal/create/actions/config/config.go @@ -127,7 +127,7 @@ func (a *Action) Execute(ctx *actions.ActionContext) error { if err := node.Command("cat", containerdConfigPath).SetStdout(&buff).Run(); err != nil { return errors.Wrap(err, "failed to read containerd config from node") } - patched, err := patch.TOML(buff.String(), ctx.Config.ContainerdConfigPatches, ctx.Config.ContainerdConfigPatchesJSON6902) + patched, err := patch.ContainerdTOML(buff.String(), ctx.Config.ContainerdConfigPatches, ctx.Config.ContainerdConfigPatchesJSON6902) if err != nil { return errors.Wrap(err, "failed to patch containerd config") } diff --git a/pkg/internal/patch/toml.go b/pkg/internal/patch/toml.go index 09b587edfe..d88b086701 100644 --- a/pkg/internal/patch/toml.go +++ b/pkg/internal/patch/toml.go @@ -29,18 +29,33 @@ import ( "sigs.k8s.io/kind/pkg/errors" ) -// TOML patches toPatch with the patches (should be TOML merge patches) and patches6902 (should be JSON 6902 patches) -func TOML(toPatch string, patches []string, patches6902 []string) (string, error) { +// ContainerdTOML patches toPatch with the patches (should be TOML merge patches) and patches6902 (should be JSON 6902 patches) +func ContainerdTOML(toPatch string, patches []string, patches6902 []string) (string, error) { // convert to JSON for patching j, err := tomlToJSON([]byte(toPatch)) if err != nil { return "", err } + version, err := containerdConfigVersion(toPatch) + if err != nil { + return "", errors.WithStack(err) + } + if version == 0 { + return "", errors.New("failed to detect containerd config version") + } // apply merge patches for _, patch := range patches { pj, err := tomlToJSON([]byte(patch)) if err != nil { - return "", err + return "", errors.WithStack(err) + } + patchVersion, err := containerdConfigVersion(patch) + if err != nil { + return "", errors.WithStack(err) + } + // skip if patch sets version and version does not match + if patchVersion != 0 && patchVersion != version { + continue } patched, err := jsonpatch.MergePatch(j, pj) if err != nil { @@ -64,6 +79,17 @@ func TOML(toPatch string, patches []string, patches6902 []string) (string, error return jsonToTOMLString(j) } +func containerdConfigVersion(configTOML string) (int, error) { + type version struct { + Version int `toml:"version,omitempty"` + } + v := version{} + if err := toml.Unmarshal([]byte(configTOML), &v); err != nil { + return 0, errors.WithStack(err) + } + return v.Version, nil +} + // tomlToJSON converts arbitrary TOML to JSON func tomlToJSON(t []byte) ([]byte, error) { // we use github.com.pelletier/go-toml here to unmarshal arbitrary TOML to JSON diff --git a/pkg/internal/patch/toml_test.go b/pkg/internal/patch/toml_test.go index 1c343e695c..ecfd86a4cf 100644 --- a/pkg/internal/patch/toml_test.go +++ b/pkg/internal/patch/toml_test.go @@ -22,7 +22,7 @@ import ( "sigs.k8s.io/kind/pkg/internal/assert" ) -func TestTOML(t *testing.T) { +func TestContainerdTOML(t *testing.T) { t.Parallel() type testCase struct { Name string @@ -39,15 +39,48 @@ func TestTOML(t *testing.T) { ExpectError: true, ExpectOutput: "", }, + { + Name: "invalid containerd versioning", + ToPatch: `version = "five"`, + ExpectError: true, + ExpectOutput: "", + }, { Name: "no patches", - ToPatch: `disabled_plugins = ["restart"] + ToPatch: `version = 2 +disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, ExpectError: false, ExpectOutput: `disabled_plugins = ["restart"] +version = 2 + +[plugins] + [plugins.cri] + [plugins.cri.containerd] + [plugins.cri.containerd.runtimes] + [plugins.cri.containerd.runtimes.runsc] + runtime_type = "io.containerd.runsc.v1" + [plugins.linux] + shim_debug = true +`, + }, + { + Name: "Only matching patches", + ToPatch: `version = 2 + +disabled_plugins = ["restart"] + +[plugins.linux] + shim_debug = true +[plugins.cri.containerd.runtimes.runsc] + runtime_type = "io.containerd.runsc.v1"`, + Patches: []string{"version = 3\ndisabled_plugins=[\"bar\"]", "version = 2\n disabled_plugins=[\"baz\"]"}, + ExpectError: false, + ExpectOutput: `disabled_plugins = ["baz"] +version = 2 [plugins] [plugins.cri] @@ -61,7 +94,8 @@ func TestTOML(t *testing.T) { }, { Name: "invalid patch TOML", - ToPatch: `disabled_plugins = ["restart"] + ToPatch: `version = 2 +disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] @@ -81,7 +115,8 @@ func TestTOML(t *testing.T) { }, { Name: "trivial patch", - ToPatch: `disabled_plugins = ["restart"] + ToPatch: `version = 2 +disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] @@ -89,6 +124,7 @@ func TestTOML(t *testing.T) { Patches: []string{`disabled_plugins=[]`}, ExpectError: false, ExpectOutput: `disabled_plugins = [] +version = 2 [plugins] [plugins.cri] @@ -102,14 +138,17 @@ func TestTOML(t *testing.T) { }, { Name: "trivial 6902 patch", - ToPatch: `disabled_plugins = ["restart"] + ToPatch: `version = 2 +disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] runtime_type = "io.containerd.runsc.v1"`, PatchesJSON6902: []string{`[{"op": "remove", "path": "/disabled_plugins"}]`}, ExpectError: false, - ExpectOutput: `[plugins] + ExpectOutput: `version = 2 + +[plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] @@ -121,7 +160,8 @@ func TestTOML(t *testing.T) { }, { Name: "trivial patch and trivial 6902 patch", - ToPatch: `disabled_plugins = ["restart"] + ToPatch: `version = 2 +disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] @@ -129,7 +169,9 @@ func TestTOML(t *testing.T) { Patches: []string{`disabled_plugins=["foo"]`}, PatchesJSON6902: []string{`[{"op": "remove", "path": "/disabled_plugins"}]`}, ExpectError: false, - ExpectOutput: `[plugins] + ExpectOutput: `version = 2 + +[plugins] [plugins.cri] [plugins.cri.containerd] [plugins.cri.containerd.runtimes] @@ -160,7 +202,8 @@ func TestTOML(t *testing.T) { }, { Name: "patch registry", - ToPatch: `disabled_plugins = ["restart"] + ToPatch: `version = 2 +disabled_plugins = ["restart"] [plugins.linux] shim_debug = true [plugins.cri.containerd.runtimes.runsc] @@ -170,6 +213,7 @@ func TestTOML(t *testing.T) { endpoint = ["http://registry:5000"]`}, ExpectError: false, ExpectOutput: `disabled_plugins = ["restart"] +version = 2 [plugins] [plugins.cri] @@ -190,7 +234,7 @@ func TestTOML(t *testing.T) { tc := tc // capture test case t.Run(tc.Name, func(t *testing.T) { t.Parallel() - out, err := TOML(tc.ToPatch, tc.Patches, tc.PatchesJSON6902) + out, err := ContainerdTOML(tc.ToPatch, tc.Patches, tc.PatchesJSON6902) assert.ExpectError(t, tc.ExpectError, err) if err == nil { assert.StringEqual(t, tc.ExpectOutput, out)