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..fdd85b410 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 } - 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("Error updating explicit versioned reference: %s", err) + } - d.annotationMod(k, versionedReference).Apply(res) + annotationMod, err := explicitRef.AnnotationMod(objectRef) + if err != nil { + return err + } + + err = annotationMod.Apply(res) + if err != nil { + return 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) } }) }