diff --git a/store/proxy/proxy_store.go b/store/proxy/proxy_store.go index 6c245804..8b16db0e 100644 --- a/store/proxy/proxy_store.go +++ b/store/proxy/proxy_store.go @@ -10,6 +10,7 @@ import ( "github.com/rancher/norman/restwatch" "github.com/rancher/norman/types" "github.com/rancher/norman/types/convert" + "github.com/rancher/norman/types/convert/merge" "github.com/rancher/norman/types/values" "github.com/sirupsen/logrus" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" @@ -326,7 +327,7 @@ func (p *Store) Update(apiContext *types.APIContext, schema *types.Schema, data } p.toInternal(schema.Mapper, data) - existing = convert.APIUpdateMerge(existing, data, apiContext.Query.Get("_replace") == "true") + existing = merge.APIUpdateMerge(schema.InternalSchema, apiContext.Schemas, existing, data, apiContext.Query.Get("_replace") == "true") values.PutValue(existing, resourceVersion, "metadata", "resourceVersion") values.PutValue(existing, namespace, "metadata", "namespace") diff --git a/types/convert/merge.go b/types/convert/merge.go deleted file mode 100644 index 5f4d30eb..00000000 --- a/types/convert/merge.go +++ /dev/null @@ -1,88 +0,0 @@ -package convert - -import ( - "strings" -) - -func APIUpdateMerge(dest, src map[string]interface{}, replace bool) map[string]interface{} { - result := map[string]interface{}{} - if replace { - if status, ok := dest["status"]; ok { - result["status"] = status - } - if metadata, ok := dest["metadata"]; ok { - result["metadata"] = metadata - } - } else { - result = copyMap(dest) - } - - for k, v := range src { - if k == "metadata" { - result["metadata"] = mergeMetadata(ToMapInterface(dest["metadata"]), ToMapInterface(v)) - } else if k == "status" { - continue - } - - existing, ok := dest[k] - if ok && !replace { - result[k] = merge(existing, v) - } else { - result[k] = v - } - } - - return result -} - -func mergeMetadata(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { - result := copyMap(dest) - - labels := mergeMaps(ToMapInterface(dest["labels"]), ToMapInterface(src["labels"])) - - existingAnnotation := ToMapInterface(dest["annotations"]) - newAnnotation := ToMapInterface(src["annotations"]) - annotations := copyMap(existingAnnotation) - - for k, v := range newAnnotation { - if strings.Contains(k, "cattle.io/") { - continue - } - annotations[k] = v - } - for k, v := range existingAnnotation { - if strings.Contains(k, "cattle.io/") { - annotations[k] = v - } - } - - result["labels"] = labels - result["annotations"] = annotations - - return result -} - -func merge(dest, src interface{}) interface{} { - sm, smOk := src.(map[string]interface{}) - dm, dmOk := dest.(map[string]interface{}) - if smOk && dmOk { - return mergeMaps(dm, sm) - } - return src -} - -func mergeMaps(dest map[string]interface{}, src map[string]interface{}) interface{} { - result := copyMap(dest) - for k, v := range src { - result[k] = merge(dest[k], v) - } - return result -} - -func copyMap(src map[string]interface{}) map[string]interface{} { - result := map[string]interface{}{} - for k, v := range src { - result[k] = v - } - return result -} diff --git a/types/convert/merge/merge.go b/types/convert/merge/merge.go new file mode 100644 index 00000000..fa44f786 --- /dev/null +++ b/types/convert/merge/merge.go @@ -0,0 +1,138 @@ +package merge + +import ( + "strings" + + "github.com/rancher/norman/types" + "github.com/rancher/norman/types/convert" +) + +func APIUpdateMerge(schema *types.Schema, schemas *types.Schemas, dest, src map[string]interface{}, replace bool) map[string]interface{} { + result := map[string]interface{}{} + if replace { + if status, ok := dest["status"]; ok { + result["status"] = status + } + if metadata, ok := dest["metadata"]; ok { + result["metadata"] = metadata + } + } else { + result = copyMap(dest) + } + + for k, v := range src { + if k == "metadata" { + result["metadata"] = mergeMetadata(convert.ToMapInterface(dest["metadata"]), convert.ToMapInterface(v)) + continue + } else if k == "status" { + continue + } + + existing, ok := dest[k] + if ok && !replace { + result[k] = merge(k, schema, schemas, existing, v) + } else { + result[k] = v + } + } + + return result +} + +func mergeProtected(dest, src map[string]interface{}) map[string]interface{} { + if src == nil { + return dest + } + + result := copyMap(dest) + + for k, v := range src { + if strings.Contains(k, "cattle.io/") { + continue + } + result[k] = v + } + + for k := range dest { + if strings.Contains(k, "cattle.io/") { + continue + } + if _, ok := src[k]; !ok { + delete(dest, k) + } + } + + return result +} + +func mergeMetadata(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} { + result := copyMap(dest) + + labels := convert.ToMapInterface(dest["labels"]) + srcLabels := convert.ToMapInterface(src["labels"]) + labels = mergeProtected(labels, srcLabels) + + annotations := convert.ToMapInterface(dest["annotations"]) + srcAnnotation := convert.ToMapInterface(src["annotations"]) + annotations = mergeProtected(annotations, srcAnnotation) + + result["labels"] = labels + result["annotations"] = annotations + + return result +} + +func merge(field string, schema *types.Schema, schemas *types.Schemas, dest, src interface{}) interface{} { + if dest == nil { + return src + } + if src == nil { + return dest + } + + if isMap(field, schema) { + return src + } + + sm, smOk := src.(map[string]interface{}) + dm, dmOk := dest.(map[string]interface{}) + if smOk && dmOk { + return mergeMaps(getSchema(field, schema, schemas), schemas, dm, sm) + } + return src +} + +func getSchema(field string, schema *types.Schema, schemas *types.Schemas) *types.Schema { + if schema == nil { + return nil + } + s := schemas.Schema(&schema.Version, schema.ResourceFields[field].Type) + if s != nil && s.InternalSchema != nil { + return s.InternalSchema + } + return s +} + +func isMap(field string, schema *types.Schema) bool { + if schema == nil { + return false + } + f := schema.ResourceFields[field] + return strings.HasPrefix(f.Type, "map[") +} + +func mergeMaps(schema *types.Schema, schemas *types.Schemas, dest map[string]interface{}, src map[string]interface{}) interface{} { + result := copyMap(dest) + for k, v := range src { + result[k] = merge(k, schema, schemas, dest[k], v) + } + return result +} + +func copyMap(src map[string]interface{}) map[string]interface{} { + result := map[string]interface{}{} + for k, v := range src { + result[k] = v + } + return result +} diff --git a/types/server_types.go b/types/server_types.go index f58c9915..8482686a 100644 --- a/types/server_types.go +++ b/types/server_types.go @@ -95,11 +95,9 @@ type APIContext struct { ResponseWriter ResponseWriter QueryFilter QueryFilter SubContextAttributeProvider SubContextAttributeProvider - //QueryOptions *QueryOptions - URLBuilder URLBuilder - AccessControl AccessControl - SubContext map[string]string - //Attributes map[string]interface{} + URLBuilder URLBuilder + AccessControl AccessControl + SubContext map[string]string Request *http.Request Response http.ResponseWriter