From a6a061215fe9b65f233a43c33649c1366fd81e9f Mon Sep 17 00:00:00 2001 From: Mike Camp Date: Sun, 11 Oct 2020 13:56:11 -0400 Subject: [PATCH] Add kustomize edit add/remove transformer command #3053 Similar to edit add/remove patch these commands add the ability to add or remove a transformer file path from the kustomization.yaml. Refactored the "remove resource" and new "remove transformer" tests into a common testRemoveCommand function to prevent code duplication. --- .../commands/edit/add/addtransformer.go | 79 ++++++++++++ .../commands/edit/add/addtransformer_test.go | 109 ++++++++++++++++ kustomize/internal/commands/edit/add/all.go | 4 + .../internal/commands/edit/remove/all.go | 4 + .../edit/remove/removeresource_test.go | 119 +++++------------- .../commands/edit/remove/removetransformer.go | 83 ++++++++++++ .../edit/remove/removetransformer_test.go | 108 ++++++++++++++++ .../commands/edit/remove_test/removetest.go | 85 +++++++++++++ 8 files changed, 506 insertions(+), 85 deletions(-) create mode 100644 kustomize/internal/commands/edit/add/addtransformer.go create mode 100644 kustomize/internal/commands/edit/add/addtransformer_test.go create mode 100644 kustomize/internal/commands/edit/remove/removetransformer.go create mode 100644 kustomize/internal/commands/edit/remove/removetransformer_test.go create mode 100644 kustomize/internal/commands/edit/remove_test/removetest.go diff --git a/kustomize/internal/commands/edit/add/addtransformer.go b/kustomize/internal/commands/edit/add/addtransformer.go new file mode 100644 index 000000000..b9eb249e4 --- /dev/null +++ b/kustomize/internal/commands/edit/add/addtransformer.go @@ -0,0 +1,79 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package add + +import ( + "errors" + "log" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/api/filesys" + "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/kustfile" + "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/util" +) + +type addTransformerOptions struct { + transformerFilePaths []string +} + +// newCmdAddTransformer adds the name of a file containing a transformer +// configuration to the kustomization file. +func newCmdAddTransformer(fSys filesys.FileSystem) *cobra.Command { + var o addTransformerOptions + + cmd := &cobra.Command{ + Use: "transformer", + Short: "Add the name of a file containing a transformer configuration to the kustomization file.", + Example: ` + add transformer {filepath}`, + RunE: func(cmd *cobra.Command, args []string) error { + err := o.Validate(args) + if err != nil { + return err + } + return o.RunAddTransformer(fSys) + }, + } + return cmd +} + +// Validate validates add transformer command. +func (o *addTransformerOptions) Validate(args []string) error { + if len(args) == 0 { + return errors.New("must specify a transformer file") + } + o.transformerFilePaths = args + return nil +} + +// RunAddTransformer runs add transformer command (do real work). +func (o *addTransformerOptions) RunAddTransformer(fSys filesys.FileSystem) error { + transformers, err := util.GlobPatterns(fSys, o.transformerFilePaths) + if err != nil { + return err + } + if len(transformers) == 0 { + return nil + } + + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + + m, err := mf.Read() + if err != nil { + return err + } + + for _, t := range transformers { + if kustfile.StringInSlice(t, m.Transformers) { + log.Printf("transformer %s already in kustomization file", t) + continue + } + m.Transformers = append(m.Transformers, t) + } + + return mf.Write(m) +} diff --git a/kustomize/internal/commands/edit/add/addtransformer_test.go b/kustomize/internal/commands/edit/add/addtransformer_test.go new file mode 100644 index 000000000..d9e27a0f7 --- /dev/null +++ b/kustomize/internal/commands/edit/add/addtransformer_test.go @@ -0,0 +1,109 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package add + +import ( + "fmt" + "strings" + "testing" + + "sigs.k8s.io/kustomize/api/filesys" + "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/kustfile" + testutils_test "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/testutils" +) + +const ( + transformerFileName = "myWonderfulTransformer.yaml" + transformerFileContent = ` +Lorem ipsum dolor sit amet, consectetur adipiscing elit, +sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. +` +) + +func TestAddTransformerHappyPath(t *testing.T) { + fSys := filesys.MakeEmptyDirInMemory() + fSys.WriteFile(transformerFileName, []byte(transformerFileContent)) + fSys.WriteFile(transformerFileName+"another", []byte(transformerFileContent)) + testutils_test.WriteTestKustomization(fSys) + + cmd := newCmdAddTransformer(fSys) + args := []string{transformerFileName + "*"} + err := cmd.RunE(cmd, args) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + content, err := testutils_test.ReadTestKustomization(fSys) + if err != nil { + t.Errorf("unexpected read error: %v", err) + } + if !strings.Contains(string(content), transformerFileName) { + t.Errorf("expected transformer name in kustomization") + } + if !strings.Contains(string(content), transformerFileName+"another") { + t.Errorf("expected transformer name in kustomization") + } +} + +func TestAddTransformerAlreadyThere(t *testing.T) { + fSys := filesys.MakeEmptyDirInMemory() + fSys.WriteFile(transformerFileName, []byte(transformerFileName)) + testutils_test.WriteTestKustomization(fSys) + + cmd := newCmdAddTransformer(fSys) + args := []string{transformerFileName} + err := cmd.RunE(cmd, args) + if err != nil { + t.Fatalf("unexpected cmd error: %v", err) + } + + // adding an existing transformer shouldn't return an error + err = cmd.RunE(cmd, args) + if err != nil { + t.Errorf("unexpected cmd error: %v", err) + } + + // There can be only one. May it be the... + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + t.Fatalf("error retrieving kustomization file: %v", err) + } + m, err := mf.Read() + if err != nil { + t.Fatalf("error reading kustomization file: %v", err) + } + + if len(m.Transformers) != 1 || m.Transformers[0] != transformerFileName { + t.Errorf("expected transformers [%s]; got transformers [%s]", transformerFileName, strings.Join(m.Transformers, ",")) + } +} + +func TestAddTransformerNoArgs(t *testing.T) { + fSys := filesys.MakeFsInMemory() + + cmd := newCmdAddTransformer(fSys) + err := cmd.Execute() + if err == nil { + t.Errorf("expected error: %v", err) + } + if err.Error() != "must specify a transformer file" { + t.Errorf("incorrect error: %v", err.Error()) + } +} + +func TestAddTransformerMissingKustomizationYAML(t *testing.T) { + fSys := filesys.MakeEmptyDirInMemory() + fSys.WriteFile(transformerFileName, []byte(transformerFileContent)) + fSys.WriteFile(transformerFileName+"another", []byte(transformerFileContent)) + + cmd := newCmdAddTransformer(fSys) + args := []string{transformerFileName + "*"} + err := cmd.RunE(cmd, args) + if err == nil { + t.Errorf("expected error: %v", err) + } + fmt.Println(err.Error()) + if err.Error() != "Missing kustomization file 'kustomization.yaml'.\n" { + t.Errorf("incorrect error: %v", err.Error()) + } +} diff --git a/kustomize/internal/commands/edit/add/all.go b/kustomize/internal/commands/edit/add/all.go index f5190acb5..e6ecbc05d 100644 --- a/kustomize/internal/commands/edit/add/all.go +++ b/kustomize/internal/commands/edit/add/all.go @@ -43,6 +43,9 @@ func NewCmdAdd( # Adds one or more commonAnnotations to the kustomization kustomize edit add annotation {annotationKey1:annotationValue1},{annotationKey2:annotationValue2} + + # Adds a transformer configuration to the kustomization + kustomize edit add transformer `, Args: cobra.MinimumNArgs(1), } @@ -55,6 +58,7 @@ func NewCmdAdd( newCmdAddBase(fSys), newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()), newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()), + newCmdAddTransformer(fSys), ) return c } diff --git a/kustomize/internal/commands/edit/remove/all.go b/kustomize/internal/commands/edit/remove/all.go index 014275e74..78be733f0 100644 --- a/kustomize/internal/commands/edit/remove/all.go +++ b/kustomize/internal/commands/edit/remove/all.go @@ -30,6 +30,9 @@ func NewCmdRemove( # Removes one or more commonAnnotations from the kustomization file kustomize edit remove annotation {annotationKey1},{annotationKey2} + + # Removes one or more transformers from the kustomization file + kustomize edit remove transformer `, Args: cobra.MinimumNArgs(1), } @@ -38,6 +41,7 @@ func NewCmdRemove( newCmdRemoveLabel(fSys, v.MakeLabelNameValidator()), newCmdRemoveAnnotation(fSys, v.MakeAnnotationNameValidator()), newCmdRemovePatch(fSys), + newCmdRemoveTransformer(fSys), ) return c } diff --git a/kustomize/internal/commands/edit/remove/removeresource_test.go b/kustomize/internal/commands/edit/remove/removeresource_test.go index fd64305de..db97ce767 100644 --- a/kustomize/internal/commands/edit/remove/removeresource_test.go +++ b/kustomize/internal/commands/edit/remove/removeresource_test.go @@ -5,65 +5,49 @@ package remove import ( "errors" - "fmt" - "strings" "testing" - "sigs.k8s.io/kustomize/api/filesys" - testutils_test "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/testutils" + "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/edit/remove_test" ) func TestRemoveResources(t *testing.T) { - type given struct { - resources []string - removeArgs []string - } - type expected struct { - resources []string - deleted []string - err error - } - testCases := []struct { - description string - given given - expected expected - }{ + testCases := []remove_test.Case{ { - description: "remove resource", - given: given{ - resources: []string{ + Description: "remove resources", + Given: remove_test.Given{ + Items: []string{ "resource1.yaml", "resource2.yaml", "resource3.yaml", }, - removeArgs: []string{"resource1.yaml"}, + RemoveArgs: []string{"resource1.yaml"}, }, - expected: expected{ - resources: []string{ + Expected: remove_test.Expected{ + Items: []string{ "resource2.yaml", "resource3.yaml", }, - deleted: []string{ + Deleted: []string{ "resource1.yaml", }, }, }, { - description: "remove resources with pattern", - given: given{ - resources: []string{ + Description: "remove resource with pattern", + Given: remove_test.Given{ + Items: []string{ "foo/resource1.yaml", "foo/resource2.yaml", "foo/resource3.yaml", "do/not/deleteme/please.yaml", }, - removeArgs: []string{"foo/resource*.yaml"}, + RemoveArgs: []string{"foo/resource*.yaml"}, }, - expected: expected{ - resources: []string{ + Expected: remove_test.Expected{ + Items: []string{ "do/not/deleteme/please.yaml", }, - deleted: []string{ + Deleted: []string{ "foo/resource1.yaml", "foo/resource2.yaml", "foo/resource3.yaml", @@ -71,17 +55,17 @@ func TestRemoveResources(t *testing.T) { }, }, { - description: "nothing found to remove", - given: given{ - resources: []string{ + Description: "nothing found to remove", + Given: remove_test.Given{ + Items: []string{ "resource1.yaml", "resource2.yaml", "resource3.yaml", }, - removeArgs: []string{"foo"}, + RemoveArgs: []string{"foo"}, }, - expected: expected{ - resources: []string{ + Expected: remove_test.Expected{ + Items: []string{ "resource2.yaml", "resource3.yaml", "resource1.yaml", @@ -89,32 +73,32 @@ func TestRemoveResources(t *testing.T) { }, }, { - description: "no arguments", - given: given{}, - expected: expected{ - err: errors.New("must specify a resource file"), + Description: "no arguments", + Given: remove_test.Given{}, + Expected: remove_test.Expected{ + Err: errors.New("must specify a resource file"), }, }, { - description: "remove with multiple pattern arguments", - given: given{ - resources: []string{ + Description: "remove with multiple pattern arguments", + Given: remove_test.Given{ + Items: []string{ "foo/foo.yaml", "bar/bar.yaml", "resource3.yaml", "do/not/deleteme/please.yaml", }, - removeArgs: []string{ + RemoveArgs: []string{ "foo/*.*", "bar/*.*", "res*.yaml", }, }, - expected: expected{ - resources: []string{ + Expected: remove_test.Expected{ + Items: []string{ "do/not/deleteme/please.yaml", }, - deleted: []string{ + Deleted: []string{ "foo/foo.yaml", "bar/bar.yaml", "resource3.yaml", @@ -123,40 +107,5 @@ func TestRemoveResources(t *testing.T) { }, } - for _, tc := range testCases { - t.Run(tc.description, func(t *testing.T) { - fSys := filesys.MakeFsInMemory() - testutils_test.WriteTestKustomizationWith( - fSys, - []byte(fmt.Sprintf( - "resources:\n - %s", strings.Join(tc.given.resources, "\n - ")))) - cmd := newCmdRemoveResource(fSys) - err := cmd.RunE(cmd, tc.given.removeArgs) - if err != nil && tc.expected.err == nil { - - t.Errorf("unexpected cmd error: %v", err) - } else if tc.expected.err != nil { - if err.Error() != tc.expected.err.Error() { - t.Errorf("expected error did not occurred. Expected: %v. Actual: %v", tc.expected.err, err) - } - return - } - content, err := testutils_test.ReadTestKustomization(fSys) - if err != nil { - t.Errorf("unexpected read error: %v", err) - } - - for _, resourceFileName := range tc.expected.resources { - if !strings.Contains(string(content), resourceFileName) { - t.Errorf("expected resource not found in kustomization file.\nResource: %s\nKustomization file:\n%s", resourceFileName, content) - } - } - for _, resourceFileName := range tc.expected.deleted { - if strings.Contains(string(content), resourceFileName) { - t.Errorf("expected deleted resource found in kustomization file. Resource: %s\nKustomization file:\n%s", resourceFileName, content) - } - } - - }) - } + remove_test.ExecuteTestCases(t, testCases, "resources", newCmdRemoveResource) } diff --git a/kustomize/internal/commands/edit/remove/removetransformer.go b/kustomize/internal/commands/edit/remove/removetransformer.go new file mode 100644 index 000000000..0036dd868 --- /dev/null +++ b/kustomize/internal/commands/edit/remove/removetransformer.go @@ -0,0 +1,83 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package remove + +import ( + "errors" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/api/filesys" + "sigs.k8s.io/kustomize/api/konfig" + "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/kustfile" +) + +type removeTransformerOptions struct { + transformerFilePaths []string +} + +// newCmdRemoveTransformer remove the name of a file containing a transformer to the kustomization file. +func newCmdRemoveTransformer(fSys filesys.FileSystem) *cobra.Command { + var o removeTransformerOptions + + cmd := &cobra.Command{ + Use: "transformer", + Short: "Removes one or more transformers from " + + konfig.DefaultKustomizationFileName(), + Example: ` + remove transformer my-transformer.yml + remove transformer transformer1.yml transformer2.yml transformer3.yml + remove transformer transformers/*.yml + `, + RunE: func(cmd *cobra.Command, args []string) error { + err := o.Validate(args) + if err != nil { + return err + } + return o.RunRemoveTransformer(fSys) + }, + } + return cmd +} + +// Validate validates removeTransformer command. +func (o *removeTransformerOptions) Validate(args []string) error { + if len(args) == 0 { + return errors.New("must specify a transformer file") + } + o.transformerFilePaths = args + return nil +} + +// RunRemoveTransformer runs Transformer command (do real work). +func (o *removeTransformerOptions) RunRemoveTransformer(fSys filesys.FileSystem) error { + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + + m, err := mf.Read() + if err != nil { + return err + } + + transformers, err := globPatterns(m.Transformers, o.transformerFilePaths) + if err != nil { + return err + } + + if len(transformers) == 0 { + return nil + } + + newTransformers := make([]string, 0, len(m.Transformers)) + for _, transformer := range m.Transformers { + if kustfile.StringInSlice(transformer, transformers) { + continue + } + newTransformers = append(newTransformers, transformer) + } + + m.Transformers = newTransformers + return mf.Write(m) +} diff --git a/kustomize/internal/commands/edit/remove/removetransformer_test.go b/kustomize/internal/commands/edit/remove/removetransformer_test.go new file mode 100644 index 000000000..98a4315df --- /dev/null +++ b/kustomize/internal/commands/edit/remove/removetransformer_test.go @@ -0,0 +1,108 @@ +package remove + +import ( + "testing" + + "github.com/pkg/errors" + "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/edit/remove_test" +) + +func TestRemoveTransformer(t *testing.T) { + testCases := []remove_test.Case{ + { + Description: "remove transformers", + Given: remove_test.Given{ + Items: []string{ + "transformer1.yaml", + "transformer2.yaml", + "transformer3.yaml", + }, + RemoveArgs: []string{"transformer1.yaml"}, + }, + Expected: remove_test.Expected{ + Items: []string{ + "transformer2.yaml", + "transformer3.yaml", + }, + Deleted: []string{ + "transformer1.yaml", + }, + }, + }, + { + Description: "remove transformer with pattern", + Given: remove_test.Given{ + Items: []string{ + "foo/transformer1.yaml", + "foo/transformer2.yaml", + "foo/transformer3.yaml", + "do/not/deleteme/please.yaml", + }, + RemoveArgs: []string{"foo/transformer*.yaml"}, + }, + Expected: remove_test.Expected{ + Items: []string{ + "do/not/deleteme/please.yaml", + }, + Deleted: []string{ + "foo/transformer1.yaml", + "foo/transformer2.yaml", + "foo/transformer3.yaml", + }, + }, + }, + { + Description: "nothing found to remove", + Given: remove_test.Given{ + Items: []string{ + "transformer1.yaml", + "transformer2.yaml", + "transformer3.yaml", + }, + RemoveArgs: []string{"foo"}, + }, + Expected: remove_test.Expected{ + Items: []string{ + "transformer2.yaml", + "transformer3.yaml", + "transformer1.yaml", + }, + }, + }, + { + Description: "no arguments", + Given: remove_test.Given{}, + Expected: remove_test.Expected{ + Err: errors.New("must specify a transformer file"), + }, + }, + { + Description: "remove with multiple pattern arguments", + Given: remove_test.Given{ + Items: []string{ + "foo/foo.yaml", + "bar/bar.yaml", + "transformer3.yaml", + "do/not/deleteme/please.yaml", + }, + RemoveArgs: []string{ + "foo/*.*", + "bar/*.*", + "tra*.yaml", + }, + }, + Expected: remove_test.Expected{ + Items: []string{ + "do/not/deleteme/please.yaml", + }, + Deleted: []string{ + "foo/foo.yaml", + "bar/bar.yaml", + "transformer3.yaml", + }, + }, + }, + } + + remove_test.ExecuteTestCases(t, testCases, "transformers", newCmdRemoveTransformer) +} diff --git a/kustomize/internal/commands/edit/remove_test/removetest.go b/kustomize/internal/commands/edit/remove_test/removetest.go new file mode 100644 index 000000000..7afb30cf9 --- /dev/null +++ b/kustomize/internal/commands/edit/remove_test/removetest.go @@ -0,0 +1,85 @@ +package remove_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/api/filesys" + testutils_test "sigs.k8s.io/kustomize/kustomize/v3/internal/commands/testutils" +) + +// Given represents the provided inputs for the test case. +type Given struct { + // Items is the given input items. + Items []string + // RemoveArgs are the arguments to pass to the remove command. + RemoveArgs []string +} + +// Expected represents the expected outputs of the test case. +type Expected struct { + // Expected is the collection of expected output items. + Items []string + // Deleted is the collection of expected Deleted items (if any). + Deleted []string + // Err represents the error that is expected in the output (if any). + Err error +} + +// Case represents a test case to execute. +type Case struct { + // Description is the description of the test case. + Description string + // Given is the provided inputs for the test case. + Given Given + // Expected is the expected outputs for the test case. + Expected Expected +} + +// ExecuteTestCases executes the provided test cases against the specified command +// for a particular collection (e.g. ) tests a command defined by the provided +// collection Name (e.g. transformers or resources) and newRemoveCmdToTest function. +func ExecuteTestCases(t *testing.T, testCases []Case, collectionName string, + newRemoveCmdToTest func(filesys.FileSystem) *cobra.Command) { + for _, tc := range testCases { + t.Run(tc.Description, func(t *testing.T) { + fSys := filesys.MakeFsInMemory() + testutils_test.WriteTestKustomizationWith( + fSys, + []byte(fmt.Sprintf("%s:\n - %s", + collectionName, + strings.Join(tc.Given.Items, "\n - ")))) + cmd := newRemoveCmdToTest(fSys) + err := cmd.RunE(cmd, tc.Given.RemoveArgs) + if err != nil && tc.Expected.Err == nil { + t.Errorf("unexpected cmd error: %v", err) + } else if tc.Expected.Err != nil { + if err.Error() != tc.Expected.Err.Error() { + t.Errorf("expected error did not occurred. Expected: %v. Actual: %v", + tc.Expected.Err, + err) + } + return + } + content, err := testutils_test.ReadTestKustomization(fSys) + if err != nil { + t.Errorf("unexpected read error: %v", err) + } + + for _, itemFileName := range tc.Expected.Items { + if !strings.Contains(string(content), itemFileName) { + t.Errorf("expected item not found in kustomization file.\nItem: %s\nKustomization file:\n%s", + itemFileName, content) + } + } + for _, itemFileName := range tc.Expected.Deleted { + if strings.Contains(string(content), itemFileName) { + t.Errorf("expected deleted item found in kustomization file. Item: %s\nKustomization file:\n%s", + itemFileName, content) + } + } + }) + } +}