Merge pull request #1237 from taxpon/add-metadata-remover

Add metadata remover
This commit is contained in:
Jeff Regan
2019-06-23 08:56:03 -07:00
committed by GitHub
7 changed files with 590 additions and 2 deletions

View File

@@ -53,6 +53,23 @@ func (v *KustValidator) MakeAnnotationValidator() func(map[string]string) error
}
}
// MakeAnnotationNameValidator returns a MapValidatorFunc using apimachinery.
func (v *KustValidator) MakeAnnotationNameValidator() func([]string) error {
return func(x []string) error {
errs := field.ErrorList{}
fldPath := field.NewPath("field")
for _, k := range x {
for _, msg := range validation.IsQualifiedName(strings.ToLower(k)) {
errs = append(errs, field.Invalid(fldPath, k, msg))
}
}
if len(errs) > 0 {
return errors.New(errs.ToAggregate().Error())
}
return nil
}
}
// MakeLabelValidator returns a MapValidatorFunc using apimachinery.
func (v *KustValidator) MakeLabelValidator() func(map[string]string) error {
return func(x map[string]string) error {
@@ -64,6 +81,21 @@ func (v *KustValidator) MakeLabelValidator() func(map[string]string) error {
}
}
// MakeLabelNameValidator returns a ArrayValidatorFunc using apimachinery.
func (v *KustValidator) MakeLabelNameValidator() func([]string) error {
return func(x []string) error {
errs := field.ErrorList{}
fldPath := field.NewPath("field")
for _, k := range x {
errs = append(errs, v1validation.ValidateLabelName(k, fldPath)...)
}
if len(errs) > 0 {
return errors.New(errs.ToAggregate().Error())
}
return nil
}
}
// ValidateNamespace validates a string is a valid namespace using apimachinery.
func (v *KustValidator) ValidateNamespace(s string) []string {
return validation.IsDNS1123Label(s)

View File

@@ -38,7 +38,7 @@ func NewCmdEdit(
add.NewCmdAdd(fSys, loader.NewFileLoaderAtCwd(v, fSys), kf),
set.NewCmdSet(fSys, v),
fix.NewCmdFix(fSys),
remove.NewCmdRemove(fSys),
remove.NewCmdRemove(fSys, loader.NewFileLoaderAtCwd(v, fSys)),
)
return c
}

View File

@@ -19,10 +19,13 @@ package remove
import (
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/ifc"
)
// NewCmdRemove returns an instance of 'remove' subcommand.
func NewCmdRemove(fsys fs.FileSystem) *cobra.Command {
func NewCmdRemove(
fsys fs.FileSystem,
ldr ifc.Loader) *cobra.Command {
c := &cobra.Command{
Use: "remove",
Short: "Removes items from the kustomization file.",
@@ -31,11 +34,19 @@ func NewCmdRemove(fsys fs.FileSystem) *cobra.Command {
# Removes resources from the kustomization file
kustomize edit remove resource {filepath} {filepath}
kustomize edit remove resource {pattern}
# Removes one or more commonLabels from the kustomization file
kustomize edit remove label {labelKey1},{labelKey2}
# Removes one or more commonAnnotations from the kustomization file
kustomize edit remove annotation {annotationKey1},{annotationKey2}
`,
Args: cobra.MinimumNArgs(1),
}
c.AddCommand(
newCmdRemoveResource(fsys),
newCmdRemoveLabel(fsys, ldr.Validator().MakeLabelNameValidator()),
newCmdRemoveAnnotation(fsys, ldr.Validator().MakeAnnotationNameValidator()),
)
return c
}

View File

@@ -0,0 +1,174 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package remove
import (
"fmt"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/pgmconfig"
"sigs.k8s.io/kustomize/pkg/types"
"strings"
)
// kindOfAdd is the kind of metadata being added: label or annotation
type kindOfAdd int
const (
annotation kindOfAdd = iota
label
)
func (k kindOfAdd) String() string {
kinds := [...]string{
"annotation",
"label",
}
if k < 0 || k > 1 {
return "Unknown metadatakind"
}
return kinds[k]
}
type removeMetadataOptions struct {
ignore bool
metadata []string
arrayValidator func([]string) error
kind kindOfAdd
}
// newCmdRemoveLabel removes one or more commonAnnotations from the kustomization file.
func newCmdRemoveAnnotation(fSys fs.FileSystem, v func([]string) error) *cobra.Command {
var o removeMetadataOptions
o.kind = label
o.arrayValidator = v
cmd := &cobra.Command{
Use: "annotation",
Short: "Removes one or more commonAnnotations from " + pgmconfig.KustomizationFileNames[0],
Example: `
remove annotation {annotationKey1},{annotationKey2}`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.runE(args, fSys, o.removeAnnotations)
},
}
cmd.Flags().BoolVarP(&o.ignore, "ignore-non-existence", "i", false,
"ignore error if the given label doesn't exist",
)
return cmd
}
// newCmdRemoveLabel removes one or more commonLabels from the kustomization file.
func newCmdRemoveLabel(fSys fs.FileSystem, v func([]string) error) *cobra.Command {
var o removeMetadataOptions
o.kind = label
o.arrayValidator = v
cmd := &cobra.Command{
Use: "label",
Short: "Removes one or more commonLabels from " + pgmconfig.KustomizationFileNames[0],
Example: `
remove label {labelKey1},{labelKey2}`,
RunE: func(cmd *cobra.Command, args []string) error {
return o.runE(args, fSys, o.removeLabels)
},
}
cmd.Flags().BoolVarP(&o.ignore, "ignore-non-existence", "i", false,
"ignore error if the given label doesn't exist",
)
return cmd
}
func (o *removeMetadataOptions) runE(
args []string, fSys fs.FileSystem, remover func(*types.Kustomization) error) error {
err := o.validateAndParse(args)
if err != nil {
return err
}
kf, err := kustfile.NewKustomizationFile(fSys)
if err != nil {
return err
}
m, err := kf.Read()
if err != nil {
return err
}
err = remover(m)
if err != nil {
return err
}
return kf.Write(m)
}
// validateAndParse validates `remove` commands and parses them into o.metadata
func (o *removeMetadataOptions) validateAndParse(args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify %s", o.kind)
}
if len(args) > 1 {
return fmt.Errorf("%ss must be comma-separated, with no spaces", o.kind)
}
m, err := o.convertToArray(args[0])
if err != nil {
return err
}
if err = o.arrayValidator(m); err != nil {
return err
}
o.metadata = m
return nil
}
func (o *removeMetadataOptions) convertToArray(arg string) ([]string, error) {
inputs := strings.Split(arg, ",")
result := make([]string, 0, len(inputs))
for _, input := range inputs {
if len(input) == 0 {
return nil, o.makeError(input, "name is empty")
}
result = append(result, input)
}
return result, nil
}
func (o *removeMetadataOptions) removeAnnotations(m *types.Kustomization) error {
if m.CommonAnnotations == nil && !o.ignore {
return fmt.Errorf("commonAnnotations is not defined in kustomization file")
}
return o.removeFromMap(m.CommonAnnotations, annotation)
}
func (o *removeMetadataOptions) removeLabels(m *types.Kustomization) error {
if m.CommonLabels == nil && !o.ignore {
return fmt.Errorf("commonLabels is not defined in kustomization file")
}
return o.removeFromMap(m.CommonLabels, label)
}
func (o *removeMetadataOptions) removeFromMap(m map[string]string, kind kindOfAdd) error {
for _, k := range o.metadata {
if _, ok := m[k]; !ok && !o.ignore {
return fmt.Errorf("%s %s is not defined in kustomization file", kind, k)
}
delete(m, k)
}
return nil
}
func (o *removeMetadataOptions) makeError(input string, message string) error {
return fmt.Errorf("invalid %s: '%s' (%s)", o.kind, input, message)
}

View File

@@ -0,0 +1,351 @@
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package remove
import (
"fmt"
"sigs.k8s.io/kustomize/pkg/commands/kustfile"
"sigs.k8s.io/kustomize/pkg/fs"
"sigs.k8s.io/kustomize/pkg/types"
"sigs.k8s.io/kustomize/pkg/validators"
"strings"
"testing"
)
func makeKustomizationFS() fs.FileSystem {
fakeFS := fs.MakeFakeFS()
commonLabels := []string{"label1: val1", "label2: val2"}
commonAnnotations := []string{"annotation1: val1", "annotation2: val2"}
fakeFS.WriteTestKustomizationWith([]byte(
fmt.Sprintf("commonLabels:\n %s\ncommonAnnotations:\n %s",
strings.Join(commonLabels, "\n "), strings.Join(commonAnnotations, "\n "))))
return fakeFS
}
func readKustomizationFS(t *testing.T, fakeFS fs.FileSystem) *types.Kustomization {
kf, err := kustfile.NewKustomizationFile(fakeFS)
if err != nil {
t.Errorf("unexpected new error %v", err)
}
m, err := kf.Read()
if err != nil {
t.Errorf("unexpected read error %v", err)
}
return m
}
func makeKustomization(t *testing.T) *types.Kustomization {
fakeFS := makeKustomizationFS()
return readKustomizationFS(t, fakeFS)
}
func TestRemoveAnnotation(t *testing.T) {
var o removeMetadataOptions
o.metadata = []string{"annotation1"}
m := makeKustomization(t)
err := o.removeAnnotations(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.removeAnnotations(m)
if err == nil {
t.Errorf("expected not exist in kustomization file error")
}
_, exists := m.CommonAnnotations["annotation1"]
if exists {
t.Errorf("annotation1 must be deleted")
}
_, exists = m.CommonAnnotations["annotation2"]
if !exists {
t.Errorf("annotation2 must exist")
}
}
func TestRemoveAnnotationIgnore(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"annotation3"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveAnnotationNoDefinition(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"annotation1,annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "commonAnnotations is not defined in kustomization file" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveAnnotationNoDefinitionIgnore(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"annotation1,annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveAnnotationNoArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
err := cmd.Execute()
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "must specify label" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveAnnotationInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"nospecialchars%^=@"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != validators.SAD {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveAnnotationMultipleArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"annotation1,annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
splitArgs := strings.Split(args[0], ",")
for _, k := range splitArgs {
if _, exist := m.CommonAnnotations[k]; exist {
t.Errorf("%s must be deleted", k)
}
}
}
func TestRemoveAnnotationMultipleArgsInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveAnnotation(fakeFS, v.ValidatorArray)
args := []string{"annotation1", "annotation2"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "labels must be comma-separated, with no spaces" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabel(t *testing.T) {
var o removeMetadataOptions
o.metadata = []string{"label1"}
m := makeKustomization(t)
err := o.removeLabels(m)
if err != nil {
t.Errorf("unexpected error: could not write to kustomization file")
}
// adding the same test input should not work
err = o.removeLabels(m)
if err == nil {
t.Errorf("expected not exist in kustomization file error")
}
_, exists := m.CommonLabels["label1"]
if exists {
t.Errorf("label1 must be deleted")
}
_, exists = m.CommonLabels["label2"]
if !exists {
t.Errorf("label2 must exist")
}
}
func TestRemoveLabelIgnore(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"label3"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveLabelNoDefinition(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"label1,label2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "commonLabels is not defined in kustomization file" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabelNoDefinitionIgnore(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteTestKustomizationWith([]byte(""))
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
cmd.Flag("ignore-non-existence").Value.Set("true")
args := []string{"label1,label2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
}
func TestRemoveLabelNoArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
err := cmd.Execute()
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "must specify label" {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabelInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"exclamation!"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != validators.SAD {
t.Errorf("incorrect error: %v", err.Error())
}
}
func TestRemoveLabelMultipleArgs(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeHappyMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"label1,label2"}
err := cmd.RunE(cmd, args)
v.VerifyCall()
if err != nil {
t.Errorf("unexpected error %v", err)
}
m := readKustomizationFS(t, fakeFS)
splitArgs := strings.Split(args[0], ",")
for _, k := range splitArgs {
if _, exist := m.CommonLabels[k]; exist {
t.Errorf("%s must be deleted", k)
}
}
}
func TestRemoveLabelMultipleArgsInvalidFormat(t *testing.T) {
fakeFS := makeKustomizationFS()
v := validators.MakeSadMapValidator(t)
cmd := newCmdRemoveLabel(fakeFS, v.ValidatorArray)
args := []string{"label1", "label2"}
err := cmd.RunE(cmd, args)
v.VerifyNoCall()
if err == nil {
t.Errorf("expected an error")
}
if err.Error() != "labels must be comma-separated, with no spaces" {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -12,7 +12,9 @@ import (
// Validator provides functions to validate annotations and labels
type Validator interface {
MakeAnnotationValidator() func(map[string]string) error
MakeAnnotationNameValidator() func([]string) error
MakeLabelValidator() func(map[string]string) error
MakeLabelNameValidator() func([]string) error
ValidateNamespace(string) []string
ErrIfInvalidKey(string) error
IsEnvVarName(k string) error

View File

@@ -50,11 +50,21 @@ func (v *FakeValidator) MakeAnnotationValidator() func(map[string]string) error
return nil
}
// MakeAnnotationNameValidator returns a nil function
func (v *FakeValidator) MakeAnnotationNameValidator() func([]string) error {
return nil
}
// MakeLabelValidator returns a nil function
func (v *FakeValidator) MakeLabelValidator() func(map[string]string) error {
return nil
}
// MakeLabelNameValidator returns a nil function
func (v *FakeValidator) MakeLabelNameValidator() func([]string) error {
return nil
}
// ValidateNamespace validates namespace by regexp
func (v *FakeValidator) ValidateNamespace(s string) []string {
pattern := regexp.MustCompile(`^[a-zA-Z].*`)
@@ -75,6 +85,14 @@ func (v *FakeValidator) Validator(_ map[string]string) error {
return errors.New(SAD)
}
func (v *FakeValidator) ValidatorArray(_ []string) error {
v.called = true
if v.happy {
return nil
}
return errors.New(SAD)
}
// VerifyCall returns true if Validator was used.
func (v *FakeValidator) VerifyCall() {
if !v.called {