Merge pull request #3085 from mikeyrcamp/feat/add-edit-transformer

Add kustomize edit add/remove transformer command #3053
This commit is contained in:
Jeff Regan
2020-10-19 15:04:12 -07:00
committed by GitHub
8 changed files with 506 additions and 85 deletions

View File

@@ -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)
}

View File

@@ -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())
}
}

View File

@@ -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 <filepath>
`,
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
}

View File

@@ -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 <filepath>
`,
Args: cobra.MinimumNArgs(1),
}
@@ -38,6 +41,7 @@ func NewCmdRemove(
newCmdRemoveLabel(fSys, v.MakeLabelNameValidator()),
newCmdRemoveAnnotation(fSys, v.MakeAnnotationNameValidator()),
newCmdRemovePatch(fSys),
newCmdRemoveTransformer(fSys),
)
return c
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}
}
})
}
}