github.com/verrazzano/verrazzano@v1.7.1/cluster-operator/controllers/capi/capi_cluster_controller.go (about) 1 // Copyright (c) 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package capi 5 6 import ( 7 "context" 8 "fmt" 9 10 clustersv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 11 internalcapi "github.com/verrazzano/verrazzano/cluster-operator/internal/capi" 12 "github.com/verrazzano/verrazzano/pkg/constants" 13 vzstring "github.com/verrazzano/verrazzano/pkg/string" 14 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common" 15 "go.uber.org/zap" 16 v1 "k8s.io/api/core/v1" 17 "k8s.io/apimachinery/pkg/api/errors" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 20 "k8s.io/apimachinery/pkg/runtime" 21 "k8s.io/apimachinery/pkg/types" 22 ctrl "sigs.k8s.io/controller-runtime" 23 "sigs.k8s.io/controller-runtime/pkg/client" 24 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 25 ) 26 27 const ( 28 finalizerName = "verrazzano.io/capi-cluster" 29 ) 30 31 type CAPIClusterReconciler struct { 32 client.Client 33 Scheme *runtime.Scheme 34 Log *zap.SugaredLogger 35 RancherIngressHost string 36 RancherEnabled bool 37 } 38 39 func CAPIClusterClientObject() client.Object { 40 obj := &unstructured.Unstructured{} 41 obj.SetGroupVersionKind(internalcapi.GVKCAPICluster) 42 return obj 43 } 44 45 // SetupWithManager creates a new controller and adds it to the manager 46 func (r *CAPIClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { 47 return ctrl.NewControllerManagedBy(mgr). 48 For(CAPIClusterClientObject()). 49 Complete(r) 50 } 51 52 // Reconcile is the main controller reconcile function 53 func (r *CAPIClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 54 r.Log.Infof("Reconciling CAPI cluster: %v", req.NamespacedName) 55 56 cluster := &unstructured.Unstructured{} 57 cluster.SetGroupVersionKind(internalcapi.GVKCAPICluster) 58 err := r.Get(context.TODO(), req.NamespacedName, cluster) 59 if err != nil && !errors.IsNotFound(err) { 60 return ctrl.Result{}, err 61 } 62 63 if errors.IsNotFound(err) { 64 r.Log.Debugf("CAPI cluster %v not found, nothing to do", req.NamespacedName) 65 return ctrl.Result{}, nil 66 } 67 68 // if the deletion timestamp is set, unregister the corresponding Rancher cluster 69 if !cluster.GetDeletionTimestamp().IsZero() { 70 if vzstring.SliceContainsString(cluster.GetFinalizers(), finalizerName) { 71 vmcName := r.getVMCName(cluster) 72 // ensure a base VMC 73 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{ 74 ObjectMeta: metav1.ObjectMeta{ 75 Name: vmcName, 76 Namespace: constants.VerrazzanoMultiClusterNamespace, 77 }, 78 } 79 if err = r.Delete(ctx, vmc); err != nil { 80 if !errors.IsNotFound(err) { 81 return ctrl.Result{}, err 82 } 83 } 84 } 85 86 if err := r.removeFinalizer(cluster); err != nil { 87 return ctrl.Result{}, err 88 } 89 90 return ctrl.Result{}, nil 91 } 92 93 // obtain and persist the API endpoint IP address for the admin cluster 94 err = r.createAdminAccessConfigMap(ctx) 95 if err != nil { 96 return ctrl.Result{}, err 97 } 98 99 vmcName := r.getVMCName(cluster) 100 // ensure a base VMC 101 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{ 102 ObjectMeta: metav1.ObjectMeta{ 103 Name: vmcName, 104 Namespace: constants.VerrazzanoMultiClusterNamespace, 105 }, 106 } 107 if _, err = r.createOrUpdateWorkloadClusterVMC(ctx, cluster, vmc, func() error { 108 vmc.Spec = clustersv1alpha1.VerrazzanoManagedClusterSpec{ 109 Description: fmt.Sprintf("%s VerrazzanoManagedCluster Resource", cluster.GetName()), 110 } 111 return nil 112 }); err != nil { 113 return ctrl.Result{}, err 114 } 115 if err = r.setVMCClusterRef(ctx, cluster, vmc); err != nil { 116 return ctrl.Result{}, err 117 } 118 119 // add a finalizer to the CAPI cluster if it doesn't already exist 120 if err = r.ensureFinalizer(cluster); err != nil { 121 return ctrl.Result{}, err 122 } 123 124 return ctrl.Result{}, nil 125 } 126 127 func (r *CAPIClusterReconciler) setVMCClusterRef(ctx context.Context, cluster *unstructured.Unstructured, vmc *clustersv1alpha1.VerrazzanoManagedCluster) error { 128 vmc.Status.ClusterRef = &clustersv1alpha1.ClusterReference{ 129 APIVersion: cluster.GetAPIVersion(), 130 Kind: cluster.GetKind(), 131 Name: cluster.GetName(), 132 Namespace: cluster.GetNamespace(), 133 } 134 return r.Client.Status().Update(ctx, vmc, &client.UpdateOptions{}) 135 } 136 137 // createOrUpdateWorkloadClusterVMC creates or updates the VMC resource for the workload cluster 138 func (r *CAPIClusterReconciler) createOrUpdateWorkloadClusterVMC(ctx context.Context, cluster *unstructured.Unstructured, vmc *clustersv1alpha1.VerrazzanoManagedCluster, f controllerutil.MutateFn) (*clustersv1alpha1.VerrazzanoManagedCluster, error) { 139 if _, err := ctrl.CreateOrUpdate(ctx, r.Client, vmc, f); err != nil { 140 r.Log.Errorf("Failed to create or update the VMC for cluster %s: %v", cluster.GetName(), err) 141 return nil, err 142 } 143 144 return vmc, nil 145 } 146 147 // createAdminAccessConfigMap creates the config map required for the creation of the admin accessing kubeconfig 148 func (r *CAPIClusterReconciler) createAdminAccessConfigMap(ctx context.Context) error { 149 ep := &v1.Endpoints{} 150 if err := r.Get(ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, ep); err != nil { 151 return err 152 } 153 apiServerIP := ep.Subsets[0].Addresses[0].IP 154 155 // create the admin server IP config map 156 cm := &v1.ConfigMap{ 157 ObjectMeta: metav1.ObjectMeta{ 158 Name: "verrazzano-admin-cluster", 159 Namespace: constants.VerrazzanoMultiClusterNamespace, 160 }, 161 } 162 if _, err := ctrl.CreateOrUpdate(ctx, r.Client, cm, func() error { 163 if cm.Data == nil { 164 cm.Data = make(map[string]string) 165 } 166 cm.Data["server"] = fmt.Sprintf("https://%s:6443", apiServerIP) 167 168 return nil 169 }); err != nil { 170 r.Log.Errorf("Failed to create the Verrazzano admin cluster config map: %v", err) 171 return err 172 } 173 return nil 174 } 175 176 // ensureFinalizer adds a finalizer to the CAPI cluster if the finalizer is not already present 177 func (r *CAPIClusterReconciler) ensureFinalizer(cluster *unstructured.Unstructured) error { 178 if finalizers, added := vzstring.SliceAddString(cluster.GetFinalizers(), finalizerName); added { 179 cluster.SetFinalizers(finalizers) 180 if err := r.Update(context.TODO(), cluster); err != nil { 181 return err 182 } 183 } 184 return nil 185 } 186 187 // removeFinalizer removes the finalizer from the Rancher cluster resource 188 func (r *CAPIClusterReconciler) removeFinalizer(cluster *unstructured.Unstructured) error { 189 finalizers := vzstring.RemoveStringFromSlice(cluster.GetFinalizers(), finalizerName) 190 cluster.SetFinalizers(finalizers) 191 192 if err := r.Update(context.TODO(), cluster); err != nil { 193 return err 194 } 195 return nil 196 } 197 198 func (r *CAPIClusterReconciler) getVMCName(cluster *unstructured.Unstructured) string { 199 // check for existence of a Rancher cluster management resource 200 rancherMgmtCluster := &unstructured.Unstructured{} 201 rancherMgmtCluster.SetGroupVersionKind(common.GetRancherMgmtAPIGVKForKind("Cluster")) 202 err := r.Get(context.TODO(), types.NamespacedName{Name: cluster.GetName(), Namespace: cluster.GetNamespace()}, rancherMgmtCluster) 203 if err != nil { 204 return cluster.GetName() 205 } 206 // return the display Name 207 return rancherMgmtCluster.UnstructuredContent()["spec"].(map[string]interface{})["displayName"].(string) 208 }