Skip to content

Commit 231a5ca

Browse files
rsmittytalos-bot
authored andcommitted
feat: support machinepools in bootstrapper
This PR adds support for the machinepool feature gate. When enabled, we can now catch new machinepools, generate userdata, and populate the secret as expected. This is necessary for using Autoscaling Groups in AWS. Signed-off-by: Spencer Smith <[email protected]>
1 parent a0b46d2 commit 231a5ca

File tree

8 files changed

+302
-128
lines changed

8 files changed

+302
-128
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# syntax = docker/dockerfile-upstream:1.1.4-experimental
22

3-
FROM golang:1.13 AS build
3+
FROM golang:1.15 AS build
44
ENV GO111MODULE on
55
ENV GOPROXY https://proxy.golang.org
66
ENV CGO_ENABLED 0

config/rbac/role.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,12 @@ rules:
4949
- get
5050
- list
5151
- watch
52+
- apiGroups:
53+
- exp.cluster.x-k8s.io
54+
resources:
55+
- machinepools
56+
- machinepools/status
57+
verbs:
58+
- get
59+
- list
60+
- watch

controllers/secrets.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,16 @@ func (r *TalosConfigReconciler) writeK8sCASecret(ctx context.Context, scope *Tal
121121
// writeBootstrapData creates a new secret with the data passed in as input
122122
func (r *TalosConfigReconciler) writeBootstrapData(ctx context.Context, scope *TalosConfigScope, data []byte) error {
123123
// Create ca secret only if it doesn't already exist
124-
_, err := r.fetchSecret(ctx, scope.Config, scope.Machine.Name+"-bootstrap-data")
124+
ownerName := scope.ConfigOwner.GetName()
125+
126+
r.Log.Info("handling bootstrap data for ", "owner", ownerName)
127+
128+
_, err := r.fetchSecret(ctx, scope.Config, ownerName+"-bootstrap-data")
125129
if k8serrors.IsNotFound(err) {
126130
certSecret := &corev1.Secret{
127131
ObjectMeta: metav1.ObjectMeta{
128132
Namespace: scope.Config.Namespace,
129-
Name: scope.Machine.Name + "-bootstrap-data",
133+
Name: ownerName + "-bootstrap-data",
130134
Labels: map[string]string{
131135
clusterv1.ClusterLabelName: scope.Cluster.Name,
132136
},

controllers/suite_test.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ import (
2828
"sigs.k8s.io/controller-runtime/pkg/client"
2929
"sigs.k8s.io/controller-runtime/pkg/envtest"
3030
"sigs.k8s.io/controller-runtime/pkg/envtest/printer"
31-
logf "sigs.k8s.io/controller-runtime/pkg/log"
32-
"sigs.k8s.io/controller-runtime/pkg/log/zap"
3331
// +kubebuilder:scaffold:imports
3432
)
3533

@@ -49,8 +47,6 @@ func TestAPIs(t *testing.T) {
4947
}
5048

5149
var _ = BeforeSuite(func(done Done) {
52-
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
53-
5450
By("bootstrapping test environment")
5551
testEnv = &envtest.Environment{
5652
CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")},

controllers/talosconfig_controller.go

Lines changed: 176 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,18 @@ import (
3838
"k8s.io/apimachinery/pkg/runtime"
3939
"k8s.io/utils/pointer"
4040
capiv1 "sigs.k8s.io/cluster-api/api/v1alpha3"
41+
bsutil "sigs.k8s.io/cluster-api/bootstrap/util"
42+
expv1 "sigs.k8s.io/cluster-api/exp/api/v1alpha3"
43+
"sigs.k8s.io/cluster-api/feature"
4144
"sigs.k8s.io/cluster-api/util"
4245
"sigs.k8s.io/cluster-api/util/patch"
46+
"sigs.k8s.io/cluster-api/util/predicates"
4347
ctrl "sigs.k8s.io/controller-runtime"
4448
"sigs.k8s.io/controller-runtime/pkg/client"
4549
"sigs.k8s.io/controller-runtime/pkg/controller"
4650
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
51+
"sigs.k8s.io/controller-runtime/pkg/handler"
52+
"sigs.k8s.io/controller-runtime/pkg/source"
4753
)
4854

4955
const (
@@ -58,9 +64,9 @@ type TalosConfigReconciler struct {
5864
}
5965

6066
type TalosConfigScope struct {
61-
Config *bootstrapv1alpha3.TalosConfig
62-
Machine *capiv1.Machine
63-
Cluster *capiv1.Cluster
67+
Config *bootstrapv1alpha3.TalosConfig
68+
ConfigOwner *bsutil.ConfigOwner
69+
Cluster *capiv1.Cluster
6470
}
6571

6672
type TalosConfigBundle struct {
@@ -80,16 +86,51 @@ type talosConfigContext struct {
8086
Key string
8187
}
8288

83-
func (r *TalosConfigReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error {
84-
return ctrl.NewControllerManagedBy(mgr).
85-
WithOptions(options).
89+
func (r *TalosConfigReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
90+
r.Scheme = mgr.GetScheme()
91+
92+
b := ctrl.NewControllerManagedBy(mgr).
8693
For(&bootstrapv1alpha3.TalosConfig{}).
87-
Complete(r)
94+
WithOptions(options).
95+
Watches(
96+
&source.Kind{Type: &capiv1.Machine{}},
97+
&handler.EnqueueRequestsFromMapFunc{
98+
ToRequests: handler.ToRequestsFunc(r.MachineToBootstrapMapFunc),
99+
},
100+
)
101+
102+
if feature.Gates.Enabled(feature.MachinePool) {
103+
b = b.Watches(
104+
&source.Kind{Type: &expv1.MachinePool{}},
105+
&handler.EnqueueRequestsFromMapFunc{
106+
ToRequests: handler.ToRequestsFunc(r.MachinePoolToBootstrapMapFunc),
107+
},
108+
)
109+
}
110+
111+
c, err := b.Build(r)
112+
if err != nil {
113+
return err
114+
}
115+
116+
err = c.Watch(
117+
&source.Kind{Type: &capiv1.Cluster{}},
118+
&handler.EnqueueRequestsFromMapFunc{
119+
ToRequests: handler.ToRequestsFunc(r.ClusterToTalosConfigs),
120+
},
121+
predicates.ClusterUnpausedAndInfrastructureReady(r.Log),
122+
)
123+
if err != nil {
124+
return err
125+
}
126+
127+
return nil
88128
}
89129

90130
// +kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=talosconfigs,verbs=get;list;watch;create;update;patch;delete
91131
// +kubebuilder:rbac:groups=bootstrap.cluster.x-k8s.io,resources=talosconfigs/status,verbs=get;update;patch
92132
// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status;machines;machines/status,verbs=get;list;watch
133+
// +kubebuilder:rbac:groups=exp.cluster.x-k8s.io,resources=machinepools;machinepools/status,verbs=get;list;watch
93134
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
94135

95136
func (r *TalosConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr error) {
@@ -100,39 +141,39 @@ func (r *TalosConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr
100141

101142
// Lookup the talosconfig config
102143
config := &bootstrapv1alpha3.TalosConfig{}
103-
if err := r.Get(ctx, req.NamespacedName, config); err != nil {
144+
if err := r.Client.Get(ctx, req.NamespacedName, config); err != nil {
104145
if apierrors.IsNotFound(err) {
105146
return ctrl.Result{}, nil
106147
}
107148
log.Error(err, "failed to get config")
108149
return ctrl.Result{}, err
109150
}
110151

111-
// Look up the Machine that owns this talosconfig if there is one
112-
machine, err := util.GetOwnerMachine(ctx, r.Client, config.ObjectMeta)
152+
// Look up the resource that owns this talosconfig if there is one
153+
owner, err := bsutil.GetConfigOwner(ctx, r.Client, config)
113154
if err != nil {
114-
log.Error(err, "could not get owner machine")
155+
log.Error(err, "could not get owner resource")
115156
return ctrl.Result{}, err
116157
}
117-
if machine == nil {
118-
log.Info("Waiting for Machine Controller to set OwnerRef on the talosconfig")
158+
if owner == nil {
159+
log.Info("Waiting for OwnerRef on the talosconfig")
119160
return ctrl.Result{}, errors.New("no owner ref")
120161
}
121-
log = log.WithName(fmt.Sprintf("machine-name=%s", machine.Name))
162+
log = log.WithName(fmt.Sprintf("owner-name=%s", owner.GetName()))
122163

123164
// Lookup the cluster the machine is associated with
124-
cluster, err := util.GetClusterFromMetadata(ctx, r.Client, machine.ObjectMeta)
165+
cluster, err := util.GetClusterByName(ctx, r.Client, owner.GetNamespace(), owner.ClusterName())
125166
if err != nil {
126167
log.Error(err, "could not get cluster by machine metadata")
127168
return ctrl.Result{}, err
128169
}
129170

130171
// Initialize the patch helper
131-
patchHelper, err := patch.NewHelper(config, r)
172+
patchHelper, err := patch.NewHelper(config, r.Client)
132173
if err != nil {
133174
return ctrl.Result{}, err
134175
}
135-
// Always attempt to Patch the KubeadmConfig object and status after each reconciliation.
176+
// Always attempt to Patch the TalosConfig object and status after each reconciliation.
136177
defer func() {
137178
if err := patchHelper.Patch(ctx, config); err != nil {
138179
log.Error(err, "failed to patch config")
@@ -163,9 +204,9 @@ func (r *TalosConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr
163204
}
164205

165206
tcScope := &TalosConfigScope{
166-
Config: config,
167-
Machine: machine,
168-
Cluster: cluster,
207+
Config: config,
208+
ConfigOwner: owner,
209+
Cluster: cluster,
169210
}
170211

171212
var retData *TalosConfigBundle
@@ -214,17 +255,31 @@ func (r *TalosConfigReconciler) Reconcile(req ctrl.Request) (_ ctrl.Result, rerr
214255
}
215256

216257
// Packet acts a fool if you don't prepend #!talos to the userdata
217-
// so we try to suss out if that's the type of machine getting created.
218-
if machine.Spec.InfrastructureRef.Kind == "PacketMachine" {
219-
retData.BootstrapData = "#!talos\n" + retData.BootstrapData
258+
// so we try to suss out if that's the type of machine/machinePool getting created.
259+
if owner.IsMachinePool() {
260+
mp := &expv1.MachinePool{}
261+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(owner.Object, mp); err != nil {
262+
return ctrl.Result{}, err
263+
}
264+
if mp.Spec.Template.Spec.InfrastructureRef.Kind == "PacketMachinePool" {
265+
retData.BootstrapData = "#!talos\n" + retData.BootstrapData
266+
}
267+
} else {
268+
machine := &capiv1.Machine{}
269+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(owner.Object, machine); err != nil {
270+
return ctrl.Result{}, err
271+
}
272+
if machine.Spec.InfrastructureRef.Name == "PacketMachine" {
273+
retData.BootstrapData = "#!talos\n" + retData.BootstrapData
274+
}
220275
}
221276

222277
err = r.writeBootstrapData(ctx, tcScope, []byte(retData.BootstrapData))
223278
if err != nil {
224279
return ctrl.Result{}, err
225280
}
226281

227-
config.Status.DataSecretName = pointer.StringPtr(tcScope.Machine.Name + "-bootstrap-data")
282+
config.Status.DataSecretName = pointer.StringPtr(tcScope.ConfigOwner.GetName() + "-bootstrap-data")
228283
config.Status.TalosConfig = retData.TalosConfig
229284
config.Status.Ready = true
230285

@@ -303,8 +358,22 @@ func (r *TalosConfigReconciler) genConfigs(ctx context.Context, scope *TalosConf
303358
// This also handles version being formatted like "vX.Y.Z" instead of without leading 'v'
304359
// TrimPrefix returns the string unchanged if the prefix isn't present.
305360
k8sVersion := constants.DefaultKubernetesVersion
306-
if scope.Machine.Spec.Version != nil {
307-
k8sVersion = strings.TrimPrefix(*scope.Machine.Spec.Version, "v")
361+
if scope.ConfigOwner.IsMachinePool() {
362+
mp := &expv1.MachinePool{}
363+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(scope.ConfigOwner.Object, mp); err != nil {
364+
return retBundle, err
365+
}
366+
if mp.Spec.Template.Spec.Version != nil {
367+
k8sVersion = strings.TrimPrefix(*mp.Spec.Template.Spec.Version, "v")
368+
}
369+
} else {
370+
machine := &capiv1.Machine{}
371+
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(scope.ConfigOwner.Object, machine); err != nil {
372+
return retBundle, err
373+
}
374+
if machine.Spec.Version != nil {
375+
k8sVersion = strings.TrimPrefix(*machine.Spec.Version, "v")
376+
}
308377
}
309378

310379
clusterDNS := constants.DefaultDNSDomain
@@ -404,3 +473,84 @@ func (r *TalosConfigReconciler) genConfigs(ctx context.Context, scope *TalosConf
404473

405474
return retBundle, nil
406475
}
476+
477+
// MachineToBootstrapMapFunc is a handler.ToRequestsFunc to be used to enqueue
478+
// request for reconciliation of TalosConfig.
479+
func (r *TalosConfigReconciler) MachineToBootstrapMapFunc(o handler.MapObject) []ctrl.Request {
480+
m, ok := o.Object.(*capiv1.Machine)
481+
if !ok {
482+
panic(fmt.Sprintf("Expected a Machine but got a %T", o))
483+
}
484+
485+
result := []ctrl.Request{}
486+
if m.Spec.Bootstrap.ConfigRef != nil && m.Spec.Bootstrap.ConfigRef.GroupVersionKind() == bootstrapv1alpha3.GroupVersion.WithKind("TalosConfig") {
487+
name := client.ObjectKey{Namespace: m.Namespace, Name: m.Spec.Bootstrap.ConfigRef.Name}
488+
result = append(result, ctrl.Request{NamespacedName: name})
489+
}
490+
return result
491+
}
492+
493+
// MachinePoolToBootstrapMapFunc is a handler.ToRequestsFunc to be used to enqueue
494+
// request for reconciliation of TalosConfig.
495+
func (r *TalosConfigReconciler) MachinePoolToBootstrapMapFunc(o handler.MapObject) []ctrl.Request {
496+
m, ok := o.Object.(*expv1.MachinePool)
497+
if !ok {
498+
panic(fmt.Sprintf("Expected a MachinePool but got a %T", o))
499+
}
500+
501+
result := []ctrl.Request{}
502+
configRef := m.Spec.Template.Spec.Bootstrap.ConfigRef
503+
if configRef != nil && configRef.GroupVersionKind().GroupKind() == bootstrapv1alpha3.GroupVersion.WithKind("TalosConfig").GroupKind() {
504+
name := client.ObjectKey{Namespace: m.Namespace, Name: configRef.Name}
505+
result = append(result, ctrl.Request{NamespacedName: name})
506+
}
507+
return result
508+
}
509+
510+
// ClusterToTalosConfigs is a handler.ToRequestsFunc to be used to enqeue
511+
// requests for reconciliation of TalosConfigs.
512+
func (r *TalosConfigReconciler) ClusterToTalosConfigs(o handler.MapObject) []ctrl.Request {
513+
result := []ctrl.Request{}
514+
515+
c, ok := o.Object.(*capiv1.Cluster)
516+
if !ok {
517+
panic(fmt.Sprintf("Expected a Cluster but got a %T", o))
518+
}
519+
520+
selectors := []client.ListOption{
521+
client.InNamespace(c.Namespace),
522+
client.MatchingLabels{
523+
capiv1.ClusterLabelName: c.Name,
524+
},
525+
}
526+
527+
machineList := &capiv1.MachineList{}
528+
if err := r.Client.List(context.TODO(), machineList, selectors...); err != nil {
529+
return nil
530+
}
531+
532+
for _, m := range machineList.Items {
533+
if m.Spec.Bootstrap.ConfigRef != nil &&
534+
m.Spec.Bootstrap.ConfigRef.GroupVersionKind().GroupKind() == bootstrapv1alpha3.GroupVersion.WithKind("TalosConfig").GroupKind() {
535+
name := client.ObjectKey{Namespace: m.Namespace, Name: m.Spec.Bootstrap.ConfigRef.Name}
536+
result = append(result, ctrl.Request{NamespacedName: name})
537+
}
538+
}
539+
540+
if feature.Gates.Enabled(feature.MachinePool) {
541+
machinePoolList := &expv1.MachinePoolList{}
542+
if err := r.Client.List(context.TODO(), machinePoolList, selectors...); err != nil {
543+
return nil
544+
}
545+
546+
for _, mp := range machinePoolList.Items {
547+
if mp.Spec.Template.Spec.Bootstrap.ConfigRef != nil &&
548+
mp.Spec.Template.Spec.Bootstrap.ConfigRef.GroupVersionKind().GroupKind() == bootstrapv1alpha3.GroupVersion.WithKind("TalosConfig").GroupKind() {
549+
name := client.ObjectKey{Namespace: mp.Namespace, Name: mp.Spec.Template.Spec.Bootstrap.ConfigRef.Name}
550+
result = append(result, ctrl.Request{NamespacedName: name})
551+
}
552+
}
553+
}
554+
555+
return result
556+
}

go.mod

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
module github.com/talos-systems/cluster-api-bootstrap-provider-talos
22

3-
go 1.13
3+
go 1.15
44

55
replace github.com/kubernetes-sigs/bootkube => github.com/talos-systems/bootkube v0.14.1-0.20200131192519-720c01d02032
66

77
require (
88
github.com/evanphx/json-patch v4.9.0+incompatible
99
github.com/go-logr/logr v0.1.0
10-
github.com/onsi/ginkgo v1.12.0
11-
github.com/onsi/gomega v1.9.0
10+
github.com/onsi/ginkgo v1.12.1
11+
github.com/onsi/gomega v1.10.1
12+
github.com/spf13/pflag v1.0.5
1213
github.com/talos-systems/crypto v0.2.0
1314
github.com/talos-systems/talos/pkg/machinery v0.0.0-20201203014938-ed31056d91d0
14-
gopkg.in/yaml.v2 v2.2.8
15-
k8s.io/api v0.18.2
16-
k8s.io/apiextensions-apiserver v0.18.2
17-
k8s.io/apimachinery v0.18.2
18-
k8s.io/client-go v0.18.2
19-
k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89
20-
sigs.k8s.io/cluster-api v0.3.6
21-
sigs.k8s.io/controller-runtime v0.6.0
15+
gopkg.in/yaml.v2 v2.3.0
16+
k8s.io/api v0.17.9
17+
k8s.io/apiextensions-apiserver v0.17.9
18+
k8s.io/apimachinery v0.17.9
19+
k8s.io/client-go v0.17.9
20+
k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19
21+
sigs.k8s.io/cluster-api v0.3.12
22+
sigs.k8s.io/controller-runtime v0.5.14
2223
)

0 commit comments

Comments
 (0)