@@ -20,14 +20,18 @@ import (
20
20
"context"
21
21
"encoding/json"
22
22
"fmt"
23
+ "time"
23
24
25
+ corev1 "k8s.io/api/core/v1"
24
26
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25
27
"k8s.io/apimachinery/pkg/labels"
26
28
"k8s.io/apimachinery/pkg/runtime"
27
29
"k8s.io/apimachinery/pkg/types"
30
+ "k8s.io/client-go/tools/clientcmd"
28
31
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
29
32
ctrl "sigs.k8s.io/controller-runtime"
30
33
"sigs.k8s.io/controller-runtime/pkg/client"
34
+ "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
31
35
"sigs.k8s.io/controller-runtime/pkg/handler"
32
36
"sigs.k8s.io/controller-runtime/pkg/log"
33
37
"sigs.k8s.io/controller-runtime/pkg/source"
@@ -38,14 +42,27 @@ import (
38
42
// ClusterBootstrapConfigReconciler reconciles a ClusterBootstrapConfig object
39
43
type ClusterBootstrapConfigReconciler struct {
40
44
client.Client
41
- Scheme * runtime.Scheme
45
+ Scheme * runtime.Scheme
46
+ configParser func (b []byte ) (client.Client , error )
42
47
}
43
48
49
+ func NewClusterBootstrapConfigReconciler (c client.Client , s * runtime.Scheme ) * ClusterBootstrapConfigReconciler {
50
+ return & ClusterBootstrapConfigReconciler {
51
+ Client : c ,
52
+ Scheme : s ,
53
+ configParser : bytesToKubeConfig ,
54
+ }
55
+ }
56
+
57
+ // TODO: make this configurable on the Spec
58
+ var requeueAfterTime = time .Minute * 2
59
+
44
60
//+kubebuilder:rbac:groups=capi.weave.works,resources=clusterbootstrapconfigs,verbs=get;list;watch;create;update;patch;delete
45
61
//+kubebuilder:rbac:groups=capi.weave.works,resources=clusterbootstrapconfigs/status,verbs=get;update;patch
46
62
//+kubebuilder:rbac:groups=capi.weave.works,resources=clusterbootstrapconfigs/finalizers,verbs=update
47
63
//+kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete
48
64
//+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;list;watch;update;patch
65
+ //+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch
49
66
50
67
// Reconcile is part of the main kubernetes reconciliation loop which aims to
51
68
// move the current state of the cluster closer to the desired state.
@@ -58,7 +75,6 @@ func (r *ClusterBootstrapConfigReconciler) Reconcile(ctx context.Context, req ct
58
75
if err := r .Client .Get (ctx , req .NamespacedName , & clusterBootstrapConfig ); err != nil {
59
76
return ctrl.Result {}, client .IgnoreNotFound (err )
60
77
}
61
-
62
78
logger .Info ("cluster bootstrap config loaded" , "name" , clusterBootstrapConfig .ObjectMeta .Name )
63
79
64
80
clusters , err := r .getClustersBySelector (ctx , req .Namespace , clusterBootstrapConfig .Spec .ClusterSelector )
@@ -68,6 +84,22 @@ func (r *ClusterBootstrapConfigReconciler) Reconcile(ctx context.Context, req ct
68
84
logger .Info ("identified clusters for reconciliation" , "clusterCount" , len (clusters ))
69
85
70
86
for _ , c := range clusters {
87
+ if clusterBootstrapConfig .Spec .RequireClusterReady {
88
+ clusterName := types.NamespacedName {Name : c .GetName (), Namespace : c .GetNamespace ()}
89
+ clusterClient , err := r .clientForCluster (ctx , clusterName )
90
+ if err != nil {
91
+ return ctrl.Result {}, fmt .Errorf ("failed to create client for cluster %s: %w" , clusterName , err )
92
+ }
93
+
94
+ ready , err := IsControlPlaneReady (ctx , clusterClient )
95
+ if err != nil {
96
+ return ctrl.Result {}, fmt .Errorf ("failed to check readiness of cluster %s: %w" , clusterName , err )
97
+ }
98
+ if ! ready {
99
+ logger .Info ("waiting for control plane to be ready" , "cluster" , clusterName )
100
+ return ctrl.Result {RequeueAfter : time .Minute * 2 }, nil
101
+ }
102
+ }
71
103
if err := bootstrapClusterWithConfig (ctx , logger , r .Client , c , & clusterBootstrapConfig ); err != nil {
72
104
return ctrl.Result {}, fmt .Errorf ("failed to bootstrap cluster config: %w" , err )
73
105
}
@@ -170,3 +202,59 @@ func (r *ClusterBootstrapConfigReconciler) clusterToClusterBootstrapConfig(o cli
170
202
}
171
203
return result
172
204
}
205
+
206
+ func (r * ClusterBootstrapConfigReconciler ) clientForCluster (ctx context.Context , name types.NamespacedName ) (client.Client , error ) {
207
+ kubeConfigBytes , err := r .getKubeConfig (ctx , name )
208
+ if err != nil {
209
+ return nil , err
210
+ }
211
+
212
+ client , err := r .configParser (kubeConfigBytes )
213
+ if err != nil {
214
+ return nil , fmt .Errorf ("getting client for cluster %s: %w" , name , err )
215
+ }
216
+ return client , nil
217
+ }
218
+
219
+ func (r * ClusterBootstrapConfigReconciler ) getKubeConfig (ctx context.Context , cluster types.NamespacedName ) ([]byte , error ) {
220
+ secretName := types.NamespacedName {
221
+ Namespace : cluster .Namespace ,
222
+ Name : cluster .Name + "-kubeconfig" ,
223
+ }
224
+
225
+ var secret corev1.Secret
226
+ if err := r .Client .Get (ctx , secretName , & secret ); err != nil {
227
+ return nil , fmt .Errorf ("unable to read KubeConfig secret %q error: %w" , secretName , err )
228
+ }
229
+
230
+ var kubeConfig []byte
231
+ for k := range secret .Data {
232
+ if k == "value" || k == "value.yaml" {
233
+ kubeConfig = secret .Data [k ]
234
+ break
235
+ }
236
+ }
237
+
238
+ if len (kubeConfig ) == 0 {
239
+ return nil , fmt .Errorf ("KubeConfig secret %q doesn't contain a 'value' key " , secretName )
240
+ }
241
+
242
+ return kubeConfig , nil
243
+ }
244
+
245
+ func bytesToKubeConfig (b []byte ) (client.Client , error ) {
246
+ restConfig , err := clientcmd .RESTConfigFromKubeConfig (b )
247
+ if err != nil {
248
+ return nil , fmt .Errorf ("failed to parse KubeConfig from secret: %w" , err )
249
+ }
250
+ restMapper , err := apiutil .NewDynamicRESTMapper (restConfig )
251
+ if err != nil {
252
+ return nil , fmt .Errorf ("failed to create RESTMapper from config: %w" , err )
253
+ }
254
+
255
+ client , err := client .New (restConfig , client.Options {Mapper : restMapper })
256
+ if err != nil {
257
+ return nil , fmt .Errorf ("failed to create a client from config: %w" , err )
258
+ }
259
+ return client , nil
260
+ }
0 commit comments