Skip to content

Commit 03cd99a

Browse files
committed
filter all fields not managedy by topology controller for cluster object
This prevents the topology controller of taking field ownership and fixes potential race conditions when topology controller would patch fields of the object using user input while the user changes them in his own.
1 parent e4c5bcf commit 03cd99a

File tree

3 files changed

+55
-17
lines changed

3 files changed

+55
-17
lines changed

internal/controllers/topology/cluster/reconcile_state.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -439,9 +439,7 @@ func (r *Reconciler) reconcileCluster(ctx context.Context, s *scope.Scope) error
439439
ctx, log := tlog.LoggerFrom(ctx).WithObject(s.Desired.Cluster).Into(ctx)
440440

441441
// Check differences between current and desired state, and eventually patch the current object.
442-
patchHelper, err := r.patchHelperFactory(s.Current.Cluster, s.Desired.Cluster, structuredmerge.IgnorePaths{
443-
{"spec", "controlPlaneEndpoint"}, // this is a well known field that is managed by the Cluster controller, topology should not express opinions on it.
444-
})
442+
patchHelper, err := r.patchHelperFactory(s.Current.Cluster, s.Desired.Cluster)
445443
if err != nil {
446444
return errors.Wrapf(err, "failed to create patch helper for %s", tlog.KObj{Obj: s.Current.Cluster})
447445
}

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,47 @@ limitations under the License.
1717
package structuredmerge
1818

1919
import (
20+
"sigs.k8s.io/controller-runtime/pkg/client"
21+
22+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
2023
"sigs.k8s.io/cluster-api/internal/contract"
2124
)
2225

26+
var (
27+
// defaultAllowedPaths are the allowed paths for all objects except Clusters.
28+
defaultAllowedPaths = []contract.Path{
29+
// apiVersion, kind, name and namespace are required field for a server side apply intent.
30+
{"apiVersion"},
31+
{"kind"},
32+
{"metadata", "name"},
33+
{"metadata", "namespace"},
34+
// the topology controller controls/has an opinion for labels, annotation, ownerReferences and spec only.
35+
{"metadata", "labels"},
36+
{"metadata", "annotations"},
37+
{"metadata", "ownerReferences"},
38+
{"spec"},
39+
}
40+
41+
// allowedPathsCluster are the allowed paths specific for Clusters.
42+
// The cluster object is not created by the topology controller and already contains fields which are
43+
// not supposed for the topology controller to have an opinion on / take (co-)ownership of.
44+
// Because of that the allowedPaths are different to other objects.
45+
// NOTE: This is mostly the same as defaultAllowedPaths but having more restrictions.
46+
allowedPathsCluster = []contract.Path{
47+
// apiVersion, kind, name and namespace are required field for a server side apply intent.
48+
{"apiVersion"},
49+
{"kind"},
50+
{"metadata", "name"},
51+
{"metadata", "namespace"},
52+
// the topology controller controls/has an opinion for the labels ClusterLabelName
53+
// and ClusterTopologyOwnedLabel as well as infrastructureRef and controlPlaneRef in spec.
54+
{"metadata", "labels", clusterv1.ClusterLabelName},
55+
{"metadata", "labels", clusterv1.ClusterTopologyOwnedLabel},
56+
{"spec", "infrastructureRef"},
57+
{"spec", "controlPlaneRef"},
58+
}
59+
)
60+
2361
// HelperOption is some configuration that modifies options for Helper.
2462
type HelperOption interface {
2563
// ApplyToHelper applies this configuration to the given helper options.
@@ -42,6 +80,20 @@ type HelperOptions struct {
4280
ignorePaths []contract.Path
4381
}
4482

83+
// newHelperOptions returns initialized HelperOptions.
84+
func newHelperOptions(target client.Object, opts ...HelperOption) *HelperOptions {
85+
helperOptions := &HelperOptions{
86+
allowedPaths: defaultAllowedPaths,
87+
}
88+
// Overwrite the allowedPaths for Cluster objects to prevent the topology controller
89+
// to take ownership of fields it is not supposed to.
90+
if _, ok := target.(*clusterv1.Cluster); ok {
91+
helperOptions.allowedPaths = allowedPathsCluster
92+
}
93+
helperOptions = helperOptions.ApplyOptions(opts)
94+
return helperOptions
95+
}
96+
4597
// ApplyOptions applies the given patch options on these options,
4698
// and then returns itself (for convenient chaining).
4799
func (o *HelperOptions) ApplyOptions(opts []HelperOption) *HelperOptions {

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

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,8 @@ type serverSidePatchHelper struct {
4242

4343
// NewServerSidePatchHelper returns a new PatchHelper using server side apply.
4444
func NewServerSidePatchHelper(original, modified client.Object, c client.Client, opts ...HelperOption) (PatchHelper, error) {
45-
helperOptions := &HelperOptions{}
46-
helperOptions = helperOptions.ApplyOptions(opts)
47-
helperOptions.allowedPaths = []contract.Path{
48-
// apiVersion, kind, name and namespace are required field for a server side apply intent.
49-
{"apiVersion"},
50-
{"kind"},
51-
{"metadata", "name"},
52-
{"metadata", "namespace"},
53-
// the topology controller controls/has an opinion for labels, annotation, ownerReferences and spec only.
54-
{"metadata", "labels"},
55-
{"metadata", "annotations"},
56-
{"metadata", "ownerReferences"},
57-
{"spec"},
58-
}
45+
// Create helperOptions for filtering the original and modified objects to the desired intent.
46+
helperOptions := newHelperOptions(modified, opts...)
5947

6048
// If required, convert the original and modified objects to unstructured and filter out all the information
6149
// not relevant for the topology controller.

0 commit comments

Comments
 (0)