17
17
// https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#fieldsv1-v1-meta
18
18
// https://github.com/kubernetes-sigs/structured-merge-diff
19
19
// https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-field-management.html
20
+ // see also: https://kubernetes.slack.com/archives/C0123CNN8F3/p1686141087220719
20
21
public class SSABasedGenericKubernetesResourceMatcher <R extends HasMetadata > {
21
22
22
23
@ SuppressWarnings ("rawtypes" )
@@ -34,51 +35,45 @@ public static <L extends HasMetadata> SSABasedGenericKubernetesResourceMatcher<L
34
35
35
36
private static final String F_PREFIX = "f:" ;
36
37
private static final String K_PREFIX = "k:" ;
38
+ private static final String V_PREFIX = "v:" ;
37
39
public static final String METADATA_KEY = "metadata" ;
38
40
39
41
private static final Logger log =
40
42
LoggerFactory .getLogger (SSABasedGenericKubernetesResourceMatcher .class );
41
43
42
- // todo list orders should be irrelevant, change lists to sets?
43
- // todo owner reference removal?
44
- public boolean matches (R actual , R desired , Context <?> context ) {
45
44
46
- var optionalManagedFieldsEntry =
47
- checkIfFieldManagerExists (actual ,
48
- context .getControllerConfiguration ().fieldManager ());
49
- // the update will add the field manager
50
- // todo check if it's an apply, since if not it could be a different field manager?
51
- if (optionalManagedFieldsEntry .isEmpty ()) {
52
- return false ;
53
- }
45
+ public boolean matches (R actual , R desired , Context <?> context ) {
46
+ try {
47
+ var optionalManagedFieldsEntry =
48
+ checkIfFieldManagerExists (actual ,
49
+ context .getControllerConfiguration ().fieldManager ());
50
+ // the results of this is that it will add the field manager; it's important from migration
51
+ // aspect
52
+ if (optionalManagedFieldsEntry .isEmpty ()) {
53
+ return false ;
54
+ }
54
55
55
- var managedFieldsEntry = optionalManagedFieldsEntry .orElseThrow ();
56
+ var managedFieldsEntry = optionalManagedFieldsEntry .orElseThrow ();
56
57
57
- var objectMapper =
58
- context .getControllerConfiguration ().getConfigurationService ().getObjectMapper ();
58
+ var objectMapper =
59
+ context .getControllerConfiguration ().getConfigurationService ().getObjectMapper ();
59
60
60
- var actualMap = objectMapper .convertValue (actual , typeRef );
61
- var desiredMap = objectMapper .convertValue (desired , typeRef );
61
+ var actualMap = objectMapper .convertValue (actual , typeRef );
62
+ var desiredMap = objectMapper .convertValue (desired , typeRef );
62
63
63
- log .trace ("Original actual: \n {} \n original desired: \n {} " , actual , desiredMap );
64
+ log .trace ("Original actual: \n {} \n original desired: \n {} " , actual , desiredMap );
64
65
65
- var prunedActual = new HashMap <String , Object >();
66
- pruneActualAccordingManagedFields (prunedActual , actualMap ,
67
- managedFieldsEntry .getFieldsV1 ().getAdditionalProperties (), objectMapper );
68
- removeIrrelevantValues (desiredMap );
66
+ var prunedActual = new HashMap <String , Object >();
67
+ pruneActualAccordingManagedFields (prunedActual , actualMap ,
68
+ managedFieldsEntry .getFieldsV1 ().getAdditionalProperties (), objectMapper );
69
69
70
- log . trace ( "Pruned actual: \n {} \n desired: \n {} " , prunedActual , desiredMap );
70
+ removeIrrelevantValues ( desiredMap );
71
71
72
- return prunedActual .equals (desiredMap );
73
- }
72
+ log .trace ("Pruned actual: \n {} \n desired: \n {} " , prunedActual , desiredMap );
74
73
75
- private void removeOwnerRefernces (HashMap <String , Object > resourceMap ) {
76
- var metadata = (Map <String , Object >) resourceMap .get (METADATA_KEY );
77
- if (metadata != null ) {
78
- metadata .remove ("ownerReferences" );
79
- if (metadata .isEmpty ()) {
80
- resourceMap .remove (METADATA_KEY );
81
- }
74
+ return prunedActual .equals (desiredMap );
75
+ } catch (JsonProcessingException e ) {
76
+ throw new IllegalStateException (e );
82
77
}
83
78
}
84
79
@@ -95,66 +90,137 @@ private void removeIrrelevantValues(HashMap<String, Object> desiredMap) {
95
90
96
91
private void pruneActualAccordingManagedFields (Map <String , Object > result ,
97
92
Map <String , Object > actualMap ,
98
- Map <String , Object > managedFields , ObjectMapper objectMapper ) {
93
+ Map <String , Object > managedFields , ObjectMapper objectMapper ) throws JsonProcessingException {
94
+
99
95
if (managedFields .isEmpty ()) {
100
96
result .putAll (actualMap );
101
97
return ;
102
98
}
103
99
for (Map .Entry <String , Object > entry : managedFields .entrySet ()) {
104
100
String key = entry .getKey ();
105
101
if (key .startsWith (F_PREFIX )) {
106
- String targetKey = key .substring (2 );
102
+ String keyInActual = key .substring (2 );
107
103
var managedFieldValue = entry .getValue ();
108
- if (managedFieldValue instanceof Map && (!((Map ) managedFieldValue ).isEmpty ())) {
104
+ // basically if we should travers further
105
+ if (isNestedValue (managedFieldValue )) {
109
106
var managedEntrySet = ((Map <String , Object >) managedFieldValue ).entrySet ();
110
107
111
- if (isListEntrySet (managedEntrySet )) {
112
- var valueList = new ArrayList <>();
113
- result .put (targetKey , valueList );
114
- for (Map .Entry <String , Object > listEntry : managedEntrySet ) {
115
- var actualListEntry = selectListEntryBasedOnKey (listEntry .getKey ().substring (2 ),
116
- (List <Map <String , Object >>) actualMap .get (targetKey ), objectMapper );
117
-
118
- if (listEntry .getValue () instanceof Map
119
- && ((Map <String , Object >) listEntry .getValue ()).isEmpty ()) {
120
- var map = new HashMap <String , Object >();
121
- valueList .add (map );
122
- pruneActualAccordingManagedFields (map , actualListEntry ,
123
- (Map <String , Object >) listEntry .getValue (), objectMapper );
124
- continue ;
125
- }
126
-
127
- if (DOT_KEY .equals (listEntry .getKey ())) {
128
- continue ;
129
- }
130
- var emptyResMapValue = new HashMap <String , Object >();
131
- valueList .add (emptyResMapValue );
132
- pruneActualAccordingManagedFields (emptyResMapValue , actualListEntry ,
133
- (Map <String , Object >) listEntry .getValue (), objectMapper );
134
- }
108
+ if (isListKeyEntrySet (managedEntrySet )) {
109
+ handleListKeyEntrySet (result , actualMap , objectMapper , keyInActual , managedEntrySet );
110
+ } else if (isSetValueField (managedEntrySet )) {
111
+ handleSetValues (result , actualMap , objectMapper , keyInActual , managedEntrySet );
135
112
} else {
136
- var emptyMapValue = new HashMap <String , Object >();
137
- result .put (targetKey , emptyMapValue );
138
- var actualMapValue = actualMap .get (targetKey );
139
- log .debug ("key: {} actual map value: {} managedFieldValue: {}" , targetKey ,
140
- actualMapValue , managedFieldValue );
141
-
142
- pruneActualAccordingManagedFields (emptyMapValue , (Map <String , Object >) actualMapValue ,
143
- (Map <String , Object >) managedFields .get (key ), objectMapper );
113
+ fillResultsAndTraversFurther (result , actualMap , managedFields , objectMapper , key ,
114
+ keyInActual , managedFieldValue );
144
115
}
145
116
} else {
146
- result .put (targetKey , actualMap .get (targetKey ));
117
+ // this should handle the case when the value is complex in the actual map (not just a simple value).
118
+ result .put (keyInActual , actualMap .get (keyInActual ));
147
119
}
148
120
} else {
149
- if (!"." .equals (key )) {
121
+ // .:{} is ignored, other should not be present
122
+ if (!DOT_KEY .equals (key )) {
150
123
throw new IllegalStateException ("Key: " + key + " has no prefix: " + F_PREFIX );
151
124
}
152
125
}
153
126
}
154
127
155
128
}
156
129
157
- private boolean isListEntrySet (Set <Map .Entry <String , Object >> managedEntrySet ) {
130
+ private void fillResultsAndTraversFurther (Map <String , Object > result ,
131
+ Map <String , Object > actualMap , Map <String , Object > managedFields , ObjectMapper objectMapper ,
132
+ String key , String keyInActual , Object managedFieldValue ) throws JsonProcessingException {
133
+ var emptyMapValue = new HashMap <String , Object >();
134
+ result .put (keyInActual , emptyMapValue );
135
+ var actualMapValue = actualMap .get (keyInActual );
136
+ log .debug ("key: {} actual map value: {} managedFieldValue: {}" , keyInActual ,
137
+ actualMapValue , managedFieldValue );
138
+
139
+ pruneActualAccordingManagedFields (emptyMapValue , (Map <String , Object >) actualMapValue ,
140
+ (Map <String , Object >) managedFields .get (key ), objectMapper );
141
+ }
142
+
143
+ private static boolean isNestedValue (Object managedFieldValue ) {
144
+ return managedFieldValue instanceof Map && (!((Map ) managedFieldValue ).isEmpty ());
145
+ }
146
+
147
+ // list entries referenced by key, or when "k:" prefix is used
148
+ private void handleListKeyEntrySet (Map <String , Object > result , Map <String , Object > actualMap ,
149
+ ObjectMapper objectMapper , String keyInActual , Set <Map .Entry <String , Object >> managedEntrySet )
150
+ throws JsonProcessingException {
151
+ var valueList = new ArrayList <>();
152
+ result .put (keyInActual , valueList );
153
+ for (Map .Entry <String , Object > listEntry : managedEntrySet ) {
154
+ var actualListEntry = selectListEntryBasedOnKey (listEntry .getKey ().substring (2 ),
155
+ (List <Map <String , Object >>) actualMap .get (keyInActual ), objectMapper );
156
+
157
+ if (listEntry .getValue () instanceof Map
158
+ && ((Map <String , Object >) listEntry .getValue ()).isEmpty ()) {
159
+ var map = new HashMap <String , Object >();
160
+ valueList .add (map );
161
+ pruneActualAccordingManagedFields (map , actualListEntry ,
162
+ (Map <String , Object >) listEntry .getValue (), objectMapper );
163
+ continue ;
164
+ }
165
+
166
+ if (DOT_KEY .equals (listEntry .getKey ())) {
167
+ continue ;
168
+ }
169
+ var emptyResMapValue = new HashMap <String , Object >();
170
+ valueList .add (emptyResMapValue );
171
+ pruneActualAccordingManagedFields (emptyResMapValue , actualListEntry ,
172
+ (Map <String , Object >) listEntry .getValue (), objectMapper );
173
+ }
174
+ }
175
+
176
+ // set values, the "v:" prefix
177
+ private static void handleSetValues (Map <String , Object > result , Map <String , Object > actualMap ,
178
+ ObjectMapper objectMapper , String keyInActual ,
179
+ Set <Map .Entry <String , Object >> managedEntrySet ) {
180
+ var valueList = new ArrayList <>();
181
+ result .put (keyInActual , valueList );
182
+ for (Map .Entry <String , Object > valueEntry : managedEntrySet ) {
183
+ // not clear if this can happen
184
+ if (DOT_KEY .equals (valueEntry .getKey ())) {
185
+ continue ;
186
+ }
187
+
188
+ Class <?> targetClass = null ;
189
+ List values = (List ) actualMap .get (keyInActual );
190
+ if (!(values .get (0 ) instanceof Map )) {
191
+ targetClass = values .get (0 ).getClass ();
192
+ }
193
+
194
+ var value =
195
+ parseKeyValue (valueEntry .getKey ().substring (2 ), targetClass , objectMapper );
196
+ valueList .add (value );
197
+ }
198
+ }
199
+
200
+ public static Object parseKeyValue (String stringValue , Class <?> targetClass ,
201
+ ObjectMapper objectMapper ) {
202
+ try {
203
+ stringValue = stringValue .trim ();
204
+ if (targetClass != null ) {
205
+ return objectMapper .readValue (stringValue , targetClass );
206
+ } else {
207
+ return objectMapper .readValue (stringValue , typeRef );
208
+ }
209
+ } catch (JsonProcessingException e ) {
210
+ throw new IllegalStateException (e );
211
+ }
212
+ }
213
+
214
+ private boolean isSetValueField (Set <Map .Entry <String , Object >> managedEntrySet ) {
215
+ var iterator = managedEntrySet .iterator ();
216
+ var managedFieldEntry = iterator .next ();
217
+ if (managedFieldEntry .getKey ().equals (DOT_KEY )) {
218
+ managedFieldEntry = iterator .next ();
219
+ }
220
+ return managedFieldEntry .getKey ().startsWith (V_PREFIX );
221
+ }
222
+
223
+ private boolean isListKeyEntrySet (Set <Map .Entry <String , Object >> managedEntrySet ) {
158
224
var iterator = managedEntrySet .iterator ();
159
225
var managedFieldEntry = iterator .next ();
160
226
// todo unit test
0 commit comments