github.com/verrazzano/verrazzano@v1.7.0/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 clustersv1alpha1 "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 10 "github.com/verrazzano/verrazzano/cluster-operator/internal/capi" 11 "github.com/verrazzano/verrazzano/pkg/constants" 12 vzstring "github.com/verrazzano/verrazzano/pkg/string" 13 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/common" 14 "go.uber.org/zap" 15 appsv1 "k8s.io/api/apps/v1" 16 v1 "k8s.io/api/core/v1" 17 netv1 "k8s.io/api/networking/v1" 18 rbacv1 "k8s.io/api/rbac/v1" 19 "k8s.io/apimachinery/pkg/api/errors" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 22 "k8s.io/apimachinery/pkg/runtime" 23 "k8s.io/apimachinery/pkg/types" 24 "k8s.io/client-go/rest" 25 "k8s.io/client-go/tools/clientcmd" 26 ctrl "sigs.k8s.io/controller-runtime" 27 "sigs.k8s.io/controller-runtime/pkg/client" 28 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 29 ) 30 31 const ( 32 finalizerName = "verrazzano.io/capi-cluster" 33 clusterProvisionerLabel = "cluster.verrazzano.io/provisioner" 34 clusterStatusSuffix = "-cluster-status" 35 clusterIDKey = "clusterId" 36 clusterRegistrationStatusKey = "clusterRegistration" 37 registrationRetrieved = "retrieved" 38 registrationInitiated = "initiated" 39 ) 40 41 type CAPIClusterReconciler struct { 42 client.Client 43 Scheme *runtime.Scheme 44 Log *zap.SugaredLogger 45 RancherRegistrar *RancherRegistration 46 RancherIngressHost string 47 RancherEnabled bool 48 VerrazzanoRegistrar *VerrazzanoRegistration 49 } 50 51 func CAPIClusterClientObject() client.Object { 52 obj := &unstructured.Unstructured{} 53 obj.SetGroupVersionKind(capi.GVKCAPICluster) 54 return obj 55 } 56 57 // SetupWithManager creates a new controller and adds it to the manager 58 func (r *CAPIClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { 59 return ctrl.NewControllerManagedBy(mgr). 60 For(CAPIClusterClientObject()). 61 Complete(r) 62 } 63 64 // Reconcile is the main controller reconcile function 65 func (r *CAPIClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 66 r.Log.Infof("Reconciling CAPI cluster: %v", req.NamespacedName) 67 68 cluster := &unstructured.Unstructured{} 69 cluster.SetGroupVersionKind(capi.GVKCAPICluster) 70 err := r.Get(context.TODO(), req.NamespacedName, cluster) 71 if err != nil && !errors.IsNotFound(err) { 72 return ctrl.Result{}, err 73 } 74 75 if errors.IsNotFound(err) { 76 r.Log.Debugf("CAPI cluster %v not found, nothing to do", req.NamespacedName) 77 return ctrl.Result{}, nil 78 } 79 80 // if the deletion timestamp is set, unregister the corresponding Rancher cluster 81 if !cluster.GetDeletionTimestamp().IsZero() { 82 if vzstring.SliceContainsString(cluster.GetFinalizers(), finalizerName) { 83 err = r.unregisterCluster(ctx, cluster) 84 if err != nil { 85 r.Log.Warnf("Unable to unregister cluster %s: %v. Cluster deletion will proceed.", cluster.GetName(), err) 86 } 87 } 88 89 if err := r.removeFinalizer(cluster); err != nil { 90 return ctrl.Result{}, err 91 } 92 93 // delete the cluster id secret 94 clusterRegistrationStatusSecret := &v1.Secret{ 95 ObjectMeta: metav1.ObjectMeta{ 96 Namespace: constants.VerrazzanoCAPINamespace, 97 Name: cluster.GetName() + clusterStatusSuffix, 98 }, 99 } 100 err = r.Delete(ctx, clusterRegistrationStatusSecret) 101 if err != nil { 102 if !errors.IsNotFound(err) { 103 return ctrl.Result{}, err 104 } 105 } 106 107 return ctrl.Result{}, nil 108 } 109 110 // obtain and persist the API endpoint IP address for the admin cluster 111 err = r.createAdminAccessConfigMap(ctx) 112 if err != nil { 113 return ctrl.Result{}, err 114 } 115 116 vmcName := r.getVMCName(cluster) 117 // ensure a base VMC 118 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: vmcName, 121 Namespace: constants.VerrazzanoMultiClusterNamespace, 122 }, 123 } 124 if _, err = r.createOrUpdateWorkloadClusterVMC(ctx, cluster, vmc, func() error { 125 vmc.Spec = clustersv1alpha1.VerrazzanoManagedClusterSpec{ 126 Description: fmt.Sprintf("%s VerrazzanoManagedCluster Resource", cluster.GetName()), 127 } 128 return nil 129 }); err != nil { 130 return ctrl.Result{}, err 131 } 132 133 if r.RancherEnabled { 134 // Is Rancher Deployment ready 135 r.Log.Debugf("Attempting cluster regisration with Rancher") 136 result, err := r.RancherRegistrar.doReconcile(ctx, cluster) 137 if err != nil { 138 return result, err 139 } 140 } 141 142 // add a finalizer to the CAPI cluster if it doesn't already exist 143 if err = r.ensureFinalizer(cluster); err != nil { 144 return ctrl.Result{}, err 145 } 146 147 r.Log.Debugf("Attempting cluster regisration with Verrazzano") 148 return verrazzanoReconcileFn(ctx, cluster, r) 149 } 150 151 // createOrUpdateWorkloadClusterVMC creates or updates the VMC resource for the workload cluster 152 func (r *CAPIClusterReconciler) createOrUpdateWorkloadClusterVMC(ctx context.Context, cluster *unstructured.Unstructured, vmc *clustersv1alpha1.VerrazzanoManagedCluster, f controllerutil.MutateFn) (*clustersv1alpha1.VerrazzanoManagedCluster, error) { 153 if _, err := ctrl.CreateOrUpdate(ctx, r.Client, vmc, f); err != nil { 154 r.Log.Errorf("Failed to create or update the VMC for cluster %s: %v", cluster.GetName(), err) 155 return nil, err 156 } 157 158 return vmc, nil 159 } 160 161 // createAdminAccessConfigMap creates the config map required for the creation of the admin accessing kubeconfig 162 func (r *CAPIClusterReconciler) createAdminAccessConfigMap(ctx context.Context) error { 163 ep := &v1.Endpoints{} 164 if err := r.Get(ctx, types.NamespacedName{Name: "kubernetes", Namespace: "default"}, ep); err != nil { 165 return err 166 } 167 apiServerIP := ep.Subsets[0].Addresses[0].IP 168 169 // create the admin server IP config map 170 cm := &v1.ConfigMap{ 171 ObjectMeta: metav1.ObjectMeta{ 172 Name: "verrazzano-admin-cluster", 173 Namespace: constants.VerrazzanoMultiClusterNamespace, 174 }, 175 } 176 if _, err := ctrl.CreateOrUpdate(ctx, r.Client, cm, func() error { 177 if cm.Data == nil { 178 cm.Data = make(map[string]string) 179 } 180 cm.Data["server"] = fmt.Sprintf("https://%s:6443", apiServerIP) 181 182 return nil 183 }); err != nil { 184 r.Log.Errorf("Failed to create the Verrazzano admin cluster config map: %v", err) 185 return err 186 } 187 return nil 188 } 189 190 // unregisterCluster removes the cluster from Rancher and/or Verrazzano. 191 func (r *CAPIClusterReconciler) unregisterCluster(ctx context.Context, cluster *unstructured.Unstructured) error { 192 var err error 193 if r.RancherEnabled { 194 err = clusterRancherUnregistrationFn(ctx, r.RancherRegistrar, cluster) 195 if err != nil { 196 return err 197 } 198 } 199 if err = UnregisterVerrazzanoCluster(ctx, r.VerrazzanoRegistrar, cluster); err != nil { 200 return err 201 } 202 203 // remove the VMC 204 vmc := &clustersv1alpha1.VerrazzanoManagedCluster{ 205 ObjectMeta: metav1.ObjectMeta{ 206 Name: cluster.GetName(), 207 Namespace: constants.VerrazzanoMultiClusterNamespace, 208 }, 209 } 210 err = r.Delete(ctx, vmc) 211 if err != nil { 212 if errors.IsNotFound(err) { 213 r.Log.Infof("VMC for cluster %s not found - nothing to do", cluster.GetName()) 214 return nil 215 } 216 return err 217 } 218 219 return nil 220 } 221 222 // ensureFinalizer adds a finalizer to the CAPI cluster if the finalizer is not already present 223 func (r *CAPIClusterReconciler) ensureFinalizer(cluster *unstructured.Unstructured) error { 224 if finalizers, added := vzstring.SliceAddString(cluster.GetFinalizers(), finalizerName); added { 225 cluster.SetFinalizers(finalizers) 226 if err := r.Update(context.TODO(), cluster); err != nil { 227 return err 228 } 229 } 230 return nil 231 } 232 233 // removeFinalizer removes the finalizer from the Rancher cluster resource 234 func (r *CAPIClusterReconciler) removeFinalizer(cluster *unstructured.Unstructured) error { 235 finalizers := vzstring.RemoveStringFromSlice(cluster.GetFinalizers(), finalizerName) 236 cluster.SetFinalizers(finalizers) 237 238 if err := r.Update(context.TODO(), cluster); err != nil { 239 return err 240 } 241 return nil 242 } 243 244 func (r *CAPIClusterReconciler) getVMCName(cluster *unstructured.Unstructured) string { 245 // check for existence of a Rancher cluster management resource 246 rancherMgmtCluster := &unstructured.Unstructured{} 247 rancherMgmtCluster.SetGroupVersionKind(common.GetRancherMgmtAPIGVKForKind("Cluster")) 248 err := r.Get(context.TODO(), types.NamespacedName{Name: cluster.GetName(), Namespace: cluster.GetNamespace()}, rancherMgmtCluster) 249 if err != nil { 250 return cluster.GetName() 251 } 252 // return the display Name 253 return rancherMgmtCluster.UnstructuredContent()["spec"].(map[string]interface{})["displayName"].(string) 254 } 255 256 // getClusterClient returns a controller runtime client configured for the workload cluster 257 func getClusterClient(restConfig *rest.Config) (client.Client, error) { 258 scheme := runtime.NewScheme() 259 _ = rbacv1.AddToScheme(scheme) 260 _ = v1.AddToScheme(scheme) 261 _ = netv1.AddToScheme(scheme) 262 _ = appsv1.AddToScheme(scheme) 263 _ = clustersv1alpha1.AddToScheme(scheme) 264 265 return client.New(restConfig, client.Options{Scheme: scheme}) 266 } 267 268 // getClusterID returns the cluster ID assigned by Rancher for the given cluster 269 func getClusterID(ctx context.Context, client client.Client, cluster *unstructured.Unstructured) string { 270 clusterID := "" 271 272 regStatusSecret, err := getClusterRegistrationStatusSecret(ctx, client, cluster) 273 if err != nil { 274 return clusterID 275 } 276 clusterID = string(regStatusSecret.Data[clusterIDKey]) 277 278 return clusterID 279 } 280 281 // getClusterRegistrationStatus returns the Rancher registration status for the cluster 282 func getClusterRegistrationStatus(ctx context.Context, c client.Client, cluster *unstructured.Unstructured) string { 283 clusterStatus := registrationRetrieved 284 285 regStatusSecret, err := getClusterRegistrationStatusSecret(ctx, c, cluster) 286 if err != nil { 287 return clusterStatus 288 } 289 clusterStatus = string(regStatusSecret.Data[clusterRegistrationStatusKey]) 290 291 return clusterStatus 292 } 293 294 // getClusterRegistrationStatusSecret returns the secret that stores cluster status information 295 func getClusterRegistrationStatusSecret(ctx context.Context, c client.Client, cluster *unstructured.Unstructured) (*v1.Secret, error) { 296 clusterIDSecret := &v1.Secret{} 297 secretName := types.NamespacedName{ 298 Namespace: constants.VerrazzanoCAPINamespace, 299 Name: cluster.GetName() + clusterStatusSuffix, 300 } 301 err := c.Get(ctx, secretName, clusterIDSecret) 302 if err != nil { 303 return nil, err 304 } 305 return clusterIDSecret, err 306 } 307 308 // persistClusterStatus stores the cluster status in the cluster status secret 309 func persistClusterStatus(ctx context.Context, client client.Client, cluster *unstructured.Unstructured, log *zap.SugaredLogger, clusterID string, status string) error { 310 log.Debugf("Persisting cluster %s cluster id: %s", cluster.GetName(), clusterID) 311 clusterRegistrationStatusSecret := &v1.Secret{ 312 ObjectMeta: metav1.ObjectMeta{ 313 Name: cluster.GetName() + clusterStatusSuffix, 314 Namespace: constants.VerrazzanoCAPINamespace, 315 }, 316 } 317 _, err := ctrl.CreateOrUpdate(ctx, client, clusterRegistrationStatusSecret, func() error { 318 // Build the secret data 319 if clusterRegistrationStatusSecret.Data == nil { 320 clusterRegistrationStatusSecret.Data = make(map[string][]byte) 321 } 322 clusterRegistrationStatusSecret.Data[clusterIDKey] = []byte(clusterID) 323 clusterRegistrationStatusSecret.Data[clusterRegistrationStatusKey] = []byte(status) 324 325 return nil 326 }) 327 if err != nil { 328 log.Errorf("Unable to persist status for cluster %s: %v", cluster.GetName(), err) 329 return err 330 } 331 332 return nil 333 } 334 335 // getWorkloadClusterKubeconfig returns a kubeconfig for accessing the workload cluster 336 func getWorkloadClusterKubeconfig(client client.Client, cluster *unstructured.Unstructured, log *zap.SugaredLogger) ([]byte, error) { 337 // get the cluster kubeconfig 338 kubeconfigSecret := &v1.Secret{} 339 err := client.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("%s-kubeconfig", cluster.GetName()), Namespace: cluster.GetNamespace()}, kubeconfigSecret) 340 if err != nil { 341 log.Warn(err, "failed to obtain workload cluster kubeconfig resource. Re-queuing...") 342 return nil, err 343 } 344 kubeconfig, ok := kubeconfigSecret.Data["value"] 345 if !ok { 346 log.Error(err, "failed to read kubeconfig from resource") 347 return nil, fmt.Errorf("Unable to read kubeconfig from retrieved cluster resource") 348 } 349 350 return kubeconfig, nil 351 } 352 353 func getWorkloadClusterClient(client client.Client, log *zap.SugaredLogger, cluster *unstructured.Unstructured) (client.Client, error) { 354 // identify whether the workload cluster is using "untrusted" certs 355 kubeconfig, err := getWorkloadClusterKubeconfig(client, cluster, log) 356 if err != nil { 357 // requeue since we're waiting for cluster 358 return nil, err 359 } 360 // create a workload cluster client 361 // create workload cluster client 362 restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig) 363 if err != nil { 364 log.Warnf("Failed getting rest config from workload kubeconfig") 365 return nil, err 366 } 367 workloadClient, err := getClusterClient(restConfig) 368 if err != nil { 369 return nil, err 370 } 371 return workloadClient, nil 372 }