From f7613631d15bbe1e63e54c56b9b00ddf03e0bacb Mon Sep 17 00:00:00 2001 From: jregan Date: Tue, 17 Nov 2020 16:53:32 -0800 Subject: [PATCH] Add some kyaml filters for k8s metadata. --- kyaml/yaml/kfns.go | 68 +++++++++++++++++++++++++++++++++++++++++ kyaml/yaml/kfns_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/kyaml/yaml/kfns.go b/kyaml/yaml/kfns.go index b01a156ab..69e8eb94f 100644 --- a/kyaml/yaml/kfns.go +++ b/kyaml/yaml/kfns.go @@ -4,6 +4,10 @@ package yaml import ( + "fmt" + "strings" + "unicode/utf8" + "gopkg.in/yaml.v3" "sigs.k8s.io/kustomize/kyaml/errors" ) @@ -40,6 +44,70 @@ func ClearEmptyAnnotations(rn *RNode) error { return nil } +// k8sDataSetter place key value pairs in either a 'data' or 'binaryData' field. +// Useful for creating ConfigMaps and Secrets. +type k8sDataSetter struct { + Key string `yaml:"key,omitempty"` + Value string `yaml:"value,omitempty"` + ProtectExisting bool `yaml:"protectExisting,omitempty"` +} + +func (s k8sDataSetter) Filter(rn *RNode) (*RNode, error) { + if !utf8.Valid([]byte(s.Value)) { + // Core k8s ConfigMaps store k,v pairs with 'v' passing the above utf8 + // test in a mapping field called "data" as a string. Pairs with a 'v' + // failing this test go into a field called binaryData as a []byte. + // TODO: support this distinction in kyaml with NodeTagBytes? + return nil, fmt.Errorf( + "key '%s' appears to have non-utf8 data; "+ + "binaryData field not yet supported", s.Key) + } + keyNode, err := rn.Pipe(Lookup(DataField, s.Key)) + if err != nil { + return nil, err + } + if keyNode != nil && s.ProtectExisting { + return nil, fmt.Errorf( + "protecting existing %s='%s' against attempt to add new value '%s'", + s.Key, strings.TrimSpace(keyNode.MustString()), s.Value) + } + v := NewScalarRNode(s.Value) + v.YNode().Tag = NodeTagString + // Add quotes? + // v.YNode().Style = yaml.SingleQuotedStyle + _, err = rn.Pipe( + LookupCreate(yaml.MappingNode, DataField), SetField(s.Key, v)) + return rn, err +} + +func SetK8sData(key, value string) k8sDataSetter { + return k8sDataSetter{Key: key, Value: value, ProtectExisting: true} +} + +// k8sMetaSetter sets a name at metadata.{key}. +// Creates metadata if does not exist. +type k8sMetaSetter struct { + Key string `yaml:"key,omitempty"` + Value string `yaml:"value,omitempty"` +} + +func (s k8sMetaSetter) Filter(rn *RNode) (*RNode, error) { + v := NewScalarRNode(s.Value) + v.YNode().Tag = NodeTagString + _, err := rn.Pipe( + PathGetter{Path: []string{MetadataField}, Create: yaml.MappingNode}, + FieldSetter{Name: s.Key, Value: v}) + return rn, err +} + +func SetK8sName(value string) k8sMetaSetter { + return k8sMetaSetter{Key: NameField, Value: value} +} + +func SetK8sNamespace(value string) k8sMetaSetter { + return k8sMetaSetter{Key: NamespaceField, Value: value} +} + // AnnotationSetter sets an annotation at metadata.annotations. // Creates metadata.annotations if does not exist. type AnnotationSetter struct { diff --git a/kyaml/yaml/kfns_test.go b/kyaml/yaml/kfns_test.go index ceda72511..8bb18319b 100644 --- a/kyaml/yaml/kfns_test.go +++ b/kyaml/yaml/kfns_test.go @@ -5,6 +5,8 @@ package yaml import ( "testing" + + "github.com/stretchr/testify/assert" ) var input = `apiVersion: v1 @@ -16,6 +18,72 @@ data: enableRisky: "false" ` +func TestSetK8sData(t *testing.T) { + rn := MustParse(`apiVersion: v1 +kind: ConfigMap +data: + altGreeting: "Good Morning!" +`) + _, err := rn.Pipe( + SetK8sData("foo", "bar"), + SetK8sData("fruit", "apple"), + SetK8sData("veggie", "celery")) + assert.NoError(t, err) + output := rn.MustString() + + expected := `apiVersion: v1 +kind: ConfigMap +data: + altGreeting: "Good Morning!" + foo: bar + fruit: apple + veggie: celery +` + if output != expected { + t.Fatalf("expected \n%s\nbut got \n%s\n", expected, output) + } +} + +func TestSetK8sDataForbidOverwrite(t *testing.T) { + rn := MustParse(`apiVersion: v1 +kind: ConfigMap +data: + altGreeting: "Good Morning!" +`) + _, err := rn.Pipe( + SetK8sData("foo", "bar"), + SetK8sData("altGreeting", "hey"), + SetK8sData("veggie", "celery")) + assert.EqualError( + t, err, "protecting existing altGreeting='\"Good Morning!\"' "+ + "against attempt to add new value 'hey'") +} + +func TestSetMeta(t *testing.T) { + rn := MustParse(`apiVersion: v1 +kind: ConfigMap +data: + altGreeting: "Good Morning!" +`) + _, err := rn.Pipe(SetK8sName("foo"), SetK8sNamespace("bar")) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + output := rn.MustString() + + expected := `apiVersion: v1 +kind: ConfigMap +data: + altGreeting: "Good Morning!" +metadata: + name: foo + namespace: bar +` + if output != expected { + t.Fatalf("expected \n%s\nbut got \n%s\n", expected, output) + } +} + func TestSetLabel1(t *testing.T) { rn := MustParse(input) _, err := rn.Pipe(SetLabel("foo", "bar"))