/
patchesUtils.go
167 lines (139 loc) · 4.38 KB
/
patchesUtils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package mutate
import (
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/mattbaird/jsonpatch"
evanjsonpatch "github.com/evanphx/json-patch"
"github.com/go-logr/logr"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func generatePatches(src, dst []byte) ([][]byte, error) {
var patchesBytes [][]byte
pp, err := jsonpatch.CreatePatch(src, dst)
for _, p := range pp {
// TODO: handle remove nil value, i.e.,
// {remove /spec/securityContext <nil>}
// {remove /status/conditions/0/lastProbeTime <nil>}
pbytes, err := p.MarshalJSON()
if err != nil {
return patchesBytes, err
}
patchesBytes = append(patchesBytes, pbytes)
fmt.Printf("generated patch %s\n", p)
}
return patchesBytes, err
}
// preProcessJSONPatchesgo deals with the JsonPatch when reinvocation
// policy is set in webhook, to avoid generating duplicate values.
// This duplicate error only occurs on type array, if it's adding to a map
// the value will be added to the map if nil, otherwise it overwrites the old value
// return skip == true to skip the json patch application
func preProcessJSONPatches(mutation kyverno.Mutation, resource unstructured.Unstructured,
log logr.Logger) (skip bool, err error) {
var patches evanjsonpatch.Patch
log = log.WithName("preProcessJSONPatches")
if len(mutation.PatchesJSON6902) > 0 {
patches, err = decodePatch(mutation.PatchesJSON6902)
if err != nil {
return false, fmt.Errorf("failed to process JSON patches: %v", err)
}
}
for _, patch := range patches {
if patch.Kind() != "add" {
continue
}
path, err := patch.Path()
if err != nil {
return false, fmt.Errorf("failed to get path in JSON Patch: %v", err)
}
// check if the target is the list
if tail := filepath.Base(path); tail != "-" {
_, err := strconv.Atoi(tail)
if err != nil {
log.V(4).Info("JSON patch does not add to the list, skipping", "path", path)
continue
}
}
resourceObj, err := getObject(path, resource.UnstructuredContent())
if err != nil {
log.V(4).Info("failed to get object by the given path", "path", path, "error", err.Error())
continue
}
val, err := patch.ValueInterface()
if err != nil {
log.V(4).Info("failed to get value by the given path", "path", path, "error", err.Error())
continue
}
// if there's one patch exist in the resource, which indicates
// this is re-invoked JSON patches, skip application
if isSubsetObject(val, resourceObj) {
return true, nil
}
}
return false, nil
}
// - insert to the end of the list
// {"op": "add", "path": "/spec/containers/-", {"value": "{"name":"busyboxx","image":"busybox:latest"}"}
// - insert value to the certain element of the list
// {"op": "add", "path": "/spec/containers/1", {"value": "{"name":"busyboxx","image":"busybox:latest"}"}
func getObject(path string, resource map[string]interface{}) (interface{}, error) {
var strippedResource interface{}
strippedResource = resource
var ok bool
if strings.HasPrefix(path, "/") {
path = path[1:]
}
paths := strings.Split(path, "/")
for i, key := range paths {
switch strippedResource.(type) {
case map[string]interface{}:
strippedResource, ok = strippedResource.(map[string]interface{})[key]
if !ok {
return nil, fmt.Errorf("referenced value does not exist at %s", strings.Join(paths[:i+1], "/"))
}
case []interface{}:
var idx int
if key == "-" {
idx = len(strippedResource.([]interface{})) - 1
} else {
var err error
idx, err = strconv.Atoi(key)
if err != nil {
return nil, fmt.Errorf("cannot parse index in JSON Patch at %s: %v", strings.Join(paths[:i+1], "/"), err)
}
}
if len(strippedResource.([]interface{})) <= idx {
return nil, nil
}
strippedResource = strippedResource.([]interface{})[idx]
}
}
return strippedResource, nil
}
// isSubsetObject returns true if object is subset of resource
// the object/resource is the element inside the list, return false
// if the type is mismatched (not map)
func isSubsetObject(object, resource interface{}) bool {
objectMap, ok := object.(map[string]interface{})
if !ok {
return false
}
resourceMap, ok := resource.(map[string]interface{})
if !ok {
return false
}
for objKey, objVal := range objectMap {
rsrcVal, ok := resourceMap[objKey]
if !ok {
return false
}
if !reflect.DeepEqual(objVal, rsrcVal) {
return false
}
}
return true
}