@@ -20,11 +20,17 @@ import (
2020 "context"
2121 "fmt"
2222
23+ cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1"
2324 "github.com/pkg/errors"
25+ corev1 "k8s.io/api/core/v1"
2426 apierrors "k8s.io/apimachinery/pkg/api/errors"
27+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+ "k8s.io/apimachinery/pkg/runtime"
2529 "k8s.io/apimachinery/pkg/types"
30+ kerrors "k8s.io/apimachinery/pkg/util/errors"
2631 "k8s.io/client-go/tools/record"
2732 "k8s.io/klog/v2"
33+ "k8s.io/utils/ptr"
2834 ctrl "sigs.k8s.io/controller-runtime"
2935 "sigs.k8s.io/controller-runtime/pkg/client"
3036 "sigs.k8s.io/controller-runtime/pkg/controller"
@@ -35,9 +41,15 @@ import (
3541 infrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/api/v1beta2"
3642 rosacontrolplanev1 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2"
3743 expinfrav1 "sigs.k8s.io/cluster-api-provider-aws/v2/exp/api/v1beta2"
44+ "sigs.k8s.io/cluster-api-provider-aws/v2/exp/utils"
45+ "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud"
46+ "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope"
47+ stsservice "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/services/sts"
3848 "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/logger"
49+ "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/rosa"
3950 "sigs.k8s.io/cluster-api-provider-aws/v2/util/paused"
4051 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
52+ expclusterv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
4153 "sigs.k8s.io/cluster-api/util"
4254 "sigs.k8s.io/cluster-api/util/patch"
4355 "sigs.k8s.io/cluster-api/util/predicates"
@@ -48,16 +60,20 @@ type ROSAClusterReconciler struct {
4860 client.Client
4961 Recorder record.EventRecorder
5062 WatchFilterValue string
63+ NewStsClient func (cloud.ScopeUsage , cloud.Session , logger.Wrapper , runtime.Object ) stsservice.STSClient
64+ NewOCMClient func (ctx context.Context , rosaScope * scope.ROSAControlPlaneScope ) (rosa.OCMClient , error )
5165}
5266
5367// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters,verbs=get;list;watch;update;patch;delete
5468// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosaclusters/status,verbs=get;update;patch
5569// +kubebuilder:rbac:groups=controlplane.cluster.x-k8s.io,resources=rosacontrolplanes;rosacontrolplanes/status,verbs=get;list;watch
5670// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters;clusters/status,verbs=get;list;watch
71+ // +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machinepools;machinepools/status,verbs=get;list;watch;create
72+ // +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=rosamachinepools;rosamachinepools/status,verbs=get;list;watch;create
5773// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
5874
5975func (r * ROSAClusterReconciler ) Reconcile (ctx context.Context , req ctrl.Request ) (_ ctrl.Result , reterr error ) {
60- log := ctrl . LoggerFrom (ctx )
76+ log := logger . FromContext (ctx )
6177 log .Info ("Reconciling ROSACluster" )
6278
6379 // Fetch the ROSACluster instance
@@ -70,11 +86,17 @@ func (r *ROSAClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
7086 return reconcile.Result {}, err
7187 }
7288
89+ if ! rosaCluster .DeletionTimestamp .IsZero () {
90+ log .Info ("Deleting ROSACluster." )
91+ return reconcile.Result {}, nil
92+ }
93+
7394 // Fetch the Cluster.
7495 cluster , err := util .GetOwnerCluster (ctx , r .Client , rosaCluster .ObjectMeta )
7596 if err != nil {
7697 return reconcile.Result {}, err
7798 }
99+
78100 if cluster == nil {
79101 log .Info ("Cluster Controller has not yet set OwnerRef" )
80102 return reconcile.Result {}, nil
@@ -111,13 +133,32 @@ func (r *ROSAClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
111133 return reconcile.Result {}, fmt .Errorf ("failed to patch ROSACluster: %w" , err )
112134 }
113135
136+ rosaScope , err := scope .NewROSAControlPlaneScope (scope.ROSAControlPlaneScopeParams {
137+ Client : r .Client ,
138+ Cluster : cluster ,
139+ ControlPlane : controlPlane ,
140+ ControllerName : "" ,
141+ Logger : log ,
142+ NewStsClient : r .NewStsClient ,
143+ })
144+ if err != nil {
145+ return ctrl.Result {}, fmt .Errorf ("failed to create rosa controlplane scope: %w" , err )
146+ }
147+
148+ err = r .syncROSAClusterNodePools (ctx , controlPlane , rosaScope )
149+ if err != nil {
150+ return ctrl.Result {}, fmt .Errorf ("failed to sync ROSA cluster nodePools: %w" , err )
151+ }
152+
114153 log .Info ("Successfully reconciled ROSACluster" )
115154
116155 return reconcile.Result {}, nil
117156}
118157
119158func (r * ROSAClusterReconciler ) SetupWithManager (ctx context.Context , mgr ctrl.Manager , options controller.Options ) error {
120159 log := logger .FromContext (ctx )
160+ r .NewOCMClient = rosa .NewWrappedOCMClient
161+ r .NewStsClient = scope .NewSTSClient
121162
122163 rosaCluster := & expinfrav1.ROSACluster {}
123164
@@ -196,3 +237,132 @@ func (r *ROSAClusterReconciler) rosaControlPlaneToManagedCluster(log *logger.Log
196237 }
197238 }
198239}
240+
241+ // getRosMachinePools returns a map of RosaMachinePool names associatd with the cluster.
242+ func (r * ROSAClusterReconciler ) getRosaMachinePoolNames (ctx context.Context , cluster * clusterv1.Cluster ) (map [string ]bool , error ) {
243+ selectors := []client.ListOption {
244+ client .InNamespace (cluster .GetNamespace ()),
245+ client.MatchingLabels {
246+ clusterv1 .ClusterNameLabel : cluster .GetName (),
247+ },
248+ }
249+
250+ rosaMachinePoolList := & expinfrav1.ROSAMachinePoolList {}
251+ err := r .Client .List (ctx , rosaMachinePoolList , selectors ... )
252+ if err != nil {
253+ return nil , err
254+ }
255+
256+ rosaMPNames := make (map [string ]bool )
257+ for _ , rosaMP := range rosaMachinePoolList .Items {
258+ rosaMPNames [rosaMP .Spec .NodePoolName ] = true
259+ }
260+
261+ return rosaMPNames , nil
262+ }
263+
264+ // buildROSAMachinePool returns a ROSAMachinePool and its corresponding MachinePool.
265+ func (r * ROSAClusterReconciler ) buildROSAMachinePool (nodePoolName string , clusterName string , namespace string , nodePool * cmv1.NodePool ) (* expinfrav1.ROSAMachinePool , * expclusterv1.MachinePool ) {
266+ rosaMPSpec := utils .NodePoolToRosaMachinePoolSpec (nodePool )
267+ rosaMachinePool := & expinfrav1.ROSAMachinePool {
268+ TypeMeta : metav1.TypeMeta {
269+ APIVersion : expinfrav1 .GroupVersion .String (),
270+ Kind : "ROSAMachinePool" ,
271+ },
272+ ObjectMeta : metav1.ObjectMeta {
273+ Name : nodePoolName ,
274+ Namespace : namespace ,
275+ Labels : map [string ]string {
276+ clusterv1 .ClusterNameLabel : clusterName ,
277+ },
278+ },
279+ Spec : rosaMPSpec ,
280+ }
281+ machinePool := & expclusterv1.MachinePool {
282+ TypeMeta : metav1.TypeMeta {
283+ APIVersion : expclusterv1 .GroupVersion .String (),
284+ Kind : "MachinePool" ,
285+ },
286+ ObjectMeta : metav1.ObjectMeta {
287+ Name : nodePoolName ,
288+ Namespace : namespace ,
289+ Labels : map [string ]string {
290+ clusterv1 .ClusterNameLabel : clusterName ,
291+ },
292+ },
293+ Spec : expclusterv1.MachinePoolSpec {
294+ ClusterName : clusterName ,
295+ Replicas : ptr .To (int32 (1 )),
296+ Template : clusterv1.MachineTemplateSpec {
297+ Spec : clusterv1.MachineSpec {
298+ ClusterName : clusterName ,
299+ Bootstrap : clusterv1.Bootstrap {
300+ DataSecretName : ptr .To (string ("" )),
301+ },
302+ InfrastructureRef : corev1.ObjectReference {
303+ APIVersion : expinfrav1 .GroupVersion .String (),
304+ Kind : "ROSAMachinePool" ,
305+ Name : rosaMachinePool .Name ,
306+ },
307+ },
308+ },
309+ },
310+ }
311+
312+ return rosaMachinePool , machinePool
313+ }
314+
315+ // syncROSAClusterNodePools ensure every NodePool has a MachinePool and create a corresponding MachinePool if it does not exist.
316+ func (r * ROSAClusterReconciler ) syncROSAClusterNodePools (ctx context.Context , controlPlane * rosacontrolplanev1.ROSAControlPlane , rosaScope * scope.ROSAControlPlaneScope ) error {
317+ if controlPlane .Status .Ready {
318+ if r .NewOCMClient == nil {
319+ return fmt .Errorf ("failed to create OCM client: NewOCMClient is nil" )
320+ }
321+
322+ ocmClient , err := r .NewOCMClient (ctx , rosaScope )
323+ if err != nil || ocmClient == nil {
324+ return fmt .Errorf ("failed to create OCM client: %w" , err )
325+ }
326+
327+ // List the ROSA-HCP nodePools and MachinePools
328+ nodePools , err := ocmClient .GetNodePools (rosaScope .ControlPlane .Status .ID )
329+ if err != nil {
330+ return fmt .Errorf ("failed to get nodePools: %w" , err )
331+ }
332+
333+ rosaMPNames , err := r .getRosaMachinePoolNames (ctx , rosaScope .Cluster )
334+ if err != nil {
335+ return fmt .Errorf ("failed to get Rosa machinePool names: %w" , err )
336+ }
337+
338+ // Ensure every NodePool has a MachinePool and create a corresponding MachinePool if it does not exist.
339+ var errs []error
340+ for _ , nodePool := range nodePools {
341+ // continue if nodePool is not in ready state.
342+ if ! rosa .IsNodePoolReady (nodePool ) {
343+ continue
344+ }
345+ // continue if nodePool exist
346+ if rosaMPNames [nodePool .ID ()] {
347+ continue
348+ }
349+ // create ROSAMachinePool & MachinePool
350+ rosaMachinePool , machinePool := r .buildROSAMachinePool (nodePool .ID (), rosaScope .Cluster .Name , rosaScope .Cluster .Namespace , nodePool )
351+
352+ rosaScope .Logger .Info (fmt .Sprintf ("Create ROSAMachinePool %s" , rosaMachinePool .Name ))
353+ if err = r .Client .Create (ctx , rosaMachinePool ); err != nil {
354+ errs = append (errs , err )
355+ }
356+
357+ rosaScope .Logger .Info (fmt .Sprintf ("Create MachinePool %s" , machinePool .Name ))
358+ if err = r .Client .Create (ctx , machinePool ); err != nil {
359+ errs = append (errs , err )
360+ }
361+ }
362+
363+ if len (errs ) > 0 {
364+ return kerrors .NewAggregate (errs )
365+ }
366+ }
367+ return nil
368+ }
0 commit comments