@@ -33,11 +33,12 @@ import (
33
33
34
34
type dryRunSSAPatchInput struct {
35
35
client client.Client
36
- // originalUnstructured contains the current state of the object
36
+ // originalUnstructured contains the current state of the object.
37
+ // Note: We will run SSA dry-run on originalUnstructured and modifiedUnstructured and then compare them.
37
38
originalUnstructured * unstructured.Unstructured
38
- // dryRunUnstructured contains the intended changes to the object and will be used to
39
- // compare to the originalUnstructured object afterwards .
40
- dryRunUnstructured * unstructured.Unstructured
39
+ // modifiedUnstructured contains the intended changes to the object.
40
+ // Note: We will run SSA dry-run on originalUnstructured and modifiedUnstructured and then compare them .
41
+ modifiedUnstructured * unstructured.Unstructured
41
42
// helperOptions contains the helper options for filtering the intent.
42
43
helperOptions * HelperOptions
43
44
}
@@ -52,16 +53,46 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
52
53
}
53
54
54
55
// Add TopologyDryRunAnnotation to notify validation webhooks to skip immutability checks.
55
- if err := unstructured .SetNestedField (dryRunCtx .dryRunUnstructured .Object , "" , "metadata" , "annotations" , clusterv1 .TopologyDryRunAnnotation ); err != nil {
56
- return false , false , errors .Wrap (err , "failed to add topology dry-run annotation" )
56
+ if err := unstructured .SetNestedField (dryRunCtx .originalUnstructured .Object , "" , "metadata" , "annotations" , clusterv1 .TopologyDryRunAnnotation ); err != nil {
57
+ return false , false , errors .Wrap (err , "failed to add topology dry-run annotation to original object" )
58
+ }
59
+ if err := unstructured .SetNestedField (dryRunCtx .modifiedUnstructured .Object , "" , "metadata" , "annotations" , clusterv1 .TopologyDryRunAnnotation ); err != nil {
60
+ return false , false , errors .Wrap (err , "failed to add topology dry-run annotation to modified object" )
57
61
}
58
62
59
- // Do a server-side apply dry-run request to get the updated object.
60
- err := dryRunCtx .client .Patch (ctx , dryRunCtx .dryRunUnstructured , client .Apply , client .DryRunAll , client .FieldOwner (TopologyManagerName ), client .ForceOwnership )
63
+ // Do a server-side apply dry-run with modifiedUnstructured to get the updated object.
64
+ err := dryRunCtx .client .Patch (ctx , dryRunCtx .modifiedUnstructured , client .Apply , client .DryRunAll , client .FieldOwner (TopologyManagerName ), client .ForceOwnership )
61
65
if err != nil {
62
66
// This catches errors like metadata.uid changes.
63
- return false , false , errors .Wrap (err , "failed to request dry-run server side apply" )
67
+ return false , false , errors .Wrap (err , "server side apply dry-run failed for modified object" )
68
+ }
69
+
70
+ // Do a server-side apply dry-run with originalUnstructured to ensure the latest defaulting is applied.
71
+ // Note: After a Cluster API upgrade there is no guarantee that defaulting has been run on existing objects.
72
+ // We have to ensure defaulting has been applied before comparing original with modified, because otherwise
73
+ // differences in defaulting would trigger rollouts.
74
+ // Note: We cannot use the managed fields of originalUnstructured after SSA dryrun, because applying
75
+ // the whole originalUnstructured will give capi-topology ownership of all fields. Thus, we back up the
76
+ // managed fields and restore them after the dry run.
77
+ // It's fine to compare the managed fields of modifiedUnstructured after dry-run with originalUnstructured
78
+ // before dry-run as we want to know if applying modifiedUnstructured would change managed fields on original.
79
+
80
+ // Filter object to drop fields which are not part of our intent.
81
+ // Note: It's especially important to also drop metadata.resourceVersion, otherwise we could get the following
82
+ // error: "the object has been modified; please apply your changes to the latest version and try again"
83
+ filterObject (dryRunCtx .originalUnstructured , dryRunHelperOptions )
84
+ // Backup managed fields.
85
+ originalUnstructuredManagedFieldsBeforeSSA := dryRunCtx .originalUnstructured .GetManagedFields ()
86
+ // Set managed fields to nil.
87
+ // Note: Otherwise we would get the following error:
88
+ // "failed to request dry-run server side apply: metadata.managedFields must be nil"
89
+ dryRunCtx .originalUnstructured .SetManagedFields (nil )
90
+ err = dryRunCtx .client .Patch (ctx , dryRunCtx .originalUnstructured , client .Apply , client .DryRunAll , client .FieldOwner (TopologyManagerName ), client .ForceOwnership )
91
+ if err != nil {
92
+ return false , false , errors .Wrap (err , "server side apply dry-run failed for original object" )
64
93
}
94
+ // Restore managed fields.
95
+ dryRunCtx .originalUnstructured .SetManagedFields (originalUnstructuredManagedFieldsBeforeSSA )
65
96
66
97
// Cleanup the dryRunUnstructured object to remove the added TopologyDryRunAnnotation
67
98
// and remove the affected managedFields for `manager=capi-topology` which would
@@ -72,8 +103,8 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
72
103
// changes to the object.
73
104
// Please note that if other managers made changes to fields that we care about and thus ownership changed,
74
105
// this would affect our managed fields as well and we would still detect it by diffing our managed fields.
75
- if err := cleanupManagedFieldsAndAnnotation (dryRunCtx .dryRunUnstructured ); err != nil {
76
- return false , false , errors .Wrap (err , "failed to filter topology dry-run annotation on dryRunUnstructured " )
106
+ if err := cleanupManagedFieldsAndAnnotation (dryRunCtx .modifiedUnstructured ); err != nil {
107
+ return false , false , errors .Wrap (err , "failed to filter topology dry-run annotation on modified object " )
77
108
}
78
109
79
110
// Also run the function for the originalUnstructured to remove the managedField
@@ -84,24 +115,24 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
84
115
// Please note that if other managers made changes to fields that we care about and thus ownership changed,
85
116
// this would affect our managed fields as well and we would still detect it by diffing our managed fields.
86
117
if err := cleanupManagedFieldsAndAnnotation (dryRunCtx .originalUnstructured ); err != nil {
87
- return false , false , errors .Wrap (err , "failed to filter topology dry-run annotation on originalUnstructured " )
118
+ return false , false , errors .Wrap (err , "failed to filter topology dry-run annotation on original object " )
88
119
}
89
120
90
121
// Drop the other fields which are not part of our intent.
91
- filterObject (dryRunCtx .dryRunUnstructured , dryRunHelperOptions )
122
+ filterObject (dryRunCtx .modifiedUnstructured , dryRunHelperOptions )
92
123
filterObject (dryRunCtx .originalUnstructured , dryRunHelperOptions )
93
124
94
125
// Compare the output of dry run to the original object.
95
126
originalJSON , err := json .Marshal (dryRunCtx .originalUnstructured )
96
127
if err != nil {
97
128
return false , false , err
98
129
}
99
- dryRunJSON , err := json .Marshal (dryRunCtx .dryRunUnstructured )
130
+ modifiedJSON , err := json .Marshal (dryRunCtx .modifiedUnstructured )
100
131
if err != nil {
101
132
return false , false , err
102
133
}
103
134
104
- rawDiff , err := jsonpatch .CreateMergePatch (originalJSON , dryRunJSON )
135
+ rawDiff , err := jsonpatch .CreateMergePatch (originalJSON , modifiedJSON )
105
136
if err != nil {
106
137
return false , false , err
107
138
}
@@ -119,7 +150,7 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
119
150
}
120
151
121
152
// cleanupManagedFieldsAndAnnotation adjusts the obj to remove the topology.cluster.x-k8s.io/dry-run
122
- // annotation as well as the field ownership reference in managedFields. It does
153
+ // and cluster.x-k8s.io/conversion-data annotations as well as the field ownership reference in managedFields. It does
123
154
// also remove the timestamp of the managedField for `manager=capi-topology` because
124
155
// it is expected to change due to the additional annotation.
125
156
func cleanupManagedFieldsAndAnnotation (obj * unstructured.Unstructured ) error {
0 commit comments