From bcebad1664c2b92846abff54525b42f7b87a577d Mon Sep 17 00:00:00 2001 From: Natasha Sarkar <natashasarkar@google.com> Date: Wed, 26 Jan 2022 15:34:50 -0800 Subject: [PATCH] new command `kustomize edit add buildmetadata` (#4413) * new command kustomize edit add buildmetadata * new commands kustomize edit set buildmetadata and kustomize edit remove buildmetadata --- api/internal/target/kusttarget.go | 6 +- api/types/kustomization.go | 2 + .../commands/edit/add/addbuildmetadata.go | 69 +++++++++++++++++ .../edit/add/addbuildmetadata_test.go | 63 +++++++++++++++ kustomize/commands/edit/add/all.go | 1 + kustomize/commands/edit/remove/all.go | 1 + .../edit/remove/removebuildmetadata.go | 71 +++++++++++++++++ .../edit/remove/removebuildmetadata_test.go | 65 ++++++++++++++++ kustomize/commands/edit/set/all.go | 1 + .../commands/edit/set/setbuildmetadata.go | 63 +++++++++++++++ .../edit/set/setbuildmetadata_test.go | 76 +++++++++++++++++++ .../internal/kustfile/kustomizationfile.go | 1 + .../kustfile/kustomizationfile_test.go | 1 + kustomize/commands/internal/util/util_test.go | 3 + kustomize/commands/internal/util/validate.go | 31 ++++++++ .../prefixtransformer/PrefixTransformer.go | 2 +- 16 files changed, 450 insertions(+), 6 deletions(-) create mode 100644 kustomize/commands/edit/add/addbuildmetadata.go create mode 100644 kustomize/commands/edit/add/addbuildmetadata_test.go create mode 100644 kustomize/commands/edit/remove/removebuildmetadata.go create mode 100644 kustomize/commands/edit/remove/removebuildmetadata_test.go create mode 100644 kustomize/commands/edit/set/setbuildmetadata.go create mode 100644 kustomize/commands/edit/set/setbuildmetadata_test.go create mode 100644 kustomize/commands/internal/util/validate.go diff --git a/api/internal/target/kusttarget.go b/api/internal/target/kusttarget.go index c89e0f7996..2fcfd830db 100644 --- a/api/internal/target/kusttarget.go +++ b/api/internal/target/kusttarget.go @@ -325,11 +325,7 @@ func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error { return err } r = append(r, lts...) - err = ra.Transform(newMultiTransformer(r)) - if err != nil { - return err - } - return nil + return ra.Transform(newMultiTransformer(r)) } func (kt *KustTarget) configureExternalTransformers(transformers []string) ([]*resmap.TransformerWithProperties, error) { diff --git a/api/types/kustomization.go b/api/types/kustomization.go index fb14f191c0..a128c77490 100644 --- a/api/types/kustomization.go +++ b/api/types/kustomization.go @@ -23,6 +23,8 @@ const ( ManagedByLabelOption = "managedByLabel" ) +var BuildMetadataOptions = []string{OriginAnnotations, TransformerAnnotations, ManagedByLabelOption} + // Kustomization holds the information needed to generate customized k8s api resources. type Kustomization struct { TypeMeta `json:",inline" yaml:",inline"` diff --git a/kustomize/commands/edit/add/addbuildmetadata.go b/kustomize/commands/edit/add/addbuildmetadata.go new file mode 100644 index 0000000000..b376238420 --- /dev/null +++ b/kustomize/commands/edit/add/addbuildmetadata.go @@ -0,0 +1,69 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package add + +import ( + "fmt" + + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/util" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +type addBuildMetadataOptions struct { + *util.BuildMetadataValidator + buildMetadataOptions []string +} + +// newCmdAddBuildMetadata adds options to the kustomization's buildMetada field. +func newCmdAddBuildMetadata(fSys filesys.FileSystem) *cobra.Command { + var o addBuildMetadataOptions + + cmd := &cobra.Command{ + Use: "buildmetadata", + Short: "Adds one or more buildMetadata options to the kustomization.yaml in the current directory", + Long: `Adds one or more buildMetadata options to the kustomization.yaml in the current directory. +The following options are valid: + - originAnnotations + - transformerAnnotations + - managedByLabel +originAnnotations will add the annotation config.kubernetes.io/origin to each resource, describing where +each resource originated from. +transformerAnnotations will add the annotation alpha.config.kubernetes.io/transformations to each resource, +describing the transformers that have acted upon the resource. +managedByLabel will add the label app.kubernetes.io/managed-by to each resource, describing which version +of kustomize managed the resource.`, + Example: ` + add buildmetadata {option1},{option2}`, + RunE: func(cmd *cobra.Command, args []string) error { + var err error + o.buildMetadataOptions, err = o.BuildMetadataValidator.Validate(args) + if err != nil { + return err + } + return o.RunAddBuildMetadata(fSys) + }, + } + return cmd +} + +// RunAddBuildMetadata runs addBuildMetadata command (do real work). +func (o *addBuildMetadataOptions) RunAddBuildMetadata(fSys filesys.FileSystem) error { + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + m, err := mf.Read() + if err != nil { + return err + } + for _, opt := range o.buildMetadataOptions { + if kustfile.StringInSlice(opt, m.BuildMetadata) { + return fmt.Errorf("buildMetadata option %s already in kustomization file", opt) + } + m.BuildMetadata = append(m.BuildMetadata, opt) + } + return mf.Write(m) +} diff --git a/kustomize/commands/edit/add/addbuildmetadata_test.go b/kustomize/commands/edit/add/addbuildmetadata_test.go new file mode 100644 index 0000000000..47d91471a6 --- /dev/null +++ b/kustomize/commands/edit/add/addbuildmetadata_test.go @@ -0,0 +1,63 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package add + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/api/types" + testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +func TestAddBuildMetadata(t *testing.T) { + tests := map[string]struct { + input string + args []string + expectedErr string + }{ + "happy path": { + input: ``, + args: []string{strings.Join(types.BuildMetadataOptions, ",")}, + }, + "option already there": { + input: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +buildMetadata: [originAnnotations]`, + args: []string{types.OriginAnnotations}, + expectedErr: "buildMetadata option originAnnotations already in kustomization file", + }, + "invalid option": { + input: ``, + args: []string{"invalid_option"}, + expectedErr: "invalid buildMetadata option: invalid_option", + }, + "too many args": { + input: ``, + args: []string{"option1", "option2"}, + expectedErr: "too many arguments: [option1 option2]; to provide multiple buildMetadata options, please separate options by comma", + }, + } + + for _, tc := range tests { + fSys := filesys.MakeFsInMemory() + testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input)) + cmd := newCmdAddBuildMetadata(fSys) + err := cmd.RunE(cmd, tc.args) + if tc.expectedErr != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErr) + } else { + assert.NoError(t, err) + content, err := testutils_test.ReadTestKustomization(fSys) + assert.NoError(t, err) + for _, opt := range strings.Split(tc.args[0], ",") { + assert.Contains(t, string(content), opt) + } + } + } +} diff --git a/kustomize/commands/edit/add/all.go b/kustomize/commands/edit/add/all.go index e9309e557f..d9605444ce 100644 --- a/kustomize/commands/edit/add/all.go +++ b/kustomize/commands/edit/add/all.go @@ -57,6 +57,7 @@ func NewCmdAdd( newCmdAddSecret(fSys, ldr, rf), newCmdAddConfigMap(fSys, ldr, rf), newCmdAddBase(fSys), + newCmdAddBuildMetadata(fSys), newCmdAddLabel(fSys, ldr.Validator().MakeLabelValidator()), newCmdAddAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()), newCmdAddTransformer(fSys), diff --git a/kustomize/commands/edit/remove/all.go b/kustomize/commands/edit/remove/all.go index 4629b48903..14db55ea1a 100644 --- a/kustomize/commands/edit/remove/all.go +++ b/kustomize/commands/edit/remove/all.go @@ -42,6 +42,7 @@ func NewCmdRemove( newCmdRemoveAnnotation(fSys, v.MakeAnnotationNameValidator()), newCmdRemovePatch(fSys), newCmdRemoveTransformer(fSys), + newCmdRemoveBuildMetadata(fSys), ) return c } diff --git a/kustomize/commands/edit/remove/removebuildmetadata.go b/kustomize/commands/edit/remove/removebuildmetadata.go new file mode 100644 index 0000000000..be918044dd --- /dev/null +++ b/kustomize/commands/edit/remove/removebuildmetadata.go @@ -0,0 +1,71 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package remove + +import ( + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/util" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +type removeBuildMetadataOptions struct { + *util.BuildMetadataValidator + buildMetadataOptions []string +} + +// newCmdRemoveBuildMetadata removes options to the kustomization's buildMetada field. +func newCmdRemoveBuildMetadata(fSys filesys.FileSystem) *cobra.Command { + var o removeBuildMetadataOptions + + cmd := &cobra.Command{ + Use: "buildmetadata", + Short: "Removes one or more buildMetadata options to the kustomization.yaml in the current directory", + Long: `Removes one or more buildMetadata options to the kustomization.yaml in the current directory. +The following options are valid: + - originAnnotations + - transformerAnnotations + - managedByLabel +originAnnotations will remove the annotation config.kubernetes.io/origin to each resource, describing where +each resource originated from. +transformerAnnotations will remove the annotation alpha.config.kubernetes.io/transformations to each resource, +describing the transformers that have acted upon the resource. +managedByLabel will remove the label app.kubernetes.io/managed-by to each resource, describing which version +of kustomize managed the resource.`, + Example: ` + remove buildmetadata {option1},{option2}`, + RunE: func(cmd *cobra.Command, args []string) error { + var err error + o.buildMetadataOptions, err = o.BuildMetadataValidator.Validate(args) + if err != nil { + return err + } + return o.RunRemoveBuildMetadata(fSys) + }, + } + return cmd +} + +// RunRemoveBuildMetadata runs removeBuildMetadata command (do real work). +func (o *removeBuildMetadataOptions) RunRemoveBuildMetadata(fSys filesys.FileSystem) error { + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + m, err := mf.Read() + if err != nil { + return err + } + var newOptions []string + for _, opt := range m.BuildMetadata { + if !kustfile.StringInSlice(opt, o.buildMetadataOptions) { + newOptions = append(newOptions, opt) + } + } + m.BuildMetadata = newOptions + if len(m.BuildMetadata) == 0 { + m.BuildMetadata = nil + } + return mf.Write(m) +} diff --git a/kustomize/commands/edit/remove/removebuildmetadata_test.go b/kustomize/commands/edit/remove/removebuildmetadata_test.go new file mode 100644 index 0000000000..0de7f0b7b0 --- /dev/null +++ b/kustomize/commands/edit/remove/removebuildmetadata_test.go @@ -0,0 +1,65 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package remove + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/api/types" + testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +func TestRemoveBuildMetadata(t *testing.T) { + tests := map[string]struct { + input string + args []string + expectedErr string + }{ + "happy path": { + input: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +buildMetadata: [originAnnotations, transformerAnnotations, managedByLabel]`, + args: []string{types.OriginAnnotations}, + }, + "option already there": { + input: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +buildMetadata: [originAnnotations]`, + args: []string{types.OriginAnnotations}, + }, + "invalid option": { + input: ``, + args: []string{"invalid_option"}, + expectedErr: "invalid buildMetadata option: invalid_option", + }, + "too many args": { + input: ``, + args: []string{"option1", "option2"}, + expectedErr: "too many arguments: [option1 option2]; to provide multiple buildMetadata options, please separate options by comma", + }, + } + + for _, tc := range tests { + fSys := filesys.MakeFsInMemory() + testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input)) + cmd := newCmdRemoveBuildMetadata(fSys) + err := cmd.RunE(cmd, tc.args) + if tc.expectedErr != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErr) + } else { + assert.NoError(t, err) + content, err := testutils_test.ReadTestKustomization(fSys) + assert.NoError(t, err) + for _, opt := range strings.Split(tc.args[0], ",") { + assert.NotContains(t, string(content), opt) + } + } + } +} diff --git a/kustomize/commands/edit/set/all.go b/kustomize/commands/edit/set/all.go index e04f64c1d7..1f62d62a31 100644 --- a/kustomize/commands/edit/set/all.go +++ b/kustomize/commands/edit/set/all.go @@ -30,6 +30,7 @@ func NewCmdSet(fSys filesys.FileSystem, ldr ifc.KvLoader, v ifc.Validator) *cobr newCmdSetNameSuffix(fSys), newCmdSetNamespace(fSys, v), newCmdSetImage(fSys), + newCmdSetBuildMetadata(fSys), newCmdSetReplicas(fSys), newCmdSetLabel(fSys, ldr.Validator().MakeLabelValidator()), newCmdSetAnnotation(fSys, ldr.Validator().MakeAnnotationValidator()), diff --git a/kustomize/commands/edit/set/setbuildmetadata.go b/kustomize/commands/edit/set/setbuildmetadata.go new file mode 100644 index 0000000000..43a7cc1eaf --- /dev/null +++ b/kustomize/commands/edit/set/setbuildmetadata.go @@ -0,0 +1,63 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package set + +import ( + "github.com/spf13/cobra" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/util" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +type setBuildMetadataOptions struct { + *util.BuildMetadataValidator + buildMetadataOptions []string +} + +// newCmdSetBuildMetadata sets options in the kustomization's buildMetada field. +func newCmdSetBuildMetadata(fSys filesys.FileSystem) *cobra.Command { + var o setBuildMetadataOptions + + cmd := &cobra.Command{ + Use: "buildmetadata", + Short: "Sets one or more buildMetadata options to the kustomization.yaml in the current directory", + Long: `Sets one or more buildMetadata options to the kustomization.yaml in the current directory. +Existing options in the buildMetadata field will be replaced entirely by the new options set by this command. +The following options are valid: + - originAnnotations + - transformerAnnotations + - managedByLabel +originAnnotations will add the annotation config.kubernetes.io/origin to each resource, describing where +each resource originated from. +transformerAnnotations will add the annotation alpha.config.kubernetes.io/transformations to each resource, +describing the transformers that have acted upon the resource. +managedByLabel will add the label app.kubernetes.io/managed-by to each resource, describing which version +of kustomize managed the resource.`, + Example: ` + set buildmetadata {option1},{option2}`, + RunE: func(cmd *cobra.Command, args []string) error { + var err error + o.buildMetadataOptions, err = o.BuildMetadataValidator.Validate(args) + if err != nil { + return err + } + return o.RunSetBuildMetadata(fSys) + }, + } + return cmd +} + +// RunSetBuildMetadata runs setBuildMetadata command (do real work). +func (o *setBuildMetadataOptions) RunSetBuildMetadata(fSys filesys.FileSystem) error { + mf, err := kustfile.NewKustomizationFile(fSys) + if err != nil { + return err + } + m, err := mf.Read() + if err != nil { + return err + } + m.BuildMetadata = o.buildMetadataOptions + return mf.Write(m) +} diff --git a/kustomize/commands/edit/set/setbuildmetadata_test.go b/kustomize/commands/edit/set/setbuildmetadata_test.go new file mode 100644 index 0000000000..9802b189c1 --- /dev/null +++ b/kustomize/commands/edit/set/setbuildmetadata_test.go @@ -0,0 +1,76 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package set + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile" + testutils_test "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/testutils" + "sigs.k8s.io/kustomize/kyaml/filesys" +) + +func TestSetBuildMetadata(t *testing.T) { + tests := map[string]struct { + input string + args []string + expectedErr string + }{ + "happy path": { + input: ``, + args: []string{strings.Join(types.BuildMetadataOptions, ",")}, + }, + "option already there": { + input: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +buildMetadata: [originAnnotations]`, + args: []string{types.OriginAnnotations}, + }, + "invalid option": { + input: ``, + args: []string{"invalid_option"}, + expectedErr: "invalid buildMetadata option: invalid_option", + }, + "too many args": { + input: ``, + args: []string{"option1", "option2"}, + expectedErr: "too many arguments: [option1 option2]; to provide multiple buildMetadata options, please separate options by comma", + }, + "remove old options": { + input: ` +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +buildMetadata: [originAnnotations, transformerAnnotations, managedByLabel]`, + args: []string{types.OriginAnnotations}, + }, + } + + for _, tc := range tests { + fSys := filesys.MakeFsInMemory() + testutils_test.WriteTestKustomizationWith(fSys, []byte(tc.input)) + cmd := newCmdSetBuildMetadata(fSys) + err := cmd.RunE(cmd, tc.args) + if tc.expectedErr != "" { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expectedErr) + } else { + assert.NoError(t, err) + content, err := testutils_test.ReadTestKustomization(fSys) + assert.NoError(t, err) + args := strings.Split(tc.args[0], ",") + for _, opt := range args { + assert.Contains(t, string(content), opt) + } + mf, err := kustfile.NewKustomizationFile(fSys) + assert.NoError(t, err) + m, err := mf.Read() + assert.NoError(t, err) + assert.Equal(t, len(m.BuildMetadata), len(args)) + } + } +} diff --git a/kustomize/commands/internal/kustfile/kustomizationfile.go b/kustomize/commands/internal/kustfile/kustomizationfile.go index d9e9493fa1..3d569cee11 100644 --- a/kustomize/commands/internal/kustfile/kustomizationfile.go +++ b/kustomize/commands/internal/kustfile/kustomizationfile.go @@ -67,6 +67,7 @@ func determineFieldOrder() []string { "Inventory", "Components", "OpenAPI", + "BuildMetadata", } // Add deprecated fields here. diff --git a/kustomize/commands/internal/kustfile/kustomizationfile_test.go b/kustomize/commands/internal/kustfile/kustomizationfile_test.go index 760ee34450..0cc0a202ed 100644 --- a/kustomize/commands/internal/kustfile/kustomizationfile_test.go +++ b/kustomize/commands/internal/kustfile/kustomizationfile_test.go @@ -49,6 +49,7 @@ func TestFieldOrder(t *testing.T) { "Inventory", "Components", "OpenAPI", + "BuildMetadata", } actual := determineFieldOrder() if len(expected) != len(actual) { diff --git a/kustomize/commands/internal/util/util_test.go b/kustomize/commands/internal/util/util_test.go index 6fab551039..ebb3742c73 100644 --- a/kustomize/commands/internal/util/util_test.go +++ b/kustomize/commands/internal/util/util_test.go @@ -1,3 +1,6 @@ +// Copyright 2019 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + package util import ( diff --git a/kustomize/commands/internal/util/validate.go b/kustomize/commands/internal/util/validate.go new file mode 100644 index 0000000000..2c45381677 --- /dev/null +++ b/kustomize/commands/internal/util/validate.go @@ -0,0 +1,31 @@ +// Copyright 2022 The Kubernetes Authors. +// SPDX-License-Identifier: Apache-2.0 + +package util + +import ( + "errors" + "fmt" + "strings" + + "sigs.k8s.io/kustomize/api/types" + "sigs.k8s.io/kustomize/kustomize/v4/commands/internal/kustfile" +) + +type BuildMetadataValidator struct{} + +func (b *BuildMetadataValidator) Validate(args []string) ([]string, error) { + if len(args) == 0 { + return nil, errors.New("must specify a buildMetadata option") + } + if len(args) > 1 { + return nil, fmt.Errorf("too many arguments: %s; to provide multiple buildMetadata options, please separate options by comma", args) + } + opts := strings.Split(args[0], ",") + for _, opt := range opts { + if !kustfile.StringInSlice(opt, types.BuildMetadataOptions) { + return nil, fmt.Errorf("invalid buildMetadata option: %s", opt) + } + } + return opts, nil +} diff --git a/plugin/builtin/prefixtransformer/PrefixTransformer.go b/plugin/builtin/prefixtransformer/PrefixTransformer.go index 3051d06367..bb486b82e2 100644 --- a/plugin/builtin/prefixtransformer/PrefixTransformer.go +++ b/plugin/builtin/prefixtransformer/PrefixTransformer.go @@ -71,7 +71,7 @@ func (p *plugin) Transform(m resmap.ResMap) error { if p.Prefix != "" { // TODO: There are multiple transformers that can change a resource's name, and each makes a call to // StorePreviousID(). We should make it so that we only call StorePreviousID once per kustomization layer - // to avoid storing intermediate names between transformations, to prevent intermediate name conflicts. + // to avoid storing intermediate names between transformations, to prevent intermediate name conflicts. r.StorePreviousId() } }