diff --git a/Gopkg.lock b/Gopkg.lock index 27da157b7a..38e4f78ce8 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -40,6 +40,14 @@ pruneopts = "UT" revision = "de5bf2ad457846296e2031421a34e2568e304e35" +[[projects]] + digest = "1:464aef731a5f82ded547c62e249a2e9ec59fbbc9ddab53cda7b9857852630a61" + name = "github.com/appscode/jsonpatch" + packages = ["."] + pruneopts = "UT" + revision = "7c0e3b262f30165a8ec3d0b4c6059fd92703bfb2" + version = "1.0.0" + [[projects]] branch = "master" digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" @@ -282,14 +290,6 @@ pruneopts = "UT" revision = "60711f1a8329503b04e1c88535f419d0bb440bff" -[[projects]] - branch = "master" - digest = "1:fc2b04b0069d6b10bdef96d278fe20c345794009685ed3c8c7f1a6dc023eefec" - name = "github.com/mattbaird/jsonpatch" - packages = ["."] - pruneopts = "UT" - revision = "81af80346b1a01caae0cbc27fd3c1ba5b11e189f" - [[projects]] digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc" name = "github.com/matttproud/golang_protobuf_extensions" @@ -927,13 +927,13 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/appscode/jsonpatch", "github.com/emicklei/go-restful", "github.com/ghodss/yaml", "github.com/go-logr/logr", "github.com/go-logr/logr/testing", "github.com/go-logr/zapr", "github.com/go-openapi/spec", - "github.com/mattbaird/jsonpatch", "github.com/onsi/ginkgo", "github.com/onsi/ginkgo/config", "github.com/onsi/ginkgo/types", diff --git a/pkg/patch/patch.go b/pkg/patch/patch.go index c3153f08ab..5a93fa08fe 100644 --- a/pkg/patch/patch.go +++ b/pkg/patch/patch.go @@ -21,7 +21,7 @@ import ( "fmt" "reflect" - "github.com/mattbaird/jsonpatch" + "github.com/appscode/jsonpatch" "k8s.io/apimachinery/pkg/runtime" ) diff --git a/pkg/webhook/admission/response_test.go b/pkg/webhook/admission/response_test.go index a6538c2aef..2c8434a041 100644 --- a/pkg/webhook/admission/response_test.go +++ b/pkg/webhook/admission/response_test.go @@ -20,7 +20,7 @@ import ( "errors" "net/http" - "github.com/mattbaird/jsonpatch" + "github.com/appscode/jsonpatch" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" diff --git a/pkg/webhook/admission/types/types.go b/pkg/webhook/admission/types/types.go index 19a808fa5c..75236779ff 100644 --- a/pkg/webhook/admission/types/types.go +++ b/pkg/webhook/admission/types/types.go @@ -17,7 +17,7 @@ limitations under the License. package types import ( - "github.com/mattbaird/jsonpatch" + "github.com/appscode/jsonpatch" admissionv1beta1 "k8s.io/api/admission/v1beta1" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/webhook/admission/webhook.go b/pkg/webhook/admission/webhook.go index ad908b4a54..6a14dd6350 100644 --- a/pkg/webhook/admission/webhook.go +++ b/pkg/webhook/admission/webhook.go @@ -26,7 +26,7 @@ import ( "strings" "sync" - "github.com/mattbaird/jsonpatch" + "github.com/appscode/jsonpatch" admissionv1beta1 "k8s.io/api/admission/v1beta1" admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" diff --git a/pkg/webhook/admission/webhook_test.go b/pkg/webhook/admission/webhook_test.go index 5fe9b2aa6a..c23e51d8a9 100644 --- a/pkg/webhook/admission/webhook_test.go +++ b/pkg/webhook/admission/webhook_test.go @@ -25,7 +25,7 @@ import ( "net/http" "net/http/httptest" - "github.com/mattbaird/jsonpatch" + "github.com/appscode/jsonpatch" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" diff --git a/vendor/github.com/mattbaird/jsonpatch/.gitignore b/vendor/github.com/appscode/jsonpatch/.gitignore similarity index 97% rename from vendor/github.com/mattbaird/jsonpatch/.gitignore rename to vendor/github.com/appscode/jsonpatch/.gitignore index daf913b1b3..0e9448e052 100644 --- a/vendor/github.com/mattbaird/jsonpatch/.gitignore +++ b/vendor/github.com/appscode/jsonpatch/.gitignore @@ -22,3 +22,5 @@ _testmain.go *.exe *.test *.prof + +.idea/ diff --git a/vendor/github.com/appscode/jsonpatch/.travis.yml b/vendor/github.com/appscode/jsonpatch/.travis.yml new file mode 100644 index 0000000000..92f2439d74 --- /dev/null +++ b/vendor/github.com/appscode/jsonpatch/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.x + - tip + +env: + - GO111MODULE=on + +script: + - go test -v diff --git a/vendor/github.com/mattbaird/jsonpatch/LICENSE b/vendor/github.com/appscode/jsonpatch/LICENSE similarity index 100% rename from vendor/github.com/mattbaird/jsonpatch/LICENSE rename to vendor/github.com/appscode/jsonpatch/LICENSE diff --git a/vendor/github.com/mattbaird/jsonpatch/README.md b/vendor/github.com/appscode/jsonpatch/README.md similarity index 67% rename from vendor/github.com/mattbaird/jsonpatch/README.md rename to vendor/github.com/appscode/jsonpatch/README.md index 91c03b3fc5..bbbf72921f 100644 --- a/vendor/github.com/mattbaird/jsonpatch/README.md +++ b/vendor/github.com/appscode/jsonpatch/README.md @@ -1,10 +1,15 @@ # jsonpatch -As per http://jsonpatch.com/ JSON Patch is specified in RFC 6902 from the IETF. + +[![Build Status](https://travis-ci.org/appscode/jsonpatch.svg?branch=master)](https://travis-ci.org/appscode/jsonpatch) +[![Go Report Card](https://goreportcard.com/badge/appscode/jsonpatch "Go Report Card")](https://goreportcard.com/report/appscode/jsonpatch) +[![GoDoc](https://godoc.org/github.com/appscode/jsonpatch?status.svg "GoDoc")](https://godoc.org/github.com/appscode/jsonpatch) + +As per http://jsonpatch.com JSON Patch is specified in RFC 6902 from the IETF. JSON Patch allows you to generate JSON that describes changes you want to make to a document, so you don't have to send the whole doc. JSON Patch format is supported by HTTP PATCH method, allowing for standards based partial updates via REST APIs. -```bash -go get github.com/mattbaird/jsonpatch +```console +go get github.com/appscode/jsonpatch ``` I tried some of the other "jsonpatch" go implementations, but none of them could diff two json documents and @@ -19,13 +24,15 @@ generate format like jsonpatch.com specifies. Here's an example of the patch for ``` The API is super simple -#example + +## example + ```go package main import ( "fmt" - "github.com/mattbaird/jsonpatch" + "github.com/appscode/jsonpatch" ) var simpleA = `{"a":100, "b":200, "c":"hello"}` diff --git a/vendor/github.com/appscode/jsonpatch/go.mod b/vendor/github.com/appscode/jsonpatch/go.mod new file mode 100644 index 0000000000..458d129ec9 --- /dev/null +++ b/vendor/github.com/appscode/jsonpatch/go.mod @@ -0,0 +1,8 @@ +module github.com/appscode/jsonpatch + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/evanphx/json-patch v4.0.0+incompatible + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 +) diff --git a/vendor/github.com/appscode/jsonpatch/go.sum b/vendor/github.com/appscode/jsonpatch/go.sum new file mode 100644 index 0000000000..0972e0e1a9 --- /dev/null +++ b/vendor/github.com/appscode/jsonpatch/go.sum @@ -0,0 +1,8 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/evanphx/json-patch v4.0.0+incompatible h1:xregGRMLBeuRcwiOTHRCsPPuzCQlqhxUPbqdw+zNkLc= +github.com/evanphx/json-patch v4.0.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go b/vendor/github.com/appscode/jsonpatch/jsonpatch.go similarity index 52% rename from vendor/github.com/mattbaird/jsonpatch/jsonpatch.go rename to vendor/github.com/appscode/jsonpatch/jsonpatch.go index 295f260f5a..3e698949e5 100644 --- a/vendor/github.com/mattbaird/jsonpatch/jsonpatch.go +++ b/vendor/github.com/appscode/jsonpatch/jsonpatch.go @@ -8,20 +8,22 @@ import ( "strings" ) -var errBadJSONDoc = fmt.Errorf("Invalid JSON Document") +var errBadJSONDoc = fmt.Errorf("invalid JSON Document") -type JsonPatchOperation struct { +type JsonPatchOperation = Operation + +type Operation struct { Operation string `json:"op"` Path string `json:"path"` Value interface{} `json:"value,omitempty"` } -func (j *JsonPatchOperation) Json() string { +func (j *Operation) Json() string { b, _ := json.Marshal(j) return string(b) } -func (j *JsonPatchOperation) MarshalJSON() ([]byte, error) { +func (j *Operation) MarshalJSON() ([]byte, error) { var b bytes.Buffer b.WriteString("{") b.WriteString(fmt.Sprintf(`"op":"%s"`, j.Operation)) @@ -39,14 +41,14 @@ func (j *JsonPatchOperation) MarshalJSON() ([]byte, error) { return b.Bytes(), nil } -type ByPath []JsonPatchOperation +type ByPath []Operation func (a ByPath) Len() int { return len(a) } func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path } -func NewPatch(operation, path string, value interface{}) JsonPatchOperation { - return JsonPatchOperation{Operation: operation, Path: path, Value: value} +func NewPatch(operation, path string, value interface{}) Operation { + return Operation{Operation: operation, Path: path, Value: value} } // CreatePatch creates a patch as specified in http://jsonpatch.com/ @@ -55,7 +57,7 @@ func NewPatch(operation, path string, value interface{}) JsonPatchOperation { // The function will return an array of JsonPatchOperations // // An error will be returned if any of the two documents are invalid. -func CreatePatch(a, b []byte) ([]JsonPatchOperation, error) { +func CreatePatch(a, b []byte) ([]Operation, error) { aI := map[string]interface{}{} bI := map[string]interface{}{} err := json.Unmarshal(a, &aI) @@ -66,7 +68,7 @@ func CreatePatch(a, b []byte) ([]JsonPatchOperation, error) { if err != nil { return nil, errBadJSONDoc } - return diff(aI, bI, "", []JsonPatchOperation{}) + return diff(aI, bI, "", []Operation{}) } // Returns true if the values matches (must be json types) @@ -78,22 +80,25 @@ func matchesValue(av, bv interface{}) bool { } switch at := av.(type) { case string: - bt := bv.(string) - if bt == at { + bt, ok := bv.(string) + if ok && bt == at { return true } case float64: - bt := bv.(float64) - if bt == at { + bt, ok := bv.(float64) + if ok && bt == at { return true } case bool: - bt := bv.(bool) - if bt == at { + bt, ok := bv.(bool) + if ok && bt == at { return true } case map[string]interface{}: - bt := bv.(map[string]interface{}) + bt, ok := bv.(map[string]interface{}) + if !ok { + return false + } for key := range at { if !matchesValue(at[key], bt[key]) { return false @@ -106,7 +111,10 @@ func matchesValue(av, bv interface{}) bool { } return true case []interface{}: - bt := bv.([]interface{}) + bt, ok := bv.([]interface{}) + if !ok { + return false + } if len(bt) != len(at) { return false } @@ -148,7 +156,7 @@ func makePath(path string, newPart interface{}) string { } // diff returns the (recursive) difference between a and b as an array of JsonPatchOperations. -func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation) ([]JsonPatchOperation, error) { +func diff(a, b map[string]interface{}, path string, patch []Operation) ([]Operation, error) { for key, bv := range b { p := makePath(path, key) av, ok := a[key] @@ -157,11 +165,6 @@ func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation) patch = append(patch, NewPatch("add", p, bv)) continue } - // If types have changed, replace completely - if reflect.TypeOf(av) != reflect.TypeOf(bv) { - patch = append(patch, NewPatch("replace", p, bv)) - continue - } // Types are the same, compare values var err error patch, err = handleValues(av, bv, p, patch) @@ -181,7 +184,21 @@ func diff(a, b map[string]interface{}, path string, patch []JsonPatchOperation) return patch, nil } -func handleValues(av, bv interface{}, p string, patch []JsonPatchOperation) ([]JsonPatchOperation, error) { +func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation, error) { + { + at := reflect.TypeOf(av) + bt := reflect.TypeOf(bv) + if at == nil && bt == nil { + // do nothing + return patch, nil + } else if at == nil && bt != nil { + return append(patch, NewPatch("add", p, bv)), nil + } else if at != bt { + // If types have changed, replace completely (preserves null in destination) + return append(patch, NewPatch("replace", p, bv)), nil + } + } + var err error switch at := av.(type) { case map[string]interface{}: @@ -195,63 +212,125 @@ func handleValues(av, bv interface{}, p string, patch []JsonPatchOperation) ([]J patch = append(patch, NewPatch("replace", p, bv)) } case []interface{}: - bt, ok := bv.([]interface{}) - if !ok { - // array replaced by non-array - patch = append(patch, NewPatch("replace", p, bv)) - } else if len(at) != len(bt) { - // arrays are not the same length - patch = append(patch, compareArray(at, bt, p)...) - + bt := bv.([]interface{}) + if isSimpleArray(at) && isSimpleArray(bt) { + patch = append(patch, compareEditDistance(at, bt, p)...) } else { - for i := range bt { + n := min(len(at), len(bt)) + for i := len(at) - 1; i >= n; i-- { + patch = append(patch, NewPatch("remove", makePath(p, i), nil)) + } + for i := n; i < len(bt); i++ { + patch = append(patch, NewPatch("add", makePath(p, i), bt[i])) + } + for i := 0; i < n; i++ { + var err error patch, err = handleValues(at[i], bt[i], makePath(p, i), patch) if err != nil { return nil, err } } } - case nil: - switch bv.(type) { - case nil: - // Both nil, fine. - default: - patch = append(patch, NewPatch("add", p, bv)) - } default: panic(fmt.Sprintf("Unknown type:%T ", av)) } return patch, nil } -func compareArray(av, bv []interface{}, p string) []JsonPatchOperation { - retval := []JsonPatchOperation{} - // var err error - for i, v := range av { - found := false - for _, v2 := range bv { - if reflect.DeepEqual(v, v2) { - found = true - break +func isBasicType(a interface{}) bool { + switch a.(type) { + case string, float64, bool: + default: + return false + } + return true +} + +func isSimpleArray(a []interface{}) bool { + for i := range a { + switch a[i].(type) { + case string, float64, bool: + default: + val := reflect.ValueOf(a[i]) + if val.Kind() == reflect.Map { + for _, k := range val.MapKeys() { + av := val.MapIndex(k) + if av.Kind() == reflect.Ptr || av.Kind() == reflect.Interface { + if av.IsNil() { + continue + } + av = av.Elem() + } + if av.Kind() != reflect.String && av.Kind() != reflect.Float64 && av.Kind() != reflect.Bool { + return false + } + } + return true } + return false } - if !found { - retval = append(retval, NewPatch("remove", makePath(p, i), nil)) - } + } + return true +} + +// https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm +// Adapted from https://github.com/texttheater/golang-levenshtein +func compareEditDistance(s, t []interface{}, p string) []Operation { + m := len(s) + n := len(t) + + d := make([][]int, m+1) + for i := 0; i <= m; i++ { + d[i] = make([]int, n+1) + d[i][0] = i + } + for j := 0; j <= n; j++ { + d[0][j] = j } - for i, v := range bv { - found := false - for _, v2 := range av { - if reflect.DeepEqual(v, v2) { - found = true - break + for j := 1; j <= n; j++ { + for i := 1; i <= m; i++ { + if reflect.DeepEqual(s[i-1], t[j-1]) { + d[i][j] = d[i-1][j-1] // no op required + } else { + del := d[i-1][j] + 1 + add := d[i][j-1] + 1 + rep := d[i-1][j-1] + 1 + d[i][j] = min(rep, min(add, del)) } } - if !found { - retval = append(retval, NewPatch("add", makePath(p, i), v)) - } } - return retval + return backtrace(s, t, p, m, n, d) +} + +func min(x int, y int) int { + if y < x { + return y + } + return x +} + +func backtrace(s, t []interface{}, p string, i int, j int, matrix [][]int) []Operation { + if i > 0 && matrix[i-1][j]+1 == matrix[i][j] { + op := NewPatch("remove", makePath(p, i-1), nil) + return append([]Operation{op}, backtrace(s, t, p, i-1, j, matrix)...) + } + if j > 0 && matrix[i][j-1]+1 == matrix[i][j] { + op := NewPatch("add", makePath(p, i), t[j-1]) + return append([]Operation{op}, backtrace(s, t, p, i, j-1, matrix)...) + } + if i > 0 && j > 0 && matrix[i-1][j-1]+1 == matrix[i][j] { + if isBasicType(s[0]) { + op := NewPatch("replace", makePath(p, i-1), t[j-1]) + return append([]Operation{op}, backtrace(s, t, p, i-1, j-1, matrix)...) + } + + p2, _ := handleValues(s[j-1], t[j-1], makePath(p, i-1), []Operation{}) + return append(p2, backtrace(s, t, p, i-1, j-1, matrix)...) + } + if i > 0 && j > 0 && matrix[i-1][j-1] == matrix[i][j] { + return backtrace(s, t, p, i-1, j-1, matrix) + } + return []Operation{} }