Skip to content

Commit 7c90c14

Browse files
authored
Merge pull request #862 from YvanGuidoin/rework-ssa-diffoptions
ssa: Align ResourceManager.Diff skipping resources with ResourceManager.Apply
2 parents e454462 + 88a752e commit 7c90c14

File tree

2 files changed

+193
-4
lines changed

2 files changed

+193
-4
lines changed

ssa/manager_diff.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ import (
3232
// DiffOptions contains options for server-side dry-run apply requests.
3333
type DiffOptions struct {
3434
// Exclusions determines which in-cluster objects are skipped from dry-run apply
35-
// based on the specified key-value pairs.
36-
// A nil Exclusions map means all objects are applied
37-
// regardless of their metadata labels and annotations.
35+
// based on the matching labels or annotations.
3836
Exclusions map[string]string `json:"exclusions"`
37+
38+
// IfNotPresentSelector determines which in-cluster objects are skipped from dry-run apply
39+
// based on the matching labels or annotations.
40+
IfNotPresentSelector map[string]string `json:"ifNotPresentSelector"`
3941
}
4042

4143
// DefaultDiffOptions returns the default dry-run apply options.
@@ -57,7 +59,7 @@ func (m *ResourceManager) Diff(ctx context.Context, object *unstructured.Unstruc
5759
existingObject.SetGroupVersionKind(object.GroupVersionKind())
5860
_ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject)
5961

60-
if existingObject != nil && utils.AnyInMetadata(existingObject, opts.Exclusions) {
62+
if m.shouldSkipDiff(object, existingObject, opts) {
6163
return m.changeSetEntry(existingObject, SkippedAction), nil, nil, nil
6264
}
6365

@@ -123,3 +125,22 @@ func prepareObjectForDiff(object *unstructured.Unstructured) *unstructured.Unstr
123125
}
124126
return deepCopy
125127
}
128+
129+
// shouldSkipDiff determines based on the object metadata and DiffOptions if the object should be skipped.
130+
// An object is not applied if it contains a label or annotation
131+
// which matches the DiffOptions.Exclusions or DiffOptions.IfNotPresentSelector.
132+
func (m *ResourceManager) shouldSkipDiff(desiredObject *unstructured.Unstructured,
133+
existingObject *unstructured.Unstructured, opts DiffOptions) bool {
134+
if utils.AnyInMetadata(desiredObject, opts.Exclusions) ||
135+
(existingObject != nil && utils.AnyInMetadata(existingObject, opts.Exclusions)) {
136+
return true
137+
}
138+
139+
if existingObject != nil &&
140+
existingObject.GetUID() != "" &&
141+
utils.AnyInMetadata(desiredObject, opts.IfNotPresentSelector) {
142+
return true
143+
}
144+
145+
return false
146+
}

ssa/manager_diff_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,174 @@ func TestDiff_Exclusions(t *testing.T) {
230230
})
231231
}
232232

233+
func TestDiff_IfNotPresent_OnExisting(t *testing.T) {
234+
timeout := 10 * time.Second
235+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
236+
defer cancel()
237+
238+
id := generateName("ifnotpresentonexisting")
239+
objects, err := readManifest("testdata/test1.yaml", id)
240+
if err != nil {
241+
t.Fatal(err)
242+
}
243+
244+
meta := map[string]string{
245+
"fluxcd.io/ignore": "true",
246+
}
247+
248+
_, configMap := getFirstObject(objects, "ConfigMap", id)
249+
configMap.SetAnnotations(meta)
250+
251+
if _, err = manager.ApplyAllStaged(ctx, objects, DefaultApplyOptions()); err != nil {
252+
t.Fatal(err)
253+
}
254+
255+
opts := DefaultDiffOptions()
256+
opts.IfNotPresentSelector = meta
257+
258+
t.Run("diffs skips", func(t *testing.T) {
259+
entry, _, _, err := manager.Diff(ctx, configMap, opts)
260+
if err != nil {
261+
t.Fatal(err)
262+
}
263+
264+
if entry.Action != SkippedAction && entry.Subject == utils.FmtUnstructured(configMap) {
265+
t.Errorf("Expected %s, got %s", SkippedAction, entry.Action)
266+
}
267+
})
268+
269+
t.Run("diffs applies without meta", func(t *testing.T) {
270+
// mutate in-cluster object
271+
configMapClone := configMap.DeepCopy()
272+
err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone)
273+
if err != nil {
274+
t.Fatal(err)
275+
}
276+
277+
err = unstructured.SetNestedField(configMapClone.Object, "public-second-key", "data", "secondKey")
278+
if err != nil {
279+
t.Fatal(err)
280+
}
281+
configMapClone.SetAnnotations(map[string]string{"fluxcd.io/ignore": ""})
282+
configMapClone.SetManagedFields(nil)
283+
entry, _, _, err := manager.Diff(ctx, configMapClone, opts)
284+
if err != nil {
285+
t.Fatal(err)
286+
}
287+
288+
if entry.Action != ConfiguredAction && entry.Subject == utils.FmtUnstructured(configMapClone) {
289+
t.Errorf("Expected %s, got %s", ConfiguredAction, entry.Action)
290+
}
291+
})
292+
293+
t.Run("diffs skips with meta", func(t *testing.T) {
294+
// mutate in-cluster object
295+
configMapClone := configMap.DeepCopy()
296+
err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone)
297+
if err != nil {
298+
t.Fatal(err)
299+
}
300+
301+
err = unstructured.SetNestedField(configMapClone.Object, "public-second-key", "data", "secondKey")
302+
if err != nil {
303+
t.Fatal(err)
304+
}
305+
configMapClone.SetManagedFields(nil)
306+
307+
entry, _, _, err := manager.Diff(ctx, configMapClone, opts)
308+
if err != nil {
309+
t.Fatal(err)
310+
}
311+
312+
if entry.Action != SkippedAction && entry.Subject == utils.FmtUnstructured(configMapClone) {
313+
t.Errorf("Expected %s, got %s", SkippedAction, entry.Action)
314+
}
315+
})
316+
}
317+
318+
func TestDiff_IfNotPresent_OnObject(t *testing.T) {
319+
timeout := 10 * time.Second
320+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
321+
defer cancel()
322+
323+
id := generateName("ifnotpresentonobject")
324+
objects, err := readManifest("testdata/test1.yaml", id)
325+
if err != nil {
326+
t.Fatal(err)
327+
}
328+
329+
_, configMap := getFirstObject(objects, "ConfigMap", id)
330+
331+
if _, err = manager.ApplyAllStaged(ctx, objects, DefaultApplyOptions()); err != nil {
332+
t.Fatal(err)
333+
}
334+
335+
meta := map[string]string{
336+
"fluxcd.io/ignore": "true",
337+
}
338+
opts := DefaultDiffOptions()
339+
opts.IfNotPresentSelector = meta
340+
341+
t.Run("diffs unchanged", func(t *testing.T) {
342+
entry, _, _, err := manager.Diff(ctx, configMap, opts)
343+
if err != nil {
344+
t.Fatal(err)
345+
}
346+
347+
if entry.Action != UnchangedAction && entry.Subject == utils.FmtUnstructured(configMap) {
348+
t.Errorf("Expected %s, got %s", UnchangedAction, entry.Action)
349+
}
350+
})
351+
352+
t.Run("diffs skips with meta", func(t *testing.T) {
353+
// mutate in-cluster object
354+
configMapClone := configMap.DeepCopy()
355+
err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone)
356+
if err != nil {
357+
t.Fatal(err)
358+
}
359+
360+
configMapClone.SetAnnotations(meta)
361+
err = unstructured.SetNestedField(configMapClone.Object, "public-second-key", "data", "secondKey")
362+
if err != nil {
363+
t.Fatal(err)
364+
}
365+
366+
entry, _, _, err := manager.Diff(ctx, configMapClone, opts)
367+
if err != nil {
368+
t.Fatal(err)
369+
}
370+
371+
if entry.Action != SkippedAction && entry.Subject == utils.FmtUnstructured(configMapClone) {
372+
t.Errorf("Expected %s, got %s", SkippedAction, entry.Action)
373+
}
374+
})
375+
376+
t.Run("diffs configures without meta", func(t *testing.T) {
377+
// mutate in-cluster object
378+
configMapClone := configMap.DeepCopy()
379+
err = manager.client.Get(ctx, client.ObjectKeyFromObject(configMapClone), configMapClone)
380+
if err != nil {
381+
t.Fatal(err)
382+
}
383+
384+
err = unstructured.SetNestedField(configMapClone.Object, "public-second-key", "data", "secondKey")
385+
if err != nil {
386+
t.Fatal(err)
387+
}
388+
configMapClone.SetManagedFields(nil)
389+
390+
entry, _, _, err := manager.Diff(ctx, configMapClone, opts)
391+
if err != nil {
392+
t.Fatal(err)
393+
}
394+
395+
if entry.Action != ConfiguredAction && entry.Subject == utils.FmtUnstructured(configMapClone) {
396+
t.Errorf("Expected %s, got %s", ConfiguredAction, entry.Action)
397+
}
398+
})
399+
}
400+
233401
func TestDiff_Removals(t *testing.T) {
234402
timeout := 10 * time.Second
235403
ctx, cancel := context.WithTimeout(context.Background(), timeout)

0 commit comments

Comments
 (0)