Skip to content

Commit 89da95f

Browse files
authored
Merge pull request kubernetes-sigs#6742 from chrischdi/pr-ssa-prevent-orphaned-creation
🐛 Carry over metadata.uid at ServerSidePatchHelper
2 parents a3f748a + 524d990 commit 89da95f

File tree

4 files changed

+56
-1
lines changed

4 files changed

+56
-1
lines changed

internal/controllers/topology/cluster/reconcile_state_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1660,7 +1660,7 @@ func TestReconcileMachineDeployments(t *testing.T) {
16601660
md8Update := newFakeMachineDeploymentTopologyState("md-8-update", infrastructureMachineTemplate8Update, bootstrapTemplate8Update, nil)
16611661
infrastructureMachineTemplate8UpdateWithChanges := infrastructureMachineTemplate8Update.DeepCopy()
16621662
g.Expect(unstructured.SetNestedField(infrastructureMachineTemplate8UpdateWithChanges.Object, "foo", "spec", "template", "spec", "foo")).To(Succeed())
1663-
bootstrapTemplate8UpdateWithChanges := bootstrapTemplate3.DeepCopy()
1663+
bootstrapTemplate8UpdateWithChanges := bootstrapTemplate8Update.DeepCopy()
16641664
g.Expect(unstructured.SetNestedField(bootstrapTemplate8UpdateWithChanges.Object, "foo", "spec", "template", "spec", "foo")).To(Succeed())
16651665
md8UpdateWithRotatedTemplates := newFakeMachineDeploymentTopologyState("md-8-update", infrastructureMachineTemplate8UpdateWithChanges, bootstrapTemplate8UpdateWithChanges, nil)
16661666

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ var (
3131
{"kind"},
3232
{"metadata", "name"},
3333
{"metadata", "namespace"},
34+
// uid is optional for a server side apply intent but sets the expectation of an object getting created or a specific one updated.
35+
{"metadata", "uid"},
3436
// the topology controller controls/has an opinion for labels, annotation, ownerReferences and spec only.
3537
{"metadata", "labels"},
3638
{"metadata", "annotations"},
@@ -49,6 +51,8 @@ var (
4951
{"kind"},
5052
{"metadata", "name"},
5153
{"metadata", "namespace"},
54+
// uid is optional for a server side apply intent but sets the expectation of an object getting created or a specific one updated.
55+
{"metadata", "uid"},
5256
// the topology controller controls/has an opinion for the labels ClusterLabelName
5357
// and ClusterTopologyOwnedLabel as well as infrastructureRef and controlPlaneRef in spec.
5458
{"metadata", "labels", clusterv1.ClusterLabelName},

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,14 @@ func NewServerSidePatchHelper(original, modified client.Object, c client.Client,
8181
}
8282
filterObject(modifiedUnstructured, helperOptions)
8383

84+
// Carry over uid to match the intent to:
85+
// * create (uid==""): Setting no uid ensures an object gets created, not updated.
86+
// * update (uid!=""): Setting an uid ensures an object which has the same uid gets updated.
87+
modifiedUnstructured.SetUID("")
88+
if originalUnstructured != nil {
89+
modifiedUnstructured.SetUID(originalUnstructured.GetUID())
90+
}
91+
8492
// Determine if the intent defined in the modified object is going to trigger
8593
// an actual change when running server side apply, and if this change might impact the object spec or not.
8694
var hasChanges, hasSpecChanges bool

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,49 @@ func TestServerSideApply(t *testing.T) {
386386
g.Expect(p0.HasChanges()).To(BeFalse())
387387
g.Expect(p0.HasSpecChanges()).To(BeFalse())
388388
})
389+
t.Run("Error on object which has another uid due to immutability", func(t *testing.T) {
390+
g := NewWithT(t)
391+
392+
// Get the current object (assumes tests to be run in sequence).
393+
original := obj.DeepCopy()
394+
g.Expect(env.GetAPIReader().Get(ctx, client.ObjectKeyFromObject(original), original)).To(Succeed())
395+
396+
// Create a patch helper for a modified object with some changes to what previously applied by th topology manager.
397+
modified := obj.DeepCopy()
398+
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "controlPlaneEndpoint", "host")).To(Succeed())
399+
g.Expect(unstructured.SetNestedField(modified.Object, "changed-by-topology-controller", "spec", "bar")).To(Succeed())
400+
401+
// Set an other uid to original
402+
original.SetUID("a-wrong-one")
403+
modified.SetUID("")
404+
405+
// Create a patch helper which should fail because original's real UID changed.
406+
p0, err := NewServerSidePatchHelper(original, modified, env.GetClient())
407+
g.Expect(err).ToNot(HaveOccurred())
408+
g.Expect(p0.HasChanges()).To(BeTrue())
409+
g.Expect(p0.HasSpecChanges()).To(BeTrue())
410+
g.Expect(p0.Patch(ctx)).ToNot(Succeed())
411+
})
412+
t.Run("Error on object which does not exist (anymore) but was expected to get updated", func(t *testing.T) {
413+
original := builder.TestInfrastructureCluster(ns.Name, "obj3").WithSpecFields(map[string]interface{}{
414+
"spec.controlPlaneEndpoint.host": "1.2.3.4",
415+
"spec.controlPlaneEndpoint.port": int64(1234),
416+
"spec.foo": "", // this field is then explicitly ignored by the patch helper
417+
}).Build()
418+
419+
modified := original.DeepCopy()
420+
g.Expect(unstructured.SetNestedField(modified.Object, "changed", "spec", "controlPlaneEndpoint", "host")).To(Succeed())
421+
422+
// Set a not existing uid to the not existing original object
423+
original.SetUID("does-not-exist")
424+
425+
// Create a patch helper which should fail because original does not exist.
426+
p0, err := NewServerSidePatchHelper(original, modified, env.GetClient())
427+
g.Expect(err).ToNot(HaveOccurred())
428+
g.Expect(p0.HasChanges()).To(BeTrue())
429+
g.Expect(p0.HasSpecChanges()).To(BeTrue())
430+
g.Expect(p0.Patch(ctx)).ToNot(Succeed())
431+
})
389432
}
390433

391434
func TestServerSideApply_CleanupLegacyManagedFields(t *testing.T) {

0 commit comments

Comments
 (0)