Skip to content

Commit c5afe7d

Browse files
authored
Merge pull request #5416 from anmazzotti/use_external_bootstrap_config_reference
Support any bootstrap provider with MachinePools
2 parents 3345a22 + dbc2bff commit c5afe7d

File tree

7 files changed

+176
-49
lines changed

7 files changed

+176
-49
lines changed

config/rbac/role.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ rules:
4848
- apiGroups:
4949
- bootstrap.cluster.x-k8s.io
5050
resources:
51-
- kubeadmconfigs
52-
- kubeadmconfigs/status
51+
- '*'
5352
verbs:
5453
- get
5554
- list

exp/controllers/azuremachinepool_controller.go

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,17 @@ package controllers
1919
import (
2020
"context"
2121
"fmt"
22-
"reflect"
2322
"time"
2423

2524
"github.com/pkg/errors"
2625
corev1 "k8s.io/api/core/v1"
2726
apierrors "k8s.io/apimachinery/pkg/api/errors"
28-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2927
"k8s.io/apimachinery/pkg/runtime"
30-
"k8s.io/apimachinery/pkg/runtime/schema"
3128
kerrors "k8s.io/apimachinery/pkg/util/errors"
3229
"k8s.io/client-go/tools/record"
30+
"k8s.io/utils/ptr"
3331
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
34-
kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
32+
"sigs.k8s.io/cluster-api/controllers/external"
3533
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
3634
"sigs.k8s.io/cluster-api/util"
3735
"sigs.k8s.io/cluster-api/util/annotations"
@@ -41,7 +39,6 @@ import (
4139
"sigs.k8s.io/controller-runtime/pkg/client"
4240
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
4341
"sigs.k8s.io/controller-runtime/pkg/handler"
44-
"sigs.k8s.io/controller-runtime/pkg/predicate"
4542
"sigs.k8s.io/controller-runtime/pkg/reconcile"
4643

4744
infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
@@ -63,7 +60,7 @@ type (
6360
Timeouts reconciler.Timeouts
6461
WatchFilterValue string
6562
createAzureMachinePoolService azureMachinePoolServiceCreator
66-
BootstrapConfigGVK schema.GroupVersionKind
63+
externalTracker external.ObjectTracker
6764
CredentialCache azure.CredentialCache
6865
}
6966

@@ -77,21 +74,13 @@ type (
7774
type azureMachinePoolServiceCreator func(machinePoolScope *scope.MachinePoolScope) (*azureMachinePoolService, error)
7875

7976
// NewAzureMachinePoolReconciler returns a new AzureMachinePoolReconciler instance.
80-
func NewAzureMachinePoolReconciler(client client.Client, recorder record.EventRecorder, timeouts reconciler.Timeouts, watchFilterValue, bootstrapConfigGVK string, credCache azure.CredentialCache) *AzureMachinePoolReconciler {
81-
gvk := schema.FromAPIVersionAndKind(kubeadmv1.GroupVersion.String(), reflect.TypeOf((*kubeadmv1.KubeadmConfig)(nil)).Elem().Name())
82-
userGVK, _ := schema.ParseKindArg(bootstrapConfigGVK)
83-
84-
if userGVK != nil {
85-
gvk = *userGVK
86-
}
87-
77+
func NewAzureMachinePoolReconciler(client client.Client, recorder record.EventRecorder, timeouts reconciler.Timeouts, watchFilterValue string, credCache azure.CredentialCache) *AzureMachinePoolReconciler {
8878
ampr := &AzureMachinePoolReconciler{
89-
Client: client,
90-
Recorder: recorder,
91-
Timeouts: timeouts,
92-
WatchFilterValue: watchFilterValue,
93-
BootstrapConfigGVK: gvk,
94-
CredentialCache: credCache,
79+
Client: client,
80+
Recorder: recorder,
81+
Timeouts: timeouts,
82+
WatchFilterValue: watchFilterValue,
83+
CredentialCache: credCache,
9584
}
9685

9786
ampr.createAzureMachinePoolService = newAzureMachinePoolService
@@ -127,9 +116,7 @@ func (ampr *AzureMachinePoolReconciler) SetupWithManager(ctx context.Context, mg
127116
return errors.Wrap(err, "failed to create mapper for Cluster to AzureMachines")
128117
}
129118

130-
config := &metav1.PartialObjectMetadata{}
131-
config.SetGroupVersionKind(ampr.BootstrapConfigGVK)
132-
return ctrl.NewControllerManagedBy(mgr).
119+
controller, err := ctrl.NewControllerManagedBy(mgr).
133120
WithOptions(options.Options).
134121
For(&infrav1exp.AzureMachinePool{}).
135122
WithEventFilter(predicates.ResourceHasFilterLabel(mgr.GetScheme(), log, ampr.WatchFilterValue)).
@@ -148,12 +135,6 @@ func (ampr *AzureMachinePoolReconciler) SetupWithManager(ctx context.Context, mg
148135
&infrav1.AzureManagedControlPlane{},
149136
handler.EnqueueRequestsFromMapFunc(azureManagedControlPlaneMapper),
150137
).
151-
// watch for changes in KubeadmConfig (or any BootstrapConfig) to sync bootstrap token
152-
Watches(
153-
config,
154-
handler.EnqueueRequestsFromMapFunc(BootstrapConfigToInfrastructureMapFunc(ctx, ampr.Client, log)),
155-
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
156-
).
157138
Watches(
158139
&infrav1exp.AzureMachinePoolMachine{},
159140
handler.EnqueueRequestsFromMapFunc(AzureMachinePoolMachineMapper(mgr.GetScheme(), log)),
@@ -170,13 +151,25 @@ func (ampr *AzureMachinePoolReconciler) SetupWithManager(ctx context.Context, mg
170151
infracontroller.ClusterPauseChangeAndInfrastructureReady(mgr.GetScheme(), log),
171152
predicates.ResourceHasFilterLabel(mgr.GetScheme(), log, ampr.WatchFilterValue),
172153
),
173-
).
174-
Complete(r)
154+
).Build(r)
155+
if err != nil {
156+
return fmt.Errorf("creating new controller manager: %w", err)
157+
}
158+
159+
predicateLog := ptr.To(ctrl.LoggerFrom(ctx).WithValues("controller", "azuremachinepool"))
160+
ampr.externalTracker = external.ObjectTracker{
161+
Controller: controller,
162+
Cache: mgr.GetCache(),
163+
Scheme: mgr.GetScheme(),
164+
PredicateLogger: predicateLog,
165+
}
166+
167+
return nil
175168
}
176169

177170
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepools,verbs=get;list;watch;create;update;patch;delete
178171
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepools/status,verbs=get;update;patch
179-
// +kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=kubeadmconfigs;kubeadmconfigs/status,verbs=get;list;watch
172+
// +kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=*,verbs=get;list;watch
180173
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepoolmachines,verbs=get;list;watch;create;update;patch;delete
181174
// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=azuremachinepoolmachines/status,verbs=get
182175
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools;machinepools/status,verbs=get;list;watch;update;patch
@@ -295,6 +288,27 @@ func (ampr *AzureMachinePoolReconciler) reconcileNormal(ctx context.Context, mac
295288
return reconcile.Result{}, nil
296289
}
297290

291+
// Add a Watch to the referenced Bootstrap Config
292+
if machinePoolScope.MachinePool.Spec.Template.Spec.Bootstrap.ConfigRef != nil {
293+
ref := machinePoolScope.MachinePool.Spec.Template.Spec.Bootstrap.ConfigRef
294+
obj, err := external.Get(ctx, ampr.Client, ref)
295+
if err != nil {
296+
if apierrors.IsNotFound(errors.Cause(err)) {
297+
return reconcile.Result{}, errors.Wrapf(err, "could not find %v %q for MachinePool %q in namespace %q, requeuing while searching for bootstrap ConfigRef",
298+
ref.GroupVersionKind(), ref.Name, machinePoolScope.MachinePool.Name, ref.Namespace)
299+
}
300+
return reconcile.Result{}, err
301+
}
302+
303+
// Ensure we add a watch to the external object, if there isn't one already.
304+
if err := ampr.externalTracker.Watch(log, obj,
305+
handler.EnqueueRequestsFromMapFunc(BootstrapConfigToInfrastructureMapFunc(ampr.Client, *ampr.externalTracker.PredicateLogger)),
306+
predicates.ResourceIsChanged(ampr.Client.Scheme(), *ampr.externalTracker.PredicateLogger)); err != nil {
307+
return reconcile.Result{}, errors.Wrapf(err, "could not add a watcher to the object %v %q for MachinePool %q in namespace %q, requeuing",
308+
ref.GroupVersionKind(), ref.Name, machinePoolScope.MachinePool.Name, ref.Namespace)
309+
}
310+
}
311+
298312
// Make sure bootstrap data is available and populated.
299313
if machinePoolScope.MachinePool.Spec.Template.Spec.Bootstrap.DataSecretName == nil {
300314
log.Info("Bootstrap data secret reference is not yet available")

exp/controllers/azuremachinepool_controller_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ var _ = Describe("AzureMachinePoolReconciler", func() {
4646
Context("Reconcile an AzureMachinePool", func() {
4747
It("should not error with minimal set up", func() {
4848
reconciler := NewAzureMachinePoolReconciler(testEnv, testEnv.GetEventRecorderFor("azuremachinepool-reconciler"),
49-
reconciler.Timeouts{}, "", "", testEnv.CredentialCache)
49+
reconciler.Timeouts{}, "", testEnv.CredentialCache)
5050
By("Calling reconcile")
5151
instance := &infrav1exp.AzureMachinePool{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}
5252
result, err := reconciler.Reconcile(context.Background(), ctrl.Request{
@@ -81,7 +81,7 @@ func TestAzureMachinePoolReconcilePaused(t *testing.T) {
8181

8282
recorder := record.NewFakeRecorder(1)
8383

84-
reconciler := NewAzureMachinePoolReconciler(c, recorder, reconciler.Timeouts{}, "", "", azure.NewCredentialCache())
84+
reconciler := NewAzureMachinePoolReconciler(c, recorder, reconciler.Timeouts{}, "", azure.NewCredentialCache())
8585
name := test.RandomName("paused", 10)
8686
namespace := "default"
8787

exp/controllers/helpers.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -373,17 +373,29 @@ func MachinePoolMachineHasStateOrVersionChange(logger logr.Logger) predicate.Fun
373373
}
374374

375375
// BootstrapConfigToInfrastructureMapFunc returns a handler.ToRequestsFunc that watches for <Bootstrap>Config events and returns.
376-
func BootstrapConfigToInfrastructureMapFunc(_ context.Context, c client.Client, log logr.Logger) handler.MapFunc {
376+
func BootstrapConfigToInfrastructureMapFunc(c client.Client, log logr.Logger) handler.MapFunc {
377377
return func(ctx context.Context, o client.Object) []reconcile.Request {
378378
ctx, cancel := context.WithTimeout(ctx, reconciler.DefaultMappingTimeout)
379379
defer cancel()
380380

381+
// Lookup the first MachinePool owner of this BootstrapConfig, if any
382+
mpName := ""
383+
for _, owner := range o.GetOwnerReferences() {
384+
if owner.Kind == "MachinePool" {
385+
mpName = owner.Name
386+
break
387+
}
388+
}
389+
if mpName == "" {
390+
log.V(4).Info("BootstrapConfig has no MachinePool owner")
391+
return []reconcile.Request{}
392+
}
393+
394+
// Fetch the MachinePool to validate the Bootstrap.ConfigRef
381395
mpKey := client.ObjectKey{
382396
Namespace: o.GetNamespace(),
383-
Name: o.GetName(),
397+
Name: mpName,
384398
}
385-
386-
// fetch MachinePool to get reference
387399
mp := &expv1.MachinePool{}
388400
if err := c.Get(ctx, mpKey, mp); err != nil {
389401
if !apierrors.IsNotFound(err) {
@@ -397,7 +409,7 @@ func BootstrapConfigToInfrastructureMapFunc(_ context.Context, c client.Client,
397409
log.V(4).Info("fetched MachinePool has no Bootstrap.ConfigRef")
398410
return []reconcile.Request{}
399411
}
400-
sameKind := ref.Kind != o.GetObjectKind().GroupVersionKind().Kind
412+
sameKind := ref.Kind == o.GetObjectKind().GroupVersionKind().Kind
401413
sameName := ref.Name == o.GetName()
402414
sameNamespace := ref.Namespace == o.GetNamespace()
403415
if !sameKind || !sameName || !sameNamespace {
@@ -411,12 +423,12 @@ func BootstrapConfigToInfrastructureMapFunc(_ context.Context, c client.Client,
411423
return []reconcile.Request{}
412424
}
413425

414-
key := client.ObjectKeyFromObject(o)
415-
log.V(4).Info("adding KubeadmConfig to watch", "key", key)
426+
// Enqueue a request for an AzureMachinePool with same name of MachinePool
427+
log.V(4).Info("Enqueueing AzureMachinePool from Bootstrap Config", "key", client.ObjectKeyFromObject(o), "kind", ref.Kind)
416428

417429
return []reconcile.Request{
418430
{
419-
NamespacedName: key,
431+
NamespacedName: mpKey,
420432
},
421433
}
422434
}

exp/controllers/helpers_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ import (
2121
"testing"
2222

2323
"github.com/go-logr/logr"
24+
. "github.com/onsi/ginkgo/v2"
2425
. "github.com/onsi/gomega"
2526
"go.uber.org/mock/gomock"
27+
corev1 "k8s.io/api/core/v1"
2628
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2729
"k8s.io/apimachinery/pkg/runtime"
2830
"k8s.io/apimachinery/pkg/types"
31+
"k8s.io/utils/ptr"
2932
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
33+
kubeadmv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
34+
expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
35+
ctrl "sigs.k8s.io/controller-runtime"
3036
"sigs.k8s.io/controller-runtime/pkg/client"
3137
"sigs.k8s.io/controller-runtime/pkg/client/fake"
3238
"sigs.k8s.io/controller-runtime/pkg/reconcile"
@@ -41,6 +47,99 @@ var (
4147
clusterName = "my-cluster"
4248
)
4349

50+
var _ = Describe("BootstrapConfigToInfrastructureMapFunc", func() {
51+
It("should map bootstrap config to machine pool", func() {
52+
ctx := context.Background()
53+
scheme := runtime.NewScheme()
54+
Expect(kubeadmv1.AddToScheme(scheme)).Should(Succeed())
55+
Expect(expv1.AddToScheme(scheme)).Should(Succeed())
56+
Expect(clusterv1.AddToScheme(scheme)).Should(Succeed())
57+
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
58+
mapFn := BootstrapConfigToInfrastructureMapFunc(fakeClient, ctrl.Log)
59+
bootstrapConfig := kubeadmv1.KubeadmConfig{
60+
ObjectMeta: metav1.ObjectMeta{
61+
Name: "bootstrap-test",
62+
Namespace: "default",
63+
},
64+
}
65+
Expect(fakeClient.Create(ctx, &bootstrapConfig)).Should(Succeed())
66+
67+
By("doing nothing if the config has no owners")
68+
Expect(mapFn(ctx, &bootstrapConfig)).Should(Equal([]ctrl.Request{}))
69+
70+
By("doing nothing if the config has no MachinePool owner")
71+
bootstrapConfig.OwnerReferences = []metav1.OwnerReference{
72+
{
73+
APIVersion: "cluster.x-k8s.io/v1beta1",
74+
Name: "machine-pool-test",
75+
Kind: "NotAMachinePool",
76+
UID: types.UID("foobar"),
77+
Controller: ptr.To(true),
78+
},
79+
}
80+
Expect(fakeClient.Update(ctx, &bootstrapConfig)).Should(Succeed())
81+
Expect(mapFn(ctx, &bootstrapConfig)).Should(Equal([]ctrl.Request{}))
82+
83+
By("doing nothing if the MachinePool is not found")
84+
bootstrapConfig.OwnerReferences = []metav1.OwnerReference{
85+
{
86+
APIVersion: "cluster.x-k8s.io/v1beta1",
87+
Name: "machine-pool-test",
88+
Kind: "MachinePool",
89+
UID: types.UID("foobar"),
90+
Controller: ptr.To(true),
91+
},
92+
}
93+
Expect(fakeClient.Update(ctx, &bootstrapConfig)).Should(Succeed())
94+
Expect(mapFn(ctx, &bootstrapConfig)).Should(Equal([]ctrl.Request{}))
95+
96+
By("doing nothing if the MachinePool has no BootstrapConfigRef")
97+
machinePool := expv1.MachinePool{
98+
ObjectMeta: metav1.ObjectMeta{
99+
Name: "machine-pool-test",
100+
Namespace: "default",
101+
},
102+
Spec: expv1.MachinePoolSpec{
103+
ClusterName: "test-cluster",
104+
Template: clusterv1.MachineTemplateSpec{
105+
Spec: clusterv1.MachineSpec{
106+
ClusterName: "test-cluster",
107+
},
108+
},
109+
},
110+
}
111+
Expect(fakeClient.Create(ctx, &machinePool)).Should(Succeed())
112+
Expect(mapFn(ctx, &bootstrapConfig)).Should(Equal([]ctrl.Request{}))
113+
114+
By("doing nothing if the MachinePool has a different BootstrapConfigRef Kind")
115+
machinePool.Spec.Template.Spec.Bootstrap = clusterv1.Bootstrap{
116+
ConfigRef: &corev1.ObjectReference{
117+
APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
118+
Kind: "OtherBootstrapConfig",
119+
Name: bootstrapConfig.Name,
120+
Namespace: bootstrapConfig.Namespace,
121+
},
122+
}
123+
Expect(fakeClient.Update(ctx, &machinePool)).Should(Succeed())
124+
Expect(mapFn(ctx, &bootstrapConfig)).Should(Equal([]ctrl.Request{}))
125+
126+
By("doing nothing if the MachinePool has a different BootstrapConfigRef Name")
127+
machinePool.Spec.Template.Spec.Bootstrap.ConfigRef.Kind = bootstrapConfig.GetObjectKind().GroupVersionKind().Kind
128+
machinePool.Spec.Template.Spec.Bootstrap.ConfigRef.Name = "other-bootstrap-config"
129+
Expect(fakeClient.Update(ctx, &machinePool)).Should(Succeed())
130+
Expect(mapFn(ctx, &bootstrapConfig)).Should(Equal([]ctrl.Request{}))
131+
132+
By("enqueueing AzureMachinePool")
133+
machinePool.Spec.Template.Spec.Bootstrap.ConfigRef.Name = bootstrapConfig.Name
134+
Expect(fakeClient.Update(ctx, &machinePool)).Should(Succeed())
135+
Expect(mapFn(ctx, &bootstrapConfig)).Should(Equal([]ctrl.Request{
136+
{
137+
NamespacedName: client.ObjectKey{Namespace: machinePool.Namespace, Name: machinePool.Name},
138+
},
139+
}))
140+
})
141+
})
142+
44143
func TestAzureClusterToAzureMachinePoolsMapper(t *testing.T) {
45144
g := NewWithT(t)
46145
scheme := newScheme(g)

exp/controllers/suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ var _ = BeforeSuite(func() {
5555
ctx = log.IntoContext(ctx, logr.New(testEnv.Log))
5656

5757
Expect(NewAzureMachinePoolReconciler(testEnv, testEnv.GetEventRecorderFor("azuremachinepool-reconciler"),
58-
reconciler.Timeouts{}, "", "", testEnv.CredentialCache).SetupWithManager(ctx, testEnv.Manager, controllers.Options{Options: controller.Options{MaxConcurrentReconciles: 1}})).To(Succeed())
58+
reconciler.Timeouts{}, "", testEnv.CredentialCache).SetupWithManager(ctx, testEnv.Manager, controllers.Options{Options: controller.Options{MaxConcurrentReconciles: 1}})).To(Succeed())
5959

6060
Expect(NewAzureMachinePoolMachineController(testEnv, testEnv.GetEventRecorderFor("azuremachinepoolmachine-reconciler"),
6161
reconciler.Timeouts{}, "", testEnv.CredentialCache).SetupWithManager(ctx, testEnv.Manager, controllers.Options{Options: controller.Options{MaxConcurrentReconciles: 1}})).To(Succeed())

0 commit comments

Comments
 (0)