From fa89a0ab4de1ab9e1d1ccfe70904526fb06a534d Mon Sep 17 00:00:00 2001 From: Jingfang Liu Date: Tue, 2 Oct 2018 15:09:19 -0700 Subject: [PATCH] Add validator interface --- pkg/commands/addmetadata.go | 7 ++- pkg/commands/commands.go | 20 ++++----- pkg/commands/setnamespace.go | 8 ++-- pkg/commands/setnamespace_test.go | 9 ++-- pkg/ifc/ifc.go | 7 +++ pkg/internal/k8sdeps/validators.go | 60 +++++++++++++++++++++++++ pkg/validators/validators.go | 70 ++++++++++++++++++------------ 7 files changed, 132 insertions(+), 49 deletions(-) create mode 100644 pkg/internal/k8sdeps/validators.go diff --git a/pkg/commands/addmetadata.go b/pkg/commands/addmetadata.go index cb77f7332..e51ef4a79 100644 --- a/pkg/commands/addmetadata.go +++ b/pkg/commands/addmetadata.go @@ -24,7 +24,6 @@ import ( "sigs.k8s.io/kustomize/pkg/constants" "sigs.k8s.io/kustomize/pkg/fs" "sigs.k8s.io/kustomize/pkg/types" - "sigs.k8s.io/kustomize/pkg/validators" ) // kindOfAdd is the kind of metadata being added: label or annotation @@ -48,12 +47,12 @@ func (k kindOfAdd) String() string { type addMetadataOptions struct { metadata map[string]string - mapValidator validators.MapValidatorFunc + mapValidator func(map[string]string) error kind kindOfAdd } // newCmdAddAnnotation adds one or more commonAnnotations to the kustomization file. -func newCmdAddAnnotation(fSys fs.FileSystem, v validators.MapValidatorFunc) *cobra.Command { +func newCmdAddAnnotation(fSys fs.FileSystem, v func(map[string]string) error) *cobra.Command { var o addMetadataOptions o.kind = annotation o.mapValidator = v @@ -70,7 +69,7 @@ func newCmdAddAnnotation(fSys fs.FileSystem, v validators.MapValidatorFunc) *cob } // newCmdAddLabel adds one or more commonLabels to the kustomization file. -func newCmdAddLabel(fSys fs.FileSystem, v validators.MapValidatorFunc) *cobra.Command { +func newCmdAddLabel(fSys fs.FileSystem, v func(map[string]string) error) *cobra.Command { var o addMetadataOptions o.kind = label o.mapValidator = v diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index fbe8f8fa9..acaaf601f 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -24,7 +24,7 @@ import ( "github.com/spf13/cobra" "sigs.k8s.io/kustomize/pkg/fs" - "sigs.k8s.io/kustomize/pkg/validators" + "sigs.k8s.io/kustomize/pkg/ifc" ) // NewDefaultCommand returns the default (aka root) command for kustomize command. @@ -45,7 +45,7 @@ See https://sigs.k8s.io/kustomize c.AddCommand( // TODO: Make consistent API for newCmd* functions. newCmdBuild(stdOut, fsys, k8sdeps.NewKustDecoder()), - newCmdEdit(fsys), + newCmdEdit(fsys, k8sdeps.NewKustValidator()), newCmdConfig(fsys), newCmdVersion(stdOut), ) @@ -58,7 +58,7 @@ See https://sigs.k8s.io/kustomize } // newCmdEdit returns an instance of 'edit' subcommand. -func newCmdEdit(fsys fs.FileSystem) *cobra.Command { +func newCmdEdit(fsys fs.FileSystem, v ifc.Validator) *cobra.Command { c := &cobra.Command{ Use: "edit", Short: "Edits a kustomization file", @@ -73,14 +73,14 @@ func newCmdEdit(fsys fs.FileSystem) *cobra.Command { Args: cobra.MinimumNArgs(1), } c.AddCommand( - newCmdAdd(fsys), - newCmdSet(fsys), + newCmdAdd(fsys, v), + newCmdSet(fsys, v), ) return c } // newAddCommand returns an instance of 'add' subcommand. -func newCmdAdd(fsys fs.FileSystem) *cobra.Command { +func newCmdAdd(fsys fs.FileSystem, v ifc.Validator) *cobra.Command { c := &cobra.Command{ Use: "add", Short: "Adds configmap/resource/patch/base to the kustomization file.", @@ -112,14 +112,14 @@ func newCmdAdd(fsys fs.FileSystem) *cobra.Command { newCmdAddPatch(fsys), newCmdAddConfigMap(fsys), newCmdAddBase(fsys), - newCmdAddLabel(fsys, validators.MakeLabelValidator()), - newCmdAddAnnotation(fsys, validators.MakeAnnotationValidator()), + newCmdAddLabel(fsys, v.MakeLabelValidator()), + newCmdAddAnnotation(fsys, v.MakeAnnotationValidator()), ) return c } // newSetCommand returns an instance of 'set' subcommand. -func newCmdSet(fsys fs.FileSystem) *cobra.Command { +func newCmdSet(fsys fs.FileSystem, v ifc.Validator) *cobra.Command { c := &cobra.Command{ Use: "set", Short: "Sets the value of different fields in kustomization file.", @@ -133,7 +133,7 @@ func newCmdSet(fsys fs.FileSystem) *cobra.Command { c.AddCommand( newCmdSetNamePrefix(fsys), - newCmdSetNamespace(fsys), + newCmdSetNamespace(fsys, v), newCmdSetImageTag(fsys), ) return c diff --git a/pkg/commands/setnamespace.go b/pkg/commands/setnamespace.go index c8f51ab8a..6834ed589 100644 --- a/pkg/commands/setnamespace.go +++ b/pkg/commands/setnamespace.go @@ -22,17 +22,18 @@ import ( "strings" "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/util/validation" "sigs.k8s.io/kustomize/pkg/constants" "sigs.k8s.io/kustomize/pkg/fs" + "sigs.k8s.io/kustomize/pkg/ifc" ) type setNamespaceOptions struct { namespace string + validator ifc.Validator } // newCmdSetNamespace sets the value of the namespace field in the kustomization. -func newCmdSetNamespace(fsys fs.FileSystem) *cobra.Command { +func newCmdSetNamespace(fsys fs.FileSystem, v ifc.Validator) *cobra.Command { var o setNamespaceOptions cmd := &cobra.Command{ @@ -45,6 +46,7 @@ will add the field "namespace: staging" to the kustomization file if it doesn't and overwrite the value with "staging" if the field does exist. `, RunE: func(cmd *cobra.Command, args []string) error { + o.validator = v err := o.Validate(args) if err != nil { return err @@ -61,7 +63,7 @@ func (o *setNamespaceOptions) Validate(args []string) error { return errors.New("must specify exactly one namespace value") } ns := args[0] - if errs := validation.IsDNS1123Label(ns); len(errs) != 0 { + if errs := o.validator.ValidateNamespace(ns); len(errs) != 0 { return fmt.Errorf("%q is not a valid namespace name: %s", ns, strings.Join(errs, ";")) } o.namespace = ns diff --git a/pkg/commands/setnamespace_test.go b/pkg/commands/setnamespace_test.go index a2fdebeca..42ef43e48 100644 --- a/pkg/commands/setnamespace_test.go +++ b/pkg/commands/setnamespace_test.go @@ -23,6 +23,7 @@ import ( "sigs.k8s.io/kustomize/pkg/constants" "sigs.k8s.io/kustomize/pkg/fs" + "sigs.k8s.io/kustomize/pkg/validators" ) const ( @@ -33,7 +34,7 @@ func TestSetNamespaceHappyPath(t *testing.T) { fakeFS := fs.MakeFakeFS() fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent)) - cmd := newCmdSetNamespace(fakeFS) + cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator()) args := []string{goodNamespaceValue} err := cmd.RunE(cmd, args) if err != nil { @@ -53,7 +54,7 @@ func TestSetNamespaceOverride(t *testing.T) { fakeFS := fs.MakeFakeFS() fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent)) - cmd := newCmdSetNamespace(fakeFS) + cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator()) args := []string{goodNamespaceValue} err := cmd.RunE(cmd, args) if err != nil { @@ -77,7 +78,7 @@ func TestSetNamespaceOverride(t *testing.T) { func TestSetNamespaceNoArgs(t *testing.T) { fakeFS := fs.MakeFakeFS() - cmd := newCmdSetNamespace(fakeFS) + cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator()) err := cmd.Execute() if err == nil { t.Errorf("expected error: %v", err) @@ -91,7 +92,7 @@ func TestSetNamespaceInvalid(t *testing.T) { fakeFS := fs.MakeFakeFS() fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent)) - cmd := newCmdSetNamespace(fakeFS) + cmd := newCmdSetNamespace(fakeFS, validators.MakeFakeValidator()) args := []string{"/badnamespace/"} err := cmd.RunE(cmd, args) if err == nil { diff --git a/pkg/ifc/ifc.go b/pkg/ifc/ifc.go index c04b61b41..9ce2b317c 100644 --- a/pkg/ifc/ifc.go +++ b/pkg/ifc/ifc.go @@ -24,3 +24,10 @@ type Decoder interface { // Decode yields the next object from the input, else io.EOF Decode(interface{}) error } + +// Validator provides functions to validate annotations and labels +type Validator interface { + MakeAnnotationValidator() func(map[string]string) error + MakeLabelValidator() func(map[string]string) error + ValidateNamespace(string) []string +} diff --git a/pkg/internal/k8sdeps/validators.go b/pkg/internal/k8sdeps/validators.go new file mode 100644 index 000000000..e6d50d183 --- /dev/null +++ b/pkg/internal/k8sdeps/validators.go @@ -0,0 +1,60 @@ +/* +Copyright 2018 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 k8sdeps + +import ( + "errors" + apivalidation "k8s.io/apimachinery/pkg/api/validation" + v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +// KustValidator validates Labels and annotations by apimachinery +type KustValidator struct{} + +// NewKustValidator returns a KustValidator object +func NewKustValidator() *KustValidator { + return &KustValidator{} +} + +// MakeAnnotationValidator returns a MapValidatorFunc using apimachinery. +func (v *KustValidator) MakeAnnotationValidator() func(map[string]string) error { + return func(x map[string]string) error { + errs := apivalidation.ValidateAnnotations(x, field.NewPath("field")) + if errs != nil { + 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 { + errs := v1validation.ValidateLabels(x, field.NewPath("field")) + if errs != nil { + 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) +} diff --git a/pkg/validators/validators.go b/pkg/validators/validators.go index 989f9c239..1be5c3df2 100644 --- a/pkg/validators/validators.go +++ b/pkg/validators/validators.go @@ -1,38 +1,28 @@ +/* +Copyright 2018 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 validators defines a FakeValidator that can be used in tests package validators import ( "errors" - apivalidation "k8s.io/apimachinery/pkg/api/validation" - v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" - "k8s.io/apimachinery/pkg/util/validation/field" + "regexp" "testing" ) -// MapValidatorFunc returns an error if a map contains errors. -type MapValidatorFunc func(map[string]string) error - -// MakeAnnotationValidator returns a MapValidatorFunc using apimachinery. -func MakeAnnotationValidator() MapValidatorFunc { - return func(x map[string]string) error { - errs := apivalidation.ValidateAnnotations(x, field.NewPath("field")) - if len(errs) > 0 { - return errors.New(errs.ToAggregate().Error()) - } - return nil - } -} - -// MakeLabelValidator returns a MapValidatorFunc using apimachinery. -func MakeLabelValidator() MapValidatorFunc { - return func(x map[string]string) error { - errs := v1validation.ValidateLabels(x, field.NewPath("field")) - if len(errs) > 0 { - return errors.New(errs.ToAggregate().Error()) - } - return nil - } -} - // FakeValidator can be used in tests. type FakeValidator struct { happy bool @@ -53,6 +43,30 @@ func MakeSadMapValidator(t *testing.T) *FakeValidator { return &FakeValidator{happy: false, t: t} } +// MakeFakeValidator makes an empty Fake Validator. +func MakeFakeValidator() *FakeValidator { + return &FakeValidator{} +} + +// MakeAnnotationValidator returns a nil function +func (v *FakeValidator) MakeAnnotationValidator() func(map[string]string) error { + return nil +} + +// MakeLabelValidator returns a nil function +func (v *FakeValidator) MakeLabelValidator() func(map[string]string) error { + return nil +} + +// ValidateNamespace validates namespace by regexp +func (v *FakeValidator) ValidateNamespace(s string) []string { + pattern := regexp.MustCompile(`^[a-zA-Z].*`) + if pattern.MatchString(s) { + return nil + } + return []string{"doesn't match"} +} + // Validator replaces apimachinery validation in tests. // Can be set to fail or succeed to test error handling. // Can confirm if run or not run by surrounding code.