From afa0769e439fff9888a49cded4b9eb94af6900eb Mon Sep 17 00:00:00 2001 From: Soumik Majumder Date: Tue, 14 Sep 2021 17:05:35 +0530 Subject: [PATCH 1/4] Added 'versioned-explicit-ref' annotation --- pkg/kapp/diff/explicit_versioned_ref.go | 90 +++++++++++++++++++++++++ pkg/kapp/diff/versioned_resource.go | 42 ++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 pkg/kapp/diff/explicit_versioned_ref.go diff --git a/pkg/kapp/diff/explicit_versioned_ref.go b/pkg/kapp/diff/explicit_versioned_ref.go new file mode 100644 index 000000000..837398dcd --- /dev/null +++ b/pkg/kapp/diff/explicit_versioned_ref.go @@ -0,0 +1,90 @@ +// Copyright 2020 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package diff + +import ( + "fmt" +) + +type VersionedRefDesc struct { + Namespace string `json:"namespace"` + APIGroup string `json:"apiGroup"` + Kind string `json:"kind"` + Name string `json:"name"` +} + +type ExplicitVersionedRefAnn struct { + References []VersionedRefDesc `json:"references"` + VersionedNames map[string]string +} + +type ExplicitVersionedRef struct { + Resource VersionedResource + Annotation ExplicitVersionedRefAnn +} + +const ( + explicitReferenceKey = "kapp.k14s.io/versioned-explicit-ref" +) + +func NewExplicitVersionedRef(res VersionedResource, annotation ExplicitVersionedRefAnn) *ExplicitVersionedRef { + return &ExplicitVersionedRef{res, annotation} +} + +// Returns true if the +func (e *ExplicitVersionedRef) IsReferenced() (bool, error) { + references, err := e.refStringList() + if err != nil { + return false, err + } + + referenceKey := e.resourceRefString() + + for _, v := range references { + if v == referenceKey { + return true, nil + } + } + + return false, nil +} + +func (e *ExplicitVersionedRef) refStringList() ([]string, error) { + list := []string{} + for _, v := range e.Annotation.References { + v, err := e.validateAndReplaceCoreGroup(v) + if err != nil { + return list, err + } + + list = append(list, fmt.Sprintf("%s/%s/%s/%s", v.Namespace, v.APIGroup, v.Kind, v.Name)) + } + return list, nil +} + +func (e *ExplicitVersionedRef) validateAndReplaceCoreGroup(resourceDescription VersionedRefDesc) (VersionedRefDesc, error) { + // Replacing APIGroup value "core" with an empty string + // Edgecase for resources that are a part of "core" + if resourceDescription.APIGroup == "core" { + resourceDescription.APIGroup = "" + } + + if resourceDescription.Kind == "" || resourceDescription.Name == "" { + return resourceDescription, fmt.Errorf("Explicit Reference Error: Name and Kind are required values in an explicit reference") + } + return resourceDescription, nil +} + +func (e *ExplicitVersionedRef) resourceRefString() string { + return e.Resource.UniqVersionedKey().String() +} + +/* +Annotation with a list of references in JSON format: + +kapp.k14s.io/versioned-explicit-ref: '{ "references": [ { "namespace": , "apiGroup": , "kind" : , "name": } ] }' + +"namespace" need not be assigned a value for cluster-scoped resources. +"apiGroup" need not be assigned a value for resources belonging to "core" API group. +*/ diff --git a/pkg/kapp/diff/versioned_resource.go b/pkg/kapp/diff/versioned_resource.go index 2b17e3797..762e7788f 100644 --- a/pkg/kapp/diff/versioned_resource.go +++ b/pkg/kapp/diff/versioned_resource.go @@ -88,12 +88,54 @@ func (d VersionedResource) updateAffected(rule ctlconf.TemplateRule, rs []ctlres if err != nil { return err } + + if val, found := res.Annotations()[explicitReferenceKey]; found { + annotation := ExplicitVersionedRefAnn{} + + err := json.Unmarshal([]byte(val), &annotation) + if err != nil { + return fmt.Errorf("Error unmarshalling explicit references : %s", err) + } + + isTarget, err := NewExplicitVersionedRef(d, annotation).IsReferenced() + if err != nil { + return err + } + + if isTarget { + if annotation.VersionedNames == nil { + annotation.VersionedNames = map[string]string{} + } + + annotation.VersionedNames[d.UniqVersionedKey().String()] = d.res.Name() + + out, err := json.Marshal(annotation) + if err != nil { + return fmt.Errorf("Error marshalling reference annotation: %s", err) + } + + err = d.annotationMod(string(out)).Apply(res) + if err != nil { + return err + } + } + } } } return nil } +func (d VersionedResource) annotationMod(annotation string) ctlres.StringMapAppendMod { + return ctlres.StringMapAppendMod{ + ResourceMatcher: ctlres.AllMatcher{}, + Path: ctlres.NewPathFromStrings([]string{"metadata", "annotations"}), + KVs: map[string]string{ + explicitReferenceKey: annotation, + }, + } +} + func (d VersionedResource) buildObjRefReplacementFunc( affectedObjRef ctlconf.TemplateAffectedObjRef) func(map[string]interface{}) error { From 18162039d79fba2ba00ffc0ccfc2965f6ad94e22 Mon Sep 17 00:00:00 2001 From: Soumik Majumder Date: Tue, 21 Sep 2021 16:56:15 +0530 Subject: [PATCH 2/4] E2E tests for versioned-explicit-ref annotation. Refactoring for legibility. --- pkg/kapp/diff/explicit_versioned_ref.go | 10 +- pkg/kapp/diff/versioned_resource.go | 4 +- test/e2e/versioned_explicit_reference_test.go | 107 ++++++++++++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 test/e2e/versioned_explicit_reference_test.go diff --git a/pkg/kapp/diff/explicit_versioned_ref.go b/pkg/kapp/diff/explicit_versioned_ref.go index 837398dcd..8995ce91e 100644 --- a/pkg/kapp/diff/explicit_versioned_ref.go +++ b/pkg/kapp/diff/explicit_versioned_ref.go @@ -32,14 +32,14 @@ func NewExplicitVersionedRef(res VersionedResource, annotation ExplicitVersioned return &ExplicitVersionedRef{res, annotation} } -// Returns true if the +// Returns true if the resource is referenced by the annotation func (e *ExplicitVersionedRef) IsReferenced() (bool, error) { - references, err := e.refStringList() + references, err := e.references() if err != nil { return false, err } - referenceKey := e.resourceRefString() + referenceKey := e.referenceKey() for _, v := range references { if v == referenceKey { @@ -50,7 +50,7 @@ func (e *ExplicitVersionedRef) IsReferenced() (bool, error) { return false, nil } -func (e *ExplicitVersionedRef) refStringList() ([]string, error) { +func (e *ExplicitVersionedRef) references() ([]string, error) { list := []string{} for _, v := range e.Annotation.References { v, err := e.validateAndReplaceCoreGroup(v) @@ -76,7 +76,7 @@ func (e *ExplicitVersionedRef) validateAndReplaceCoreGroup(resourceDescription V return resourceDescription, nil } -func (e *ExplicitVersionedRef) resourceRefString() string { +func (e *ExplicitVersionedRef) referenceKey() string { return e.Resource.UniqVersionedKey().String() } diff --git a/pkg/kapp/diff/versioned_resource.go b/pkg/kapp/diff/versioned_resource.go index 762e7788f..dff3bb49f 100644 --- a/pkg/kapp/diff/versioned_resource.go +++ b/pkg/kapp/diff/versioned_resource.go @@ -97,12 +97,12 @@ func (d VersionedResource) updateAffected(rule ctlconf.TemplateRule, rs []ctlres return fmt.Errorf("Error unmarshalling explicit references : %s", err) } - isTarget, err := NewExplicitVersionedRef(d, annotation).IsReferenced() + isReferenced, err := NewExplicitVersionedRef(d, annotation).IsReferenced() if err != nil { return err } - if isTarget { + if isReferenced { if annotation.VersionedNames == nil { annotation.VersionedNames = map[string]string{} } diff --git a/test/e2e/versioned_explicit_reference_test.go b/test/e2e/versioned_explicit_reference_test.go new file mode 100644 index 000000000..e9d1a07e2 --- /dev/null +++ b/test/e2e/versioned_explicit_reference_test.go @@ -0,0 +1,107 @@ +// Copyright 2020 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package e2e + +import ( + "reflect" + "strings" + "testing" + + uitest "github.com/cppforlife/go-cli-ui/ui/test" +) + +func TestVersionedExplicitReference(t *testing.T) { + env := BuildEnv(t) + logger := Logger{} + kapp := Kapp{t, env.Namespace, env.KappBinaryPath, logger} + + yaml1 := ` +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-1 + annotations: + kapp.k14s.io/versioned: "" +data: + foo: bar +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-2 + annotations: + kapp.k14s.io/versioned-explicit-ref: '{ "references": [ { "namespace": "kapp-test", "kind" : "ConfigMap", "name": "config-1"} ] }' +data: + foo: bar +` + + yaml2 := ` +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-1 + annotations: + kapp.k14s.io/versioned: "" +data: + foo: alpha +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-2 + annotations: + kapp.k14s.io/versioned-explicit-ref: '{ "references": [ { "namespace": "kapp-test", "kind" : "ConfigMap", "name": "config-1"} ] }' +data: + foo: bar +` + + name := "test-versioned-explicit-references" + cleanUp := func() { + kapp.Run([]string{"delete", "-a", name}) + } + + cleanUp() + defer cleanUp() + + logger.Section("deploy initial", func() { + kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, + RunOpts{IntoNs: true, StdinReader: strings.NewReader(yaml1)}) + }) + + logger.Section("update versioned resource", func() { + out, _ := kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name, "--json"}, + RunOpts{IntoNs: true, StdinReader: strings.NewReader(yaml2)}) + resp := uitest.JSONUIFromBytes(t, []byte(out)) + + expected := []map[string]string{{ + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-1-ver-2", + "namespace": "kapp-test", + "op": "create", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "", + "wait_to": "reconcile", + }, { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-2", + "namespace": "kapp-test", + "op": "update", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "ok", + "wait_to": "reconcile", + }} + + if !reflect.DeepEqual(replaceAge(resp.Tables[0].Rows), expected) { + t.Fatalf("Expected to see correct changes, but did not: '%s'", out) + } + }) +} From fa88a3aaf77ad63fb293f94055341a71afb57eb9 Mon Sep 17 00:00:00 2001 From: Soumik Majumder Date: Wed, 22 Sep 2021 18:30:26 +0530 Subject: [PATCH 3/4] YAML format for versioned references --- pkg/kapp/diff/explicit_versioned_ref.go | 78 ++++++++----------- pkg/kapp/diff/versioned_resource.go | 35 +++------ test/e2e/versioned_explicit_reference_test.go | 12 ++- 3 files changed, 56 insertions(+), 69 deletions(-) diff --git a/pkg/kapp/diff/explicit_versioned_ref.go b/pkg/kapp/diff/explicit_versioned_ref.go index 8995ce91e..19f92d569 100644 --- a/pkg/kapp/diff/explicit_versioned_ref.go +++ b/pkg/kapp/diff/explicit_versioned_ref.go @@ -5,79 +5,69 @@ package diff import ( "fmt" + "reflect" + + "gopkg.in/yaml.v2" ) type VersionedRefDesc struct { - Namespace string `json:"namespace"` - APIGroup string `json:"apiGroup"` - Kind string `json:"kind"` - Name string `json:"name"` -} - -type ExplicitVersionedRefAnn struct { - References []VersionedRefDesc `json:"references"` - VersionedNames map[string]string + Namespace string `yaml:"namespace"` + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + Name string `yaml:"name"` } type ExplicitVersionedRef struct { Resource VersionedResource - Annotation ExplicitVersionedRefAnn + Annotation string } const ( - explicitReferenceKey = "kapp.k14s.io/versioned-explicit-ref" + explicitReferenceKey = "kapp.k14s.io/versioned-explicit-ref" + explicitReferenceKeyPrefix = "kapp.k14s.io/versioned-explicit-ref." ) -func NewExplicitVersionedRef(res VersionedResource, annotation ExplicitVersionedRefAnn) *ExplicitVersionedRef { +func NewExplicitVersionedRef(res VersionedResource, annotation string) *ExplicitVersionedRef { return &ExplicitVersionedRef{res, annotation} } // Returns true if the resource is referenced by the annotation func (e *ExplicitVersionedRef) IsReferenced() (bool, error) { - references, err := e.references() + reference := VersionedRefDesc{} + err := yaml.Unmarshal([]byte(e.Annotation), &reference) if err != nil { - return false, err + return false, fmt.Errorf("Error unmarshalling versioned reference: %s", err) } - referenceKey := e.referenceKey() - - for _, v := range references { - if v == referenceKey { - return true, nil - } + if reference.APIVersion == "" || reference.Kind == "" || reference.Name == "" { + return false, fmt.Errorf("Explicit reference error: apiVersion, kind and name are required values in an explicit versioned reference") } - return false, nil -} - -func (e *ExplicitVersionedRef) references() ([]string, error) { - list := []string{} - for _, v := range e.Annotation.References { - v, err := e.validateAndReplaceCoreGroup(v) - if err != nil { - return list, err - } - - list = append(list, fmt.Sprintf("%s/%s/%s/%s", v.Namespace, v.APIGroup, v.Kind, v.Name)) + baseName, _ := e.Resource.BaseNameAndVersion() + versionedResourceDesc := VersionedRefDesc{ + Namespace: e.Resource.res.Namespace(), + APIVersion: e.Resource.res.APIVersion(), + Kind: e.Resource.res.Kind(), + Name: baseName, } - return list, nil + + return reflect.DeepEqual(versionedResourceDesc, reference), nil } -func (e *ExplicitVersionedRef) validateAndReplaceCoreGroup(resourceDescription VersionedRefDesc) (VersionedRefDesc, error) { - // Replacing APIGroup value "core" with an empty string - // Edgecase for resources that are a part of "core" - if resourceDescription.APIGroup == "core" { - resourceDescription.APIGroup = "" +func (e *ExplicitVersionedRef) VersionedReference() (string, error) { + reference := VersionedRefDesc{} + err := yaml.Unmarshal([]byte(e.Annotation), &reference) + if err != nil { + return "", fmt.Errorf("Error unmarshalling versioned reference: %s", err) } - if resourceDescription.Kind == "" || resourceDescription.Name == "" { - return resourceDescription, fmt.Errorf("Explicit Reference Error: Name and Kind are required values in an explicit reference") + reference.Name = e.Resource.res.Name() + versionedReference, err := yaml.Marshal(reference) + if err != nil { + return "", fmt.Errorf("Error marshalling versioned reference: %s", err) } - return resourceDescription, nil -} -func (e *ExplicitVersionedRef) referenceKey() string { - return e.Resource.UniqVersionedKey().String() + return string(versionedReference), nil } /* diff --git a/pkg/kapp/diff/versioned_resource.go b/pkg/kapp/diff/versioned_resource.go index dff3bb49f..ef8257693 100644 --- a/pkg/kapp/diff/versioned_resource.go +++ b/pkg/kapp/diff/versioned_resource.go @@ -88,36 +88,25 @@ func (d VersionedResource) updateAffected(rule ctlconf.TemplateRule, rs []ctlres if err != nil { return err } + } + } - if val, found := res.Annotations()[explicitReferenceKey]; found { - annotation := ExplicitVersionedRefAnn{} - - err := json.Unmarshal([]byte(val), &annotation) - if err != nil { - return fmt.Errorf("Error unmarshalling explicit references : %s", err) - } - - isReferenced, err := NewExplicitVersionedRef(d, annotation).IsReferenced() + for _, res := range rs { + for k, v := range res.Annotations() { + if k == explicitReferenceKey || strings.HasPrefix(k, explicitReferenceKeyPrefix) { + explicitRef := NewExplicitVersionedRef(d, v) + isReferenced, err := explicitRef.IsReferenced() if err != nil { return err } if isReferenced { - if annotation.VersionedNames == nil { - annotation.VersionedNames = map[string]string{} - } - - annotation.VersionedNames[d.UniqVersionedKey().String()] = d.res.Name() - - out, err := json.Marshal(annotation) - if err != nil { - return fmt.Errorf("Error marshalling reference annotation: %s", err) - } - - err = d.annotationMod(string(out)).Apply(res) + versionedReference, err := explicitRef.VersionedReference() if err != nil { return err } + + d.annotationMod(k, versionedReference).Apply(res) } } } @@ -126,12 +115,12 @@ func (d VersionedResource) updateAffected(rule ctlconf.TemplateRule, rs []ctlres return nil } -func (d VersionedResource) annotationMod(annotation string) ctlres.StringMapAppendMod { +func (d VersionedResource) annotationMod(annotation string, value string) ctlres.StringMapAppendMod { return ctlres.StringMapAppendMod{ ResourceMatcher: ctlres.AllMatcher{}, Path: ctlres.NewPathFromStrings([]string{"metadata", "annotations"}), KVs: map[string]string{ - explicitReferenceKey: annotation, + annotation: value, }, } } diff --git a/test/e2e/versioned_explicit_reference_test.go b/test/e2e/versioned_explicit_reference_test.go index e9d1a07e2..c8af003a8 100644 --- a/test/e2e/versioned_explicit_reference_test.go +++ b/test/e2e/versioned_explicit_reference_test.go @@ -32,7 +32,11 @@ kind: ConfigMap metadata: name: config-2 annotations: - kapp.k14s.io/versioned-explicit-ref: '{ "references": [ { "namespace": "kapp-test", "kind" : "ConfigMap", "name": "config-1"} ] }' + kapp.k14s.io/versioned-explicit-ref: | + namespace: kapp-test + apiVersion: v1 + kind: ConfigMap + name: config-1 data: foo: bar ` @@ -53,7 +57,11 @@ kind: ConfigMap metadata: name: config-2 annotations: - kapp.k14s.io/versioned-explicit-ref: '{ "references": [ { "namespace": "kapp-test", "kind" : "ConfigMap", "name": "config-1"} ] }' + kapp.k14s.io/versioned-explicit-ref: | + namespace: kapp-test + apiVersion: v1 + kind: ConfigMap + name: config-1 data: foo: bar ` From 94e364e4b6975a2d2e06468c16281e7df519976c Mon Sep 17 00:00:00 2001 From: Soumik Majumder Date: Sat, 25 Sep 2021 00:21:29 +0530 Subject: [PATCH 4/4] Versioned explicit reference now consumes existing buildObjRefReplacementFunc --- pkg/kapp/diff/explicit_versioned_ref.go | 72 +++----- pkg/kapp/diff/versioned_resource.go | 35 ++-- test/e2e/versioned_explicit_reference_test.go | 171 +++++++++++++++--- 3 files changed, 185 insertions(+), 93 deletions(-) diff --git a/pkg/kapp/diff/explicit_versioned_ref.go b/pkg/kapp/diff/explicit_versioned_ref.go index 19f92d569..aa9fc86ed 100644 --- a/pkg/kapp/diff/explicit_versioned_ref.go +++ b/pkg/kapp/diff/explicit_versioned_ref.go @@ -5,21 +5,14 @@ package diff import ( "fmt" - "reflect" + ctlres "github.com/k14s/kapp/pkg/kapp/resources" "gopkg.in/yaml.v2" ) -type VersionedRefDesc struct { - Namespace string `yaml:"namespace"` - APIVersion string `yaml:"apiVersion"` - Kind string `yaml:"kind"` - Name string `yaml:"name"` -} - type ExplicitVersionedRef struct { - Resource VersionedResource - Annotation string + AnnotationKey string + Annotation string } const ( @@ -27,54 +20,39 @@ const ( explicitReferenceKeyPrefix = "kapp.k14s.io/versioned-explicit-ref." ) -func NewExplicitVersionedRef(res VersionedResource, annotation string) *ExplicitVersionedRef { - return &ExplicitVersionedRef{res, annotation} +func NewExplicitVersionedRef(annotationKey string, annotation string) *ExplicitVersionedRef { + return &ExplicitVersionedRef{annotationKey, annotation} } -// Returns true if the resource is referenced by the annotation -func (e *ExplicitVersionedRef) IsReferenced() (bool, error) { - reference := VersionedRefDesc{} - err := yaml.Unmarshal([]byte(e.Annotation), &reference) +func (e *ExplicitVersionedRef) AsObjectRef() (map[string]interface{}, error) { + var objectRef map[string]interface{} + err := yaml.Unmarshal([]byte(e.Annotation), &objectRef) if err != nil { - return false, fmt.Errorf("Error unmarshalling versioned reference: %s", err) + return nil, fmt.Errorf("Parsing versioned explicit reference from annotation '%s': %s", e.AnnotationKey, err) } - if reference.APIVersion == "" || reference.Kind == "" || reference.Name == "" { - return false, fmt.Errorf("Explicit reference error: apiVersion, kind and name are required values in an explicit versioned reference") - } + _, hasAPIVersionKey := objectRef["apiVersion"] + _, hasKindKey := objectRef["kind"] + _, hasNameKey := objectRef["name"] - baseName, _ := e.Resource.BaseNameAndVersion() - versionedResourceDesc := VersionedRefDesc{ - Namespace: e.Resource.res.Namespace(), - APIVersion: e.Resource.res.APIVersion(), - Kind: e.Resource.res.Kind(), - Name: baseName, + if !(hasAPIVersionKey && hasKindKey && hasNameKey) { + return nil, fmt.Errorf("Expected versioned explicit reference to specify non-empty apiVersion, kind and name keys") } - return reflect.DeepEqual(versionedResourceDesc, reference), nil + return objectRef, nil } -func (e *ExplicitVersionedRef) VersionedReference() (string, error) { - reference := VersionedRefDesc{} - err := yaml.Unmarshal([]byte(e.Annotation), &reference) +func (e *ExplicitVersionedRef) AnnotationMod(objectRef map[string]interface{}) (ctlres.StringMapAppendMod, error) { + value, err := yaml.Marshal(objectRef) if err != nil { - return "", fmt.Errorf("Error unmarshalling versioned reference: %s", err) + return ctlres.StringMapAppendMod{}, fmt.Errorf("Marshalling explicit reference: %s", err) } - reference.Name = e.Resource.res.Name() - versionedReference, err := yaml.Marshal(reference) - if err != nil { - return "", fmt.Errorf("Error marshalling versioned reference: %s", err) - } - - return string(versionedReference), nil + return ctlres.StringMapAppendMod{ + ResourceMatcher: ctlres.AllMatcher{}, + Path: ctlres.NewPathFromStrings([]string{"metadata", "annotations"}), + KVs: map[string]string{ + e.AnnotationKey: string(value), + }, + }, nil } - -/* -Annotation with a list of references in JSON format: - -kapp.k14s.io/versioned-explicit-ref: '{ "references": [ { "namespace": , "apiGroup": , "kind" : , "name": } ] }' - -"namespace" need not be assigned a value for cluster-scoped resources. -"apiGroup" need not be assigned a value for resources belonging to "core" API group. -*/ diff --git a/pkg/kapp/diff/versioned_resource.go b/pkg/kapp/diff/versioned_resource.go index ef8257693..ba1cd0dbb 100644 --- a/pkg/kapp/diff/versioned_resource.go +++ b/pkg/kapp/diff/versioned_resource.go @@ -94,19 +94,26 @@ func (d VersionedResource) updateAffected(rule ctlconf.TemplateRule, rs []ctlres for _, res := range rs { for k, v := range res.Annotations() { if k == explicitReferenceKey || strings.HasPrefix(k, explicitReferenceKeyPrefix) { - explicitRef := NewExplicitVersionedRef(d, v) - isReferenced, err := explicitRef.IsReferenced() + explicitRef := NewExplicitVersionedRef(k, v) + objectRef, err := explicitRef.AsObjectRef() if err != nil { - return err + return fmt.Errorf("Parsing versioned explicit ref on resource '%s': %s", res.Description(), err) } - if isReferenced { - versionedReference, err := explicitRef.VersionedReference() - if err != nil { - return err - } + // Passing empty TemplateAffectedObjRef as explicit references do not have a special name key + err = d.buildObjRefReplacementFunc(ctlconf.TemplateAffectedObjRef{})(objectRef) + if err != nil { + return fmt.Errorf("Processing object ref for explicit ref on resource '%s': %s", res.Description(), err) + } - d.annotationMod(k, versionedReference).Apply(res) + annotationMod, err := explicitRef.AnnotationMod(objectRef) + if err != nil { + return fmt.Errorf("Preparing annotation mod for versioned explicit ref on resource '%s': %s", res.Description(), err) + } + + err = annotationMod.Apply(res) + if err != nil { + return fmt.Errorf("Updating versioned explicit ref on resource '%s': %s", res.Description(), err) } } } @@ -115,16 +122,6 @@ func (d VersionedResource) updateAffected(rule ctlconf.TemplateRule, rs []ctlres return nil } -func (d VersionedResource) annotationMod(annotation string, value string) ctlres.StringMapAppendMod { - return ctlres.StringMapAppendMod{ - ResourceMatcher: ctlres.AllMatcher{}, - Path: ctlres.NewPathFromStrings([]string{"metadata", "annotations"}), - KVs: map[string]string{ - annotation: value, - }, - } -} - func (d VersionedResource) buildObjRefReplacementFunc( affectedObjRef ctlconf.TemplateAffectedObjRef) func(map[string]interface{}) error { diff --git a/test/e2e/versioned_explicit_reference_test.go b/test/e2e/versioned_explicit_reference_test.go index c8af003a8..4c5baf704 100644 --- a/test/e2e/versioned_explicit_reference_test.go +++ b/test/e2e/versioned_explicit_reference_test.go @@ -33,10 +33,33 @@ metadata: name: config-2 annotations: kapp.k14s.io/versioned-explicit-ref: | - namespace: kapp-test apiVersion: v1 kind: ConfigMap name: config-1 +data: +foo: bar +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-3 + annotations: + kapp.k14s.io/versioned-explicit-ref.match: | + apiVersion: v1 + kind: ConfigMap + name: config-1 +data: + foo: bar +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-4 + annotations: + kapp.k14s.io/versioned-explicit-ref.nomatch: | + apiVersion: v1 + kind: ConfigMap + name: config-2 data: foo: bar ` @@ -58,12 +81,35 @@ metadata: name: config-2 annotations: kapp.k14s.io/versioned-explicit-ref: | - namespace: kapp-test apiVersion: v1 kind: ConfigMap name: config-1 data: foo: bar +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-3 + annotations: + kapp.k14s.io/versioned-explicit-ref.match: | + apiVersion: v1 + kind: ConfigMap + name: config-1 +data: + foo: bar +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: config-4 + annotations: + kapp.k14s.io/versioned-explicit-ref.nomatch: | + apiVersion: v1 + kind: ConfigMap + name: config-2 +data: + foo: bar ` name := "test-versioned-explicit-references" @@ -75,8 +121,64 @@ data: defer cleanUp() logger.Section("deploy initial", func() { - kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name}, + out, _ := kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name, "--json"}, RunOpts{IntoNs: true, StdinReader: strings.NewReader(yaml1)}) + resp := uitest.JSONUIFromBytes(t, []byte(out)) + + expected := []map[string]string{ + { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-1-ver-1", + "namespace": "kapp-test", + "op": "create", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "", + "wait_to": "reconcile", + }, + { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-2", + "namespace": "kapp-test", + "op": "create", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "", + "wait_to": "reconcile", + }, + { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-3", + "namespace": "kapp-test", + "op": "create", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "", + "wait_to": "reconcile", + }, + { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-4", + "namespace": "kapp-test", + "op": "create", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "", + "wait_to": "reconcile", + }, + } + + if !reflect.DeepEqual(resp.Tables[0].Rows, expected) { + t.Fatalf("Expected to see correct changes but recieved >>%s<<", out) + } }) logger.Section("update versioned resource", func() { @@ -84,32 +186,47 @@ data: RunOpts{IntoNs: true, StdinReader: strings.NewReader(yaml2)}) resp := uitest.JSONUIFromBytes(t, []byte(out)) - expected := []map[string]string{{ - "age": "", - "conditions": "", - "kind": "ConfigMap", - "name": "config-1-ver-2", - "namespace": "kapp-test", - "op": "create", - "op_strategy": "", - "reconcile_info": "", - "reconcile_state": "", - "wait_to": "reconcile", - }, { - "age": "", - "conditions": "", - "kind": "ConfigMap", - "name": "config-2", - "namespace": "kapp-test", - "op": "update", - "op_strategy": "", - "reconcile_info": "", - "reconcile_state": "ok", - "wait_to": "reconcile", - }} + expected := []map[string]string{ + { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-1-ver-2", + "namespace": "kapp-test", + "op": "create", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "", + "wait_to": "reconcile", + }, + { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-2", + "namespace": "kapp-test", + "op": "update", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "ok", + "wait_to": "reconcile", + }, + { + "age": "", + "conditions": "", + "kind": "ConfigMap", + "name": "config-3", + "namespace": "kapp-test", + "op": "update", + "op_strategy": "", + "reconcile_info": "", + "reconcile_state": "ok", + "wait_to": "reconcile", + }, + } if !reflect.DeepEqual(replaceAge(resp.Tables[0].Rows), expected) { - t.Fatalf("Expected to see correct changes, but did not: '%s'", out) + t.Fatalf("Expected to see correct changes but recieved >>%s<<", out) } }) }