forked from openshift/library-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
generic_config_merger.go
134 lines (112 loc) · 4.03 KB
/
generic_config_merger.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
package resourcemerge
import (
"bytes"
"encoding/json"
"fmt"
"reflect"
"k8s.io/klog"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
kyaml "k8s.io/apimachinery/pkg/util/yaml"
)
// MergeConfigMap takes a configmap, the target key, special overlay funcs a list of config configs to overlay on top of each other
// It returns the resultant configmap and a bool indicating if any changes were made to the configmap
func MergeConfigMap(configMap *corev1.ConfigMap, configKey string, specialCases map[string]MergeFunc, configYAMLs ...[]byte) (*corev1.ConfigMap, bool, error) {
configBytes, err := MergeProcessConfig(specialCases, configYAMLs...)
if err != nil {
return nil, false, err
}
if reflect.DeepEqual(configMap.Data[configKey], configBytes) {
return configMap, false, nil
}
ret := configMap.DeepCopy()
ret.Data[configKey] = string(configBytes)
return ret, true, nil
}
// MergeProcessConfig merges a series of config yaml files together with each later one overlaying all previous
func MergeProcessConfig(specialCases map[string]MergeFunc, configYAMLs ...[]byte) ([]byte, error) {
currentConfigYAML := configYAMLs[0]
for _, currConfigYAML := range configYAMLs[1:] {
prevConfigJSON, err := kyaml.ToJSON(currentConfigYAML)
if err != nil {
klog.Warning(err)
// maybe it's just json
prevConfigJSON = currentConfigYAML
}
prevConfig := map[string]interface{}{}
if err := json.NewDecoder(bytes.NewBuffer(prevConfigJSON)).Decode(&prevConfig); err != nil {
return nil, err
}
if len(currConfigYAML) > 0 {
currConfigJSON, err := kyaml.ToJSON(currConfigYAML)
if err != nil {
klog.Warning(err)
// maybe it's just json
currConfigJSON = currConfigYAML
}
currConfig := map[string]interface{}{}
if err := json.NewDecoder(bytes.NewBuffer(currConfigJSON)).Decode(&currConfig); err != nil {
return nil, err
}
// protected against mismatched typemeta
prevAPIVersion, _, _ := unstructured.NestedString(prevConfig, "apiVersion")
prevKind, _, _ := unstructured.NestedString(prevConfig, "kind")
currAPIVersion, _, _ := unstructured.NestedString(currConfig, "apiVersion")
currKind, _, _ := unstructured.NestedString(currConfig, "kind")
currGVKSet := len(currAPIVersion) > 0 || len(currKind) > 0
gvkMismatched := currAPIVersion != prevAPIVersion || currKind != prevKind
if currGVKSet && gvkMismatched {
return nil, fmt.Errorf("%v/%v does not equal %v/%v", currAPIVersion, currKind, prevAPIVersion, prevKind)
}
if err := mergeConfig(prevConfig, currConfig, "", specialCases); err != nil {
return nil, err
}
}
currentConfigYAML, err = runtime.Encode(unstructured.UnstructuredJSONScheme, &unstructured.Unstructured{Object: prevConfig})
if err != nil {
return nil, err
}
}
return currentConfigYAML, nil
}
type MergeFunc func(dst, src interface{}, currentPath string) (interface{}, error)
// mergeConfig overwrites entries in curr by additional. It modifies curr.
func mergeConfig(curr, additional map[string]interface{}, currentPath string, specialCases map[string]MergeFunc) error {
for additionalKey, additionalVal := range additional {
fullKey := currentPath + "." + additionalKey
specialCase, ok := specialCases[fullKey]
if ok {
var err error
curr[additionalKey], err = specialCase(curr[additionalKey], additionalVal, currentPath)
if err != nil {
return err
}
continue
}
currVal, ok := curr[additionalKey]
if !ok {
curr[additionalKey] = additionalVal
continue
}
// only some scalars are accepted
switch castVal := additionalVal.(type) {
case map[string]interface{}:
currValAsMap, ok := currVal.(map[string]interface{})
if !ok {
currValAsMap = map[string]interface{}{}
curr[additionalKey] = currValAsMap
}
err := mergeConfig(currValAsMap, castVal, fullKey, specialCases)
if err != nil {
return err
}
continue
default:
if err := unstructured.SetNestedField(curr, castVal, additionalKey); err != nil {
return err
}
}
}
return nil
}