-
Notifications
You must be signed in to change notification settings - Fork 5.1k
/
knowntypes_normalizer.go
158 lines (142 loc) · 4.41 KB
/
knowntypes_normalizer.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
package normalizers
import (
"encoding/json"
"fmt"
"strings"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
)
//go:generate go run github.com/argoproj/argo-cd/v2/hack/known_types corev1 k8s.io/api/core/v1 corev1_known_types.go --docs diffing_known_types.txt
var knownTypes = map[string]func() interface{}{}
type knownTypeField struct {
fieldPath []string
newFieldFn func() interface{}
}
type knownTypesNormalizer struct {
typeFields map[schema.GroupKind][]knownTypeField
}
// Register some non-code-generated types here. The bulk of them are in corev1_known_types.go
func init() {
knownTypes["core/Quantity"] = func() interface{} {
return &resource.Quantity{}
}
}
// NewKnownTypesNormalizer create a normalizer that re-format custom resource fields using built-in Kubernetes types.
func NewKnownTypesNormalizer(overrides map[string]v1alpha1.ResourceOverride) (*knownTypesNormalizer, error) {
normalizer := knownTypesNormalizer{typeFields: map[schema.GroupKind][]knownTypeField{}}
for key, override := range overrides {
group, kind, err := getGroupKindForOverrideKey(key)
if err != nil {
log.Warn(err)
}
gk := schema.GroupKind{Group: group, Kind: kind}
for _, f := range override.KnownTypeFields {
if err := normalizer.addKnownField(gk, f.Field, f.Type); err != nil {
log.Warnf("Failed to configure known field normalizer: %v", err)
}
}
}
normalizer.ensureDefaultCRDsConfigured()
return &normalizer, nil
}
func (n *knownTypesNormalizer) ensureDefaultCRDsConfigured() {
rolloutGK := schema.GroupKind{Group: application.Group, Kind: "Rollout"}
if _, ok := n.typeFields[rolloutGK]; !ok {
n.typeFields[rolloutGK] = []knownTypeField{{
fieldPath: []string{"spec", "template", "spec"},
newFieldFn: func() interface{} {
return &v1.PodSpec{}
},
}}
}
}
func (n *knownTypesNormalizer) addKnownField(gk schema.GroupKind, fieldPath string, typePath string) error {
newFieldFn, ok := knownTypes[typePath]
if !ok {
return fmt.Errorf("type '%s' is not supported", typePath)
}
n.typeFields[gk] = append(n.typeFields[gk], knownTypeField{
fieldPath: strings.Split(fieldPath, "."),
newFieldFn: newFieldFn,
})
return nil
}
func normalize(obj map[string]interface{}, field knownTypeField, fieldPath []string) error {
for i := range fieldPath {
if nestedField, ok, err := unstructured.NestedFieldNoCopy(obj, fieldPath[:i+1]...); err == nil && ok {
items, ok := nestedField.([]interface{})
if !ok {
continue
}
for j := range items {
item, ok := items[j].(map[string]interface{})
if !ok {
continue
}
subPath := fieldPath[i+1:]
if len(subPath) == 0 {
newItem, err := remarshal(item, field)
if err != nil {
return err
}
items[j] = newItem
} else {
if err = normalize(item, field, subPath); err != nil {
return err
}
}
}
return unstructured.SetNestedSlice(obj, items, fieldPath[:i+1]...)
}
}
if fieldVal, ok, err := unstructured.NestedFieldNoCopy(obj, fieldPath...); ok && err == nil {
newFieldVal, err := remarshal(fieldVal, field)
if err != nil {
return err
}
err = unstructured.SetNestedField(obj, newFieldVal, fieldPath...)
if err != nil {
return err
}
}
return nil
}
func remarshal(fieldVal interface{}, field knownTypeField) (interface{}, error) {
data, err := json.Marshal(fieldVal)
if err != nil {
return nil, err
}
typedValue := field.newFieldFn()
err = json.Unmarshal(data, typedValue)
if err != nil {
return nil, err
}
data, err = json.Marshal(typedValue)
if err != nil {
return nil, err
}
var newFieldVal interface{}
err = json.Unmarshal(data, &newFieldVal)
if err != nil {
return nil, err
}
return newFieldVal, nil
}
// Normalize re-format custom resource fields using built-in Kubernetes types JSON marshaler.
// This technique allows avoiding false drift detections in CRDs that import data structures from Kubernetes codebase.
func (n *knownTypesNormalizer) Normalize(un *unstructured.Unstructured) error {
if fields, ok := n.typeFields[un.GroupVersionKind().GroupKind()]; ok {
for _, field := range fields {
err := normalize(un.Object, field, field.fieldPath)
if err != nil {
return err
}
}
}
return nil
}