Skip to content

Commit

Permalink
Versioned explicit reference now consumes existing buildObjRefReplace…
Browse files Browse the repository at this point in the history
…mentFunc
  • Loading branch information
100mik committed Sep 27, 2021
1 parent 4177a57 commit ae0480f
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 98 deletions.
72 changes: 25 additions & 47 deletions pkg/kapp/diff/explicit_versioned_ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,54 @@ 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 (
explicitReferenceKey = "kapp.k14s.io/versioned-explicit-ref"
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": <resource-namespace>, "apiGroup": <resource-apiGroup>, "kind" : <resource-kind>, "name": <resource-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.
*/
33 changes: 15 additions & 18 deletions pkg/kapp/diff/versioned_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand All @@ -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 {

Expand Down
125 changes: 92 additions & 33 deletions test/e2e/versioned_explicit_reference_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
package e2e

import (
"reflect"
"fmt"
"strings"
"testing"

uitest "github.com/cppforlife/go-cli-ui/ui/test"
)

func TestVersionedExplicitReference(t *testing.T) {
Expand All @@ -33,12 +31,27 @@ 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
kapp.k14s.io/versioned-explicit-ref.nomatch: |
apiVersion: v1
kind: ConfigMap
name: config-2
data:
foo: bar
`

yaml2 := `
Expand All @@ -58,12 +71,27 @@ 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
kapp.k14s.io/versioned-explicit-ref.nomatch: |
apiVersion: v1
kind: ConfigMap
name: config-2
data:
foo: bar
`

name := "test-versioned-explicit-references"
Expand All @@ -75,41 +103,72 @@ data:
defer cleanUp()

logger.Section("deploy initial", func() {
kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name},
out, _ := kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name, "-c"},
RunOpts{IntoNs: true, StdinReader: strings.NewReader(yaml1)})

expectedDiff1 := `
@@ create configmap/config-2 (v1) namespace: kapp-test @@
0 + apiVersion: v1
1 + data:
2 + foo: bar
3 + kind: ConfigMap
4 + metadata:
5 + annotations:
6 + kapp.k14s.io/versioned-explicit-ref: |
7 + apiVersion: v1
8 + kind: ConfigMap
9 + name: config-1-ver-1
`
expectedDiff2 := `
@@ create configmap/config-3 (v1) namespace: kapp-test @@
0 + apiVersion: v1
1 + data:
2 + foo: bar
3 + kind: ConfigMap
4 + metadata:
5 + annotations:
6 + kapp.k14s.io/versioned-explicit-ref.match: |
7 + apiVersion: v1
8 + kind: ConfigMap
9 + name: config-1-ver-1
10 + kapp.k14s.io/versioned-explicit-ref.nomatch: |
11 + apiVersion: v1
12 + kind: ConfigMap
13 + name: config-2
`

fmt.Println(strings.Contains(out, expectedDiff1))
fmt.Println(strings.Contains(out, expectedDiff2))
if !(strings.Contains(out, expectedDiff1) && strings.Contains(out, expectedDiff2)) {
t.Fatalf("Expected to see correct diff but recieved >>%s<<", out)
}
})

logger.Section("update versioned resource", func() {
out, _ := kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name, "--json"},
out, _ := kapp.RunWithOpts([]string{"deploy", "-f", "-", "-a", name, "-c", "--diff-context=0"},
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": "<replaced>",
"conditions": "",
"kind": "ConfigMap",
"name": "config-2",
"namespace": "kapp-test",
"op": "update",
"op_strategy": "",
"reconcile_info": "",
"reconcile_state": "ok",
"wait_to": "reconcile",
}}
expectedOutput := `
@@ create configmap/config-1-ver-2 (v1) namespace: kapp-test @@
2 - foo: bar
2 + foo: alpha
@@ update configmap/config-2 (v1) namespace: kapp-test @@
9 - name: config-1-ver-1
9 + name: config-1-ver-2
@@ update configmap/config-3 (v1) namespace: kapp-test @@
9 - name: config-1-ver-1
9 + name: config-1-ver-2
Changes
Namespace Name Kind Conds. Age Op Op st. Wait to Rs Ri
kapp-test config-1-ver-2 ConfigMap - - create - reconcile - -
^ config-2 ConfigMap - <replaced> update - reconcile ok -
^ config-3 ConfigMap - <replaced> update - reconcile ok -
`

if !reflect.DeepEqual(replaceAge(resp.Tables[0].Rows), expected) {
t.Fatalf("Expected to see correct changes, but did not: '%s'", out)
if !strings.Contains(replaceAgeStr(out), expectedOutput) {
t.Fatalf("Expected to observe diff >>%s<< but recieved >>%s<<", expectedOutput, replaceAgeStr(out))
}
})
}

0 comments on commit ae0480f

Please sign in to comment.