From 436ba8c86e4124e4a9095223300df62866bb4aa3 Mon Sep 17 00:00:00 2001 From: Praveen Rewar <8457124+praveenrewar@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:47:07 +0530 Subject: [PATCH] Optimise ops diff Signed-off-by: Praveen Rewar <8457124+praveenrewar@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 +- pkg/kapp/diff/change.go | 32 +-------- pkg/kapp/resources/resource.go | 4 ++ .../cppforlife/go-patch/patch/diff.go | 72 ++++++++++++++++++- vendor/modules.txt | 2 +- 6 files changed, 80 insertions(+), 36 deletions(-) diff --git a/go.mod b/go.mod index 5611513f3..e667ef185 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/cppforlife/cobrautil v0.0.0-20221130162803-acdfead391ef github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835 github.com/cppforlife/go-cli-ui v0.0.0-20220425131040-94f26b16bc14 - github.com/cppforlife/go-patch v0.2.0 + github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-version v1.6.0 github.com/k14s/difflib v0.0.0-20201117154628-0c031775bf57 diff --git a/go.sum b/go.sum index 996d24dc3..7b9537a1a 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/cppforlife/color v1.9.1-0.20200716202919-6706ac40b835/go.mod h1:dYeVs github.com/cppforlife/go-cli-ui v0.0.0-20200505234325-512793797f05/go.mod h1:I0qrzCmuPWYI6kAOvkllYjaW2aovclWbJ96+v+YyHb0= github.com/cppforlife/go-cli-ui v0.0.0-20220425131040-94f26b16bc14 h1:MjRdR01xh0sfkeS3OOBv+MYkYsrbHuTDc4rfBnVdFaI= github.com/cppforlife/go-cli-ui v0.0.0-20220425131040-94f26b16bc14/go.mod h1:AlgTssDlstr4mf92TR4DPITLfl5+7wEY4cKStCmeeto= -github.com/cppforlife/go-patch v0.2.0 h1:Y14MnCQjDlbw7WXT4k+u6DPAA9XnygN4BfrSpI/19RU= -github.com/cppforlife/go-patch v0.2.0/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= +github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b h1:+8LQctLhaj+63L/37l8IK/5Q3odN6RzWlglonUwrKok= +github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= diff --git a/pkg/kapp/diff/change.go b/pkg/kapp/diff/change.go index 528477798..f216f4de4 100644 --- a/pkg/kapp/diff/change.go +++ b/pkg/kapp/diff/change.go @@ -6,7 +6,6 @@ package diff import ( "github.com/cppforlife/go-patch/patch" ctlres "github.com/vmware-tanzu/carvel-kapp/pkg/kapp/resources" - "gopkg.in/yaml.v2" ) type ChangeOp string @@ -147,36 +146,7 @@ func (d *ChangeImpl) OpsDiff() OpsDiff { } func (d *ChangeImpl) calculateOpsDiff() OpsDiff { - var existingObj interface{} - var newObj interface{} - - if d.existingRes != nil { - existingBytes, err := d.existingRes.AsYAMLBytes() - if err != nil { - panic("yamling existingRes") // TODO panic - } - - err = yaml.Unmarshal(existingBytes, &existingObj) - if err != nil { - panic("unyamling existingRes") // TODO panic - } - } - - if d.newRes != nil { - newBytes, err := d.newRes.AsYAMLBytes() - if err != nil { - panic("yamling newRes") // TODO panic - } - - err = yaml.Unmarshal(newBytes, &newObj) - if err != nil { - panic("unyamling newRes") // TODO panic - } - } else if d.IsIgnored() { - newObj = existingObj // show as no changes - } - - return OpsDiff(patch.Diff{Left: existingObj, Right: newObj}.Calculate()) + return OpsDiff(patch.Diff{Left: d.existingRes.UnstructuredObject(), Right: d.newRes.UnstructuredObject()}.Calculate()) } func (d *ChangeImpl) newResHasExistsAnnotation() bool { diff --git a/pkg/kapp/resources/resource.go b/pkg/kapp/resources/resource.go index 94ad74417..5e39b7d0d 100644 --- a/pkg/kapp/resources/resource.go +++ b/pkg/kapp/resources/resource.go @@ -61,6 +61,8 @@ type Resource interface { MarkTransient(bool) Transient() bool + UnstructuredObject() map[string]interface{} + unstructured() unstructured.Unstructured // private unstructuredPtr() *unstructured.Unstructured // private setUnstructured(unstructured.Unstructured) // private @@ -280,6 +282,8 @@ func (r *ResourceImpl) Debug(title string) { func (r *ResourceImpl) SetOrigin(origin string) { r.origin = origin } func (r *ResourceImpl) Origin() string { return r.origin } +func (r *ResourceImpl) UnstructuredObject() map[string]interface{} { return r.un.Object } + func (r *ResourceImpl) unstructured() unstructured.Unstructured { return r.un } func (r *ResourceImpl) unstructuredPtr() *unstructured.Unstructured { return &r.un } func (r *ResourceImpl) setUnstructured(un unstructured.Unstructured) { r.un = un } diff --git a/vendor/github.com/cppforlife/go-patch/patch/diff.go b/vendor/github.com/cppforlife/go-patch/patch/diff.go index bedb01a42..3526b574c 100644 --- a/vendor/github.com/cppforlife/go-patch/patch/diff.go +++ b/vendor/github.com/cppforlife/go-patch/patch/diff.go @@ -31,6 +31,50 @@ func (d Diff) Calculate() Ops { func (d Diff) calculate(left, right interface{}, tokens []Token) []Op { switch typedLeft := left.(type) { + case map[string]interface{}: + if typedRight, ok := right.(map[string]interface{}); ok { + ops := []Op{} + var allKeys []string + for k := range typedLeft { + allKeys = append(allKeys, k) + } + for k := range typedRight { + if _, found := typedLeft[k]; !found { + allKeys = append(allKeys, k) + } + } + sort.SliceStable(allKeys, func(i, j int) bool { + return allKeys[i] < allKeys[j] + }) + for _, k := range allKeys { + newTokens := append([]Token{}, tokens...) + if leftVal, found := typedLeft[k]; found { + newTokens = append(newTokens, KeyToken{Key: k}) + if rightVal, found := typedRight[k]; found { + ops = append(ops, d.calculate(leftVal, rightVal, newTokens)...) + } else { // remove existing + ops = append(ops, + TestOp{Path: NewPointer(newTokens), Value: leftVal}, + RemoveOp{Path: NewPointer(newTokens)}, + ) + } + } else { // add new + testOpTokens := append([]Token{}, newTokens...) + testOpTokens = append(testOpTokens, KeyToken{Key: k}) + newTokens = append(newTokens, KeyToken{Key: k, Optional: true}) + ops = append(ops, + TestOp{Path: NewPointer(testOpTokens), Absent: true}, + ReplaceOp{Path: NewPointer(newTokens), Value: typedRight[k]}, + ) + } + } + return ops + } + return []Op{ + TestOp{Path: NewPointer(tokens), Value: left}, + ReplaceOp{Path: NewPointer(tokens), Value: right}, + } + case map[interface{}]interface{}: if typedRight, ok := right.(map[interface{}]interface{}); ok { ops := []Op{} @@ -114,7 +158,7 @@ func (d Diff) calculate(left, right interface{}, tokens []Token) []Op { } default: - if !reflect.DeepEqual(left, right) { + if !reflect.DeepEqual(jsonToYAMLValue(left), jsonToYAMLValue(right)) { return []Op{ TestOp{Path: NewPointer(tokens), Value: left}, ReplaceOp{Path: NewPointer(tokens), Value: right}, @@ -125,6 +169,32 @@ func (d Diff) calculate(left, right interface{}, tokens []Token) []Op { return []Op{} } +// The Go JSON library doesn't try to pick the right number type (int, float, +// etc.) when unmarshalling to interface{}, it just picks float64 +// universally +func jsonToYAMLValue(j interface{}) interface{} { + switch j := j.(type) { + case float64: + // replicate the logic in https://github.com/go-yaml/yaml/blob/51d6538a90f86fe93ac480b35f37b2be17fef232/resolve.go#L151 + if i64 := int64(j); j == float64(i64) { + if i := int(i64); i64 == int64(i) { + return i + } + return i64 + } + if ui64 := uint64(j); j == float64(ui64) { + return ui64 + } + return j + case int64: + if i := int(j); j == int64(i) { + return i + } + return j + } + return j +} + func max(a, b int) int { if a > b { return a diff --git a/vendor/modules.txt b/vendor/modules.txt index a8bc1aaea..5951c85fb 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/cppforlife/go-cli-ui/errors github.com/cppforlife/go-cli-ui/ui github.com/cppforlife/go-cli-ui/ui/table github.com/cppforlife/go-cli-ui/ui/test -# github.com/cppforlife/go-patch v0.2.0 +# github.com/cppforlife/go-patch v0.0.0-20240118020416-2147782e467b ## explicit github.com/cppforlife/go-patch/patch # github.com/davecgh/go-spew v1.1.1