1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package mergepatch
18
19import (
20	"fmt"
21	"reflect"
22
23	"github.com/davecgh/go-spew/spew"
24	"sigs.k8s.io/yaml"
25)
26
27// PreconditionFunc asserts that an incompatible change is not present within a patch.
28type PreconditionFunc func(interface{}) bool
29
30// RequireKeyUnchanged returns a precondition function that fails if the provided key
31// is present in the patch (indicating that its value has changed).
32func RequireKeyUnchanged(key string) PreconditionFunc {
33	return func(patch interface{}) bool {
34		patchMap, ok := patch.(map[string]interface{})
35		if !ok {
36			return true
37		}
38
39		// The presence of key means that its value has been changed, so the test fails.
40		_, ok = patchMap[key]
41		return !ok
42	}
43}
44
45// RequireMetadataKeyUnchanged creates a precondition function that fails
46// if the metadata.key is present in the patch (indicating its value
47// has changed).
48func RequireMetadataKeyUnchanged(key string) PreconditionFunc {
49	return func(patch interface{}) bool {
50		patchMap, ok := patch.(map[string]interface{})
51		if !ok {
52			return true
53		}
54		patchMap1, ok := patchMap["metadata"]
55		if !ok {
56			return true
57		}
58		patchMap2, ok := patchMap1.(map[string]interface{})
59		if !ok {
60			return true
61		}
62		_, ok = patchMap2[key]
63		return !ok
64	}
65}
66
67func ToYAMLOrError(v interface{}) string {
68	y, err := toYAML(v)
69	if err != nil {
70		return err.Error()
71	}
72
73	return y
74}
75
76func toYAML(v interface{}) (string, error) {
77	y, err := yaml.Marshal(v)
78	if err != nil {
79		return "", fmt.Errorf("yaml marshal failed:%v\n%v\n", err, spew.Sdump(v))
80	}
81
82	return string(y), nil
83}
84
85// HasConflicts returns true if the left and right JSON interface objects overlap with
86// different values in any key. All keys are required to be strings. Since patches of the
87// same Type have congruent keys, this is valid for multiple patch types. This method
88// supports JSON merge patch semantics.
89//
90// NOTE: Numbers with different types (e.g. int(0) vs int64(0)) will be detected as conflicts.
91//       Make sure the unmarshaling of left and right are consistent (e.g. use the same library).
92func HasConflicts(left, right interface{}) (bool, error) {
93	switch typedLeft := left.(type) {
94	case map[string]interface{}:
95		switch typedRight := right.(type) {
96		case map[string]interface{}:
97			for key, leftValue := range typedLeft {
98				rightValue, ok := typedRight[key]
99				if !ok {
100					continue
101				}
102				if conflict, err := HasConflicts(leftValue, rightValue); err != nil || conflict {
103					return conflict, err
104				}
105			}
106
107			return false, nil
108		default:
109			return true, nil
110		}
111	case []interface{}:
112		switch typedRight := right.(type) {
113		case []interface{}:
114			if len(typedLeft) != len(typedRight) {
115				return true, nil
116			}
117
118			for i := range typedLeft {
119				if conflict, err := HasConflicts(typedLeft[i], typedRight[i]); err != nil || conflict {
120					return conflict, err
121				}
122			}
123
124			return false, nil
125		default:
126			return true, nil
127		}
128	case string, float64, bool, int64, nil:
129		return !reflect.DeepEqual(left, right), nil
130	default:
131		return true, fmt.Errorf("unknown type: %v", reflect.TypeOf(left))
132	}
133}
134