// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package kio_test import ( "fmt" "math/rand" "os" "path/filepath" "runtime" "testing" "time" "sigs.k8s.io/kustomize/kyaml/filesys" "sigs.k8s.io/kustomize/kyaml/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" . "sigs.k8s.io/kustomize/kyaml/kio" "sigs.k8s.io/kustomize/kyaml/yaml" ) // TestLocalPackageWriter_Write tests: // - ReaderAnnotations are cleared when writing the Resources func TestLocalPackageWriter_Write(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() w := LocalPackageWriter{ PackagePath: d, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err := w.Write([]*yaml.RNode{node2, node1, node3}) require.NoError(t, err) b, err := fs.ReadFile(filepath.Join(d, "a", "b", "a_test.yaml")) require.NoError(t, err) require.Equal(t, `a: b #first --- c: d # second `, string(b)) b, err = fs.ReadFile(filepath.Join(d, "a", "b", "b_test.yaml")) require.NoError(t, err) require.Equal(t, `e: f g: h: - i # has a list - j `, string(b)) }) } // TestLocalPackageWriter_Write_keepReaderAnnotations tests: // - ReaderAnnotations are kept when writing the Resources func TestLocalPackageWriter_Write_keepReaderAnnotations(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() w := LocalPackageWriter{ PackagePath: d, KeepReaderAnnotations: true, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err := w.Write([]*yaml.RNode{node2, node1, node3}) require.NoError(t, err) b, err := fs.ReadFile(filepath.Join(d, "a", "b", "a_test.yaml")) require.NoError(t, err) require.Equal(t, `a: b #first metadata: annotations: config.kubernetes.io/index: "0" config.kubernetes.io/path: "a/b/a_test.yaml" internal.config.kubernetes.io/path: 'a/b/a_test.yaml' internal.config.kubernetes.io/index: '0' --- c: d # second metadata: annotations: config.kubernetes.io/index: "1" config.kubernetes.io/path: "a/b/a_test.yaml" internal.config.kubernetes.io/path: 'a/b/a_test.yaml' internal.config.kubernetes.io/index: '1' `, string(b)) b, err = fs.ReadFile(filepath.Join(d, "a", "b", "b_test.yaml")) require.NoError(t, err) require.Equal(t, `e: f g: h: - i # has a list - j metadata: annotations: config.kubernetes.io/index: "0" config.kubernetes.io/path: "a/b/b_test.yaml" internal.config.kubernetes.io/path: 'a/b/b_test.yaml' internal.config.kubernetes.io/index: '0' `, string(b)) }) } // TestLocalPackageWriter_Write_clearAnnotations tests: // - ClearAnnotations are removed from Resources func TestLocalPackageWriter_Write_clearAnnotations(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() w := LocalPackageWriter{ PackagePath: d, ClearAnnotations: []string{"config.kubernetes.io/mode"}, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err := w.Write([]*yaml.RNode{node2, node1, node3}) require.NoError(t, err) b, err := fs.ReadFile(filepath.Join(d, "a", "b", "a_test.yaml")) require.NoError(t, err) require.Equal(t, `a: b #first --- c: d # second `, string(b)) b, err = fs.ReadFile(filepath.Join(d, "a", "b", "b_test.yaml")) require.NoError(t, err) require.Equal(t, `e: f g: h: - i # has a list - j `, string(b)) }) } // TestLocalPackageWriter_Write_failRelativePath tests: // - If a relative path above the package is defined, write fails func TestLocalPackageWriter_Write_failRelativePath(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() node4, err := yaml.Parse(`e: f g: h: - i # has a list - j metadata: annotations: config.kubernetes.io/index: 0 config.kubernetes.io/path: "a/b/../../../b_test.yaml" `) require.NoError(t, err) w := LocalPackageWriter{ PackagePath: d, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err = w.Write([]*yaml.RNode{node2, node1, node3, node4}) if assert.Error(t, err) { assert.Contains(t, err.Error(), "resource must be written under package") } }) } // TestLocalPackageWriter_Write_invalidIndex tests: // - If a non-int index is given, fail func TestLocalPackageWriter_Write_invalidIndex(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() node4, err := yaml.Parse(`e: f g: h: - i # has a list - j metadata: annotations: config.kubernetes.io/index: a config.kubernetes.io/path: "a/b/b_test.yaml" # use a different path, should still collide `) require.NoError(t, err) w := LocalPackageWriter{ PackagePath: d, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err = w.Write([]*yaml.RNode{node2, node1, node3, node4}) if assert.Error(t, err) { assert.Contains(t, err.Error(), "unable to parse config.kubernetes.io/index") } }) } // TestLocalPackageWriter_Write_absPath tests: // - If config.kubernetes.io/path is absolute, fail func TestLocalPackageWriter_Write_absPath(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() d = filepath.ToSlash(d) n4 := fmt.Sprintf(`e: f g: h: - i # has a list - j metadata: annotations: config.kubernetes.io/index: a config.kubernetes.io/path: "%s/a/b/b_test.yaml" # use a different path, should still collide `, d) node4, err := yaml.Parse(n4) testutil.AssertNoError(t, err, n4) w := LocalPackageWriter{ PackagePath: d, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err = w.Write([]*yaml.RNode{node2, node1, node3, node4}) testutil.AssertErrorContains(t, err, "package paths may not be absolute paths") }) } // TestLocalPackageWriter_Write_missingPath tests: // - If config.kubernetes.io/path or index are missing, then default them func TestLocalPackageWriter_Write_missingAnnotations(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() node4String := `e: f g: h: - i # has a list - j kind: Foo metadata: name: bar ` node4, err := yaml.Parse(node4String) require.NoError(t, err) w := LocalPackageWriter{ PackagePath: d, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err = w.Write([]*yaml.RNode{node2, node1, node3, node4}) require.NoError(t, err) b, err := fs.ReadFile(filepath.Join(d, "foo_bar.yaml")) require.NoError(t, err) require.Equal(t, node4String, string(b)) }) } // TestLocalPackageWriter_Write_pathIsDir tests: // - If config.kubernetes.io/path is a directory, fail func TestLocalPackageWriter_Write_pathIsDir(t *testing.T) { testWriterOnDiskAndOnMem(t, func(t *testing.T, fs filesys.FileSystem) { t.Helper() d, node1, node2, node3, cleanup := getWriterInputs(t, fs) defer cleanup() node4, err := yaml.Parse(`e: f g: h: - i # has a list - j metadata: annotations: config.kubernetes.io/path: a/ config.kubernetes.io/index: "0" `) require.NoError(t, err) w := LocalPackageWriter{ PackagePath: d, FileSystem: filesys.FileSystemOrOnDisk{FileSystem: fs}, } err = w.Write([]*yaml.RNode{node2, node1, node3, node4}) require.Error(t, err) require.Contains(t, err.Error(), "config.kubernetes.io/path cannot be a directory") }) } func testWriterOnDiskAndOnMem(t *testing.T, f func(t *testing.T, fs filesys.FileSystem)) { t.Helper() t.Run("on_disk", func(t *testing.T) { f(t, filesys.MakeFsOnDisk()) }) // TODO: Once fsnode supports Windows, these tests should also be run. if runtime.GOOS != "windows" { t.Run("on_mem", func(t *testing.T) { f(t, filesys.MakeFsInMemory()) }) } } func getWriterInputs(t *testing.T, mockFS filesys.FileSystem) (string, *yaml.RNode, *yaml.RNode, *yaml.RNode, func()) { t.Helper() node1, err := yaml.Parse(`a: b #first metadata: annotations: config.kubernetes.io/index: "0" config.kubernetes.io/path: "a/b/a_test.yaml" `) require.NoError(t, err) node2, err := yaml.Parse(`c: d # second metadata: annotations: config.kubernetes.io/index: "1" config.kubernetes.io/path: "a/b/a_test.yaml" `) require.NoError(t, err) node3, err := yaml.Parse(`e: f g: h: - i # has a list - j metadata: annotations: config.kubernetes.io/index: "0" config.kubernetes.io/path: "a/b/b_test.yaml" `) require.NoError(t, err) // These two lines are similar to calling os.MkdirTemp, but we don't actually create any directory. rand.Seed(time.Now().Unix()) path := filepath.Join(os.TempDir(), fmt.Sprintf("kyaml-test%d", rand.Int31())) //nolint:gosec require.NoError(t, mockFS.MkdirAll(filepath.Join(path, "a"))) return path, node1, node2, node3, func() { require.NoError(t, mockFS.RemoveAll(path)) } }