Skip to content

Commit 7efddcf

Browse files
authored
Merge pull request #8139 from sbueringer/pr-cc-dry-run-both
🐛 ClusterClass: run dry-run on original and modified object
2 parents 99b0627 + 4d3b181 commit 7efddcf

File tree

4 files changed

+360
-37
lines changed

4 files changed

+360
-37
lines changed

internal/controllers/topology/cluster/structuredmerge/dryrun.go

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ import (
3333

3434
type dryRunSSAPatchInput struct {
3535
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.
3738
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
4142
// helperOptions contains the helper options for filtering the intent.
4243
helperOptions *HelperOptions
4344
}
@@ -52,16 +53,46 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
5253
}
5354

5455
// 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")
5761
}
5862

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)
6165
if err != nil {
6266
// 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")
6493
}
94+
// Restore managed fields.
95+
dryRunCtx.originalUnstructured.SetManagedFields(originalUnstructuredManagedFieldsBeforeSSA)
6596

6697
// Cleanup the dryRunUnstructured object to remove the added TopologyDryRunAnnotation
6798
// and remove the affected managedFields for `manager=capi-topology` which would
@@ -72,8 +103,8 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
72103
// changes to the object.
73104
// Please note that if other managers made changes to fields that we care about and thus ownership changed,
74105
// 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")
77108
}
78109

79110
// Also run the function for the originalUnstructured to remove the managedField
@@ -84,24 +115,24 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
84115
// Please note that if other managers made changes to fields that we care about and thus ownership changed,
85116
// this would affect our managed fields as well and we would still detect it by diffing our managed fields.
86117
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")
88119
}
89120

90121
// Drop the other fields which are not part of our intent.
91-
filterObject(dryRunCtx.dryRunUnstructured, dryRunHelperOptions)
122+
filterObject(dryRunCtx.modifiedUnstructured, dryRunHelperOptions)
92123
filterObject(dryRunCtx.originalUnstructured, dryRunHelperOptions)
93124

94125
// Compare the output of dry run to the original object.
95126
originalJSON, err := json.Marshal(dryRunCtx.originalUnstructured)
96127
if err != nil {
97128
return false, false, err
98129
}
99-
dryRunJSON, err := json.Marshal(dryRunCtx.dryRunUnstructured)
130+
modifiedJSON, err := json.Marshal(dryRunCtx.modifiedUnstructured)
100131
if err != nil {
101132
return false, false, err
102133
}
103134

104-
rawDiff, err := jsonpatch.CreateMergePatch(originalJSON, dryRunJSON)
135+
rawDiff, err := jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
105136
if err != nil {
106137
return false, false, err
107138
}
@@ -119,7 +150,7 @@ func dryRunSSAPatch(ctx context.Context, dryRunCtx *dryRunSSAPatchInput) (bool,
119150
}
120151

121152
// 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
123154
// also remove the timestamp of the managedField for `manager=capi-topology` because
124155
// it is expected to change due to the additional annotation.
125156
func cleanupManagedFieldsAndAnnotation(obj *unstructured.Unstructured) error {

internal/controllers/topology/cluster/structuredmerge/serversidepathhelper.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func NewServerSidePatchHelper(ctx context.Context, original, modified client.Obj
9696
hasChanges, hasSpecChanges, err = dryRunSSAPatch(ctx, &dryRunSSAPatchInput{
9797
client: c,
9898
originalUnstructured: originalUnstructured,
99-
dryRunUnstructured: modifiedUnstructured.DeepCopy(),
99+
modifiedUnstructured: modifiedUnstructured.DeepCopy(),
100100
helperOptions: helperOptions,
101101
})
102102
if err != nil {

0 commit comments

Comments
 (0)