Skip to content
This repository has been archived by the owner on Nov 17, 2021. It is now read-only.

Commit

Permalink
Merge pull request #304 from bitnami/annotation
Browse files Browse the repository at this point in the history
Support kubecfg.ksonnet.io/last-applied-configuration annotation in diff
  • Loading branch information
mkmik authored May 11, 2021
2 parents 8d25841 + af6792c commit 886ea66
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 9 deletions.
18 changes: 12 additions & 6 deletions pkg/kubecfg/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"encoding/json"
"fmt"
"io"
v1 "k8s.io/api/core/v1"
"os"
"regexp"
"sort"
Expand Down Expand Up @@ -89,7 +88,11 @@ func (c DiffCmd) Run(ctx context.Context, apiObjects []*unstructured.Unstructure
continue
}

liveObjText, _ := json.MarshalIndent(c.getLiveObjObject(obj, liveObj), "", " ")
liveObjMap, err := c.getLiveObjObject(obj, liveObj)
if err != nil {
return err
}
liveObjText, _ := json.MarshalIndent(liveObjMap, "", " ")
objText, _ := json.MarshalIndent(obj.Object, "", " ")

liveObjTextLines, objTextLines, lines := dmp.DiffLinesToChars(string(liveObjText), string(objText))
Expand All @@ -115,17 +118,20 @@ func (c DiffCmd) Run(ctx context.Context, apiObjects []*unstructured.Unstructure
return nil
}

func (c DiffCmd) getLiveObjObject(obj *unstructured.Unstructured, liveObj *unstructured.Unstructured) map[string]interface{} {
func (c DiffCmd) getLiveObjObject(obj *unstructured.Unstructured, liveObj *unstructured.Unstructured) (map[string]interface{}, error) {
var liveObjObject map[string]interface{}
if c.DiffStrategy == "subset" {
liveObjObject = removeMapFields(obj.Object, liveObj.Object)
} else if c.DiffStrategy == "last-applied" {
lastAppliedText := liveObj.GetAnnotations()[v1.LastAppliedConfigAnnotation]
json.Unmarshal([]byte(lastAppliedText), &liveObjObject)
var err error
liveObjObject, err = origObject(liveObj)
if err != nil {
return nil, err
}
} else {
liveObjObject = liveObj.Object
}
return liveObjObject
return liveObjObject, nil
}

// Formats the supplied Diff as a unified-diff-like text with infinite context and optionally colorizes it.
Expand Down
89 changes: 87 additions & 2 deletions pkg/kubecfg/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ package kubecfg

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"testing"
)

func TestLastAppliedStrategy(t *testing.T) {
Expand Down Expand Up @@ -91,7 +92,91 @@ func TestLastAppliedStrategy(t *testing.T) {
}
}`
json.Unmarshal([]byte(expectedText), &expectedMap)
require.Equal(t, expectedMap, c.getLiveObjObject(nil, &unstructured.Unstructured{Object: liveMap}))
liveObj, err := c.getLiveObjObject(nil, &unstructured.Unstructured{Object: liveMap})
if err != nil {
t.Error(err)
}
require.Equal(t, expectedMap, liveObj)
}

func TestLastAppliedStrategyKubecfgAnnotation(t *testing.T) {

var liveMap map[string]interface{}
var expectedMap map[string]interface{}

c := DiffCmd{}
c.DiffStrategy = "last-applied"

liveText :=
`{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "foo",
"namespace": "default",
"resourceVersion": "288527730",
"selfLink": "/api/v1/namespaces/default/services/foo",
"uid": "687c86fe-12ec-45d1-a4e8-61db473e2d01",
"creationTimestamp": "2021-04-11T13:38:06Z",
"annotations": {
"testKey": "testValue",
"kubecfg.ksonnet.io/last-applied-configuration": "H4sIANVcmWAAAzWOsQrDMBBD935F0Jwh3YJ/oWMhS+lwOBdqmtjGvgRC8L/3nLabJKSHDlB0A6fsgofBdkWLt/Oj6junzVnWYGGhkYRgDpD3QUi0nqsVznLjHaY55UDzyigtPC2sjCkEfE2OZGsy8kTrLNppkCPbCokhidIex3/2Eom6qzlM3/VdebbIPLOVkM4XMf7opZTLB1jX/izFAAAA"
}
},
"spec": {
"sessionAffinity": "None",
"type": "ClusterIP",
"selector": {
"app": "foo"
},
"ports": [
{
"name": "http",
"port": 8080,
"protocol": "TCP",
"targetPort": 8080
}
]
},
"status": {
"loadBalancer": {}
}
}`
if err := json.Unmarshal([]byte(liveText), &liveMap); err != nil {
t.Error(err)
}

expectedText :=
`{
"apiVersion": "v1",
"kind": "Service",
"metadata": {
"name": "foo",
"namespace": "default",
"annotations": {
"testKey": "testValue"
}
},
"spec": {
"selector": {
"app": "foo"
},
"ports": [
{
"name": "http",
"port": 8080
}
]
}
}`
if err := json.Unmarshal([]byte(expectedText), &expectedMap); err != nil {
t.Error(err)
}
liveObj, err := c.getLiveObjObject(nil, &unstructured.Unstructured{Object: liveMap})
if err != nil {
t.Error(err)
}
require.Equal(t, expectedMap, liveObj)
}

func TestRemoveListFields(t *testing.T) {
Expand Down
30 changes: 29 additions & 1 deletion pkg/kubecfg/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package kubecfg

import (
"context"
"encoding/json"
"fmt"
"sort"
"time"

jsonpatch "github.com/evanphx/json-patch"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
apiext_v1b1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -95,7 +97,7 @@ func isValidKindSchema(schema proto.Schema) bool {
return err == nil
}

func patch(existing, new *unstructured.Unstructured, schema proto.Schema) (*unstructured.Unstructured, error) {
func origObject(existing *unstructured.Unstructured) (map[string]interface{}, error) {
annos := existing.GetAnnotations()
var origData []byte
if data := annos[AnnotationOrigObject]; data != "" {
Expand All @@ -108,10 +110,36 @@ func patch(existing, new *unstructured.Unstructured, schema proto.Schema) (*unst
if err != nil {
return nil, err
}
} else if data, ok := annos[v1.LastAppliedConfigAnnotation]; ok {
origData = []byte(data)
} else {
return nil, fmt.Errorf("no original object annotation")
}

log.Debugf("origData: %s", origData)

var liveObjObject map[string]interface{}
if err := json.Unmarshal(origData, &liveObjObject); err != nil {
return nil, err
}
return liveObjObject, nil
}

func patch(existing, new *unstructured.Unstructured, schema proto.Schema) (*unstructured.Unstructured, error) {
annos := existing.GetAnnotations()
var origData []byte
if data := annos[AnnotationOrigObject]; data != "" {
tmp := unstructured.Unstructured{}
err := utils.CompactDecodeObject(data, &tmp)
if err != nil {
return nil, err
}
origData, err = tmp.MarshalJSON()
if err != nil {
return nil, err
}
}

new = new.DeepCopy()
utils.DeleteMetaDataAnnotation(new, AnnotationOrigObject)
data, err := utils.CompactEncodeObject(new)
Expand Down

0 comments on commit 886ea66

Please sign in to comment.