github.com/verrazzano/verrazzano@v1.7.0/cluster-operator/controllers/vmc/sync_manifest_secret.go (about) 1 // Copyright (c) 2021, 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 vmc 5 6 import ( 7 "context" 8 "fmt" 9 "strings" 10 11 clusterapi "github.com/verrazzano/verrazzano/cluster-operator/apis/clusters/v1alpha1" 12 "github.com/verrazzano/verrazzano/cluster-operator/controllers/rancher" 13 constants2 "github.com/verrazzano/verrazzano/pkg/mcconstants" 14 "github.com/verrazzano/verrazzano/pkg/rancherutil" 15 "github.com/verrazzano/verrazzano/platform-operator/constants" 16 corev1 "k8s.io/api/core/v1" 17 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 "k8s.io/apimachinery/pkg/types" 19 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 20 "sigs.k8s.io/yaml" 21 ) 22 23 const ( 24 yamlSep = "---\n" 25 26 caCertSecretKey = "cacrt" 27 ) 28 29 // Create or update a secret that contains Kubernetes resource YAML which will be applied 30 // on the managed cluster to create resources needed there. The YAML has multiple Kubernetes 31 // resources separated by 3 hyphens ( --- ), so that applying the YAML will create/update multiple 32 // resources at once. This YAML is stored in the Verrazzano manifest secret. 33 // This function returns a boolean and an error, where the boolean is true if the VMC was created by 34 // Verrazzano and the Rancher cluster ID has not yet been set in the status. This allows the 35 // calling function to requeue and process the VMC again before marking the VMC ready. 36 func (r *VerrazzanoManagedClusterReconciler) syncManifestSecret(ctx context.Context, vmc *clusterapi.VerrazzanoManagedCluster) (bool, error) { 37 // Builder used to build up the full YAML 38 // For each secret, generate the YAML and append to the full YAML which contains multiple resources 39 var sb = strings.Builder{} 40 41 // add agent secret YAML 42 agentYaml, err := r.getSecretAsYaml(GetAgentSecretName(vmc.Name), vmc.Namespace, 43 constants.MCAgentSecret, constants.VerrazzanoSystemNamespace) 44 if err != nil { 45 return false, err 46 } 47 sb.Write([]byte(yamlSep)) 48 sb.Write(agentYaml) 49 50 // add registration secret YAML 51 regYaml, err := r.getSecretAsYaml(GetRegistrationSecretName(vmc.Name), vmc.Namespace, 52 constants.MCRegistrationSecret, constants.VerrazzanoSystemNamespace) 53 if err != nil { 54 return false, err 55 } 56 sb.Write([]byte(yamlSep)) 57 sb.Write(regYaml) 58 59 // if we created the VMC from a Rancher cluster, then wait for the cluster id to be populated in the status before we 60 // attempt to fetch the registration manifest YAML 61 vzVMCWaitingForClusterID := false 62 63 if vmc.Labels != nil && vmc.Labels[rancher.CreatedByLabel] == rancher.CreatedByVerrazzano && len(vmc.Status.RancherRegistration.ClusterID) == 0 { 64 r.log.Progressf("Waiting for Verrazzano-created VMC named %s to have a cluster id in the status before attempting to fetch Rancher registration manifest", vmc.Name) 65 vzVMCWaitingForClusterID = true 66 } else { 67 // register the cluster with Rancher - the cluster will show as "pending" until the 68 // Rancher YAML is applied on the managed cluster 69 // NOTE: If this errors we log it and do not fail the reconcile 70 var clusterID string 71 rc, err := rancherutil.NewAdminRancherConfig(r.Client, r.RancherIngressHost, r.log) 72 if err != nil { 73 msg := "Failed to create Rancher API client" 74 r.log.Infof("Unable to connect to Rancher API on admin cluster, manifest secret will not contain Rancher YAML: %v", err) 75 r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationFailed, "", msg) 76 } else { 77 var rancherYAML string 78 rancherYAML, clusterID, err = RegisterManagedClusterWithRancher(rc, vmc.Name, vmc.Status.RancherRegistration.ClusterID, r.log) 79 if err != nil { 80 msg := "Failed to register managed cluster with Rancher" 81 // Even if there was a failure, if the cluster id was retrieved and is currently empty 82 // on the VMC, populate it during status update 83 r.log.Info("Failed to register managed cluster, manifest secret will not contain Rancher YAML") 84 r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationFailed, clusterID, msg) 85 } else if len(rancherYAML) == 0 { 86 // we successfully called the Rancher API but for some reason the returned registration manifest YAML is empty, 87 // set the status on the VMC and return an error so we reconcile again 88 r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationFailed, clusterID, "Empty Rancher manifest YAML") 89 msg := fmt.Sprintf("Failed retrieving Rancher manifest, YAML is an empty string for cluster ID %s", clusterID) 90 r.log.Infof(msg) 91 return vzVMCWaitingForClusterID, r.log.ErrorNewErr(msg) 92 } else { 93 msg := fmt.Sprintf("Registration of managed cluster completed successfully for cluster %s with ID %s", vmc.Name, clusterID) 94 r.log.Once(msg) 95 r.updateRancherStatus(ctx, vmc, clusterapi.RegistrationCompleted, clusterID, msg) 96 sb.WriteString(rancherYAML) 97 } 98 } 99 } 100 101 // create/update the manifest secret with the YAML 102 _, err = r.createOrUpdateManifestSecret(vmc, sb.String()) 103 if err != nil { 104 return vzVMCWaitingForClusterID, err 105 } 106 107 // Save the ClusterRegistrationSecret name in the VMC 108 vmc.Spec.ManagedClusterManifestSecret = GetManifestSecretName(vmc.Name) 109 110 // finally, update the VMC 111 err = r.Update(context.TODO(), vmc) 112 if err != nil { 113 return vzVMCWaitingForClusterID, err 114 } 115 116 return vzVMCWaitingForClusterID, nil 117 } 118 119 // syncCACertSecret gets the CA cert from the managed cluster (if the cluster is active) and creates 120 // or updates the CA cert secret. If the secret is created, it also updates the VMC with the secret 121 // name. This function returns true if the sync was completed, false if it was not needed or not 122 // completed, and any error that occurred 123 func (r *VerrazzanoManagedClusterReconciler) syncCACertSecret(vmc *clusterapi.VerrazzanoManagedCluster) (bool, error) { 124 clusterID := vmc.Status.RancherRegistration.ClusterID 125 if len(clusterID) == 0 { 126 return false, nil 127 } 128 if len(vmc.Spec.CASecret) > 0 { 129 return false, nil 130 } 131 rc, err := rancherutil.NewAdminRancherConfig(r.Client, r.RancherIngressHost, r.log) 132 if err != nil { 133 return false, err 134 } 135 if rc == nil { 136 return false, nil 137 } 138 139 isActive, err := isManagedClusterActiveInRancher(rc, clusterID, r.log) 140 if err != nil { 141 return false, err 142 } 143 if !isActive { 144 r.log.Progressf("Waiting for managed cluster with id %s to become active before fetching CA cert", clusterID) 145 return false, nil 146 } 147 148 caCert, err := getCACertFromManagedCluster(rc, clusterID, r.log) 149 if err != nil { 150 return false, err 151 } 152 153 if len(caCert) > 0 { 154 caSecretName := getCASecretName(vmc.Name) 155 r.log.Infof("Retrieved CA cert from managed cluster with id %s, creating/updating secret %s", clusterID, caSecretName) 156 if _, err := r.createOrUpdateCASecret(vmc, caCert); err != nil { 157 return false, err 158 } 159 if len(caSecretName) > 0 { 160 vmc.Spec.CASecret = caSecretName 161 // update the VMC with ca secret name 162 r.log.Infof("Updating VMC %s with managed cluster CA secret %s", vmc.Name, caSecretName) 163 // Replace the VMC in the update call with a copy 164 // That way the existing VMC status updates do not get overwritten by the update 165 updateVMC := vmc.DeepCopy() 166 err = r.Update(context.TODO(), updateVMC) 167 if err != nil { 168 return false, err 169 } 170 return true, nil 171 } 172 } 173 174 return false, nil 175 } 176 177 // Update the Rancher registration status 178 func (r *VerrazzanoManagedClusterReconciler) updateRancherStatus(ctx context.Context, vmc *clusterapi.VerrazzanoManagedCluster, status clusterapi.RancherRegistrationStatus, rancherClusterID string, message string) { 179 // Skip the update if the status has not changed 180 if vmc.Status.RancherRegistration.Status == status && 181 vmc.Status.RancherRegistration.Message == message && 182 vmc.Status.RancherRegistration.ClusterID == rancherClusterID { 183 return 184 } 185 vmc.Status.RancherRegistration.Status = status 186 // don't wipe out existing cluster id with empty string 187 if rancherClusterID != "" { 188 vmc.Status.RancherRegistration.ClusterID = rancherClusterID 189 } 190 vmc.Status.RancherRegistration.Message = message 191 192 // Fetch the existing VMC to avoid conflicts in the status update 193 existingVMC := &clusterapi.VerrazzanoManagedCluster{} 194 err := r.Get(context.TODO(), types.NamespacedName{Namespace: vmc.Namespace, Name: vmc.Name}, existingVMC) 195 if err != nil { 196 r.log.Errorf("Failed to get the existing VMC %s from the cluster: %v", vmc.Name, err) 197 } 198 existingVMC.Status.RancherRegistration = vmc.Status.RancherRegistration 199 200 err = r.Status().Update(ctx, existingVMC) 201 if err != nil { 202 r.log.Errorf("Failed to update Rancher registration status for VMC %s: %v", vmc.Name, err) 203 } 204 } 205 206 // Create or update the manifest secret 207 func (r *VerrazzanoManagedClusterReconciler) createOrUpdateManifestSecret(vmc *clusterapi.VerrazzanoManagedCluster, yamlData string) (controllerutil.OperationResult, error) { 208 var secret corev1.Secret 209 secret.Namespace = vmc.Namespace 210 secret.Name = GetManifestSecretName(vmc.Name) 211 212 return controllerutil.CreateOrUpdate(context.TODO(), r.Client, &secret, func() error { 213 r.mutateManifestSecret(&secret, yamlData) 214 // This SetControllerReference call will trigger garbage collection i.e. the secret 215 // will automatically get deleted when the VerrazzanoManagedCluster is deleted 216 return controllerutil.SetControllerReference(vmc, &secret, r.Scheme) 217 }) 218 } 219 220 // Mutate the secret, setting the yaml data 221 func (r *VerrazzanoManagedClusterReconciler) mutateManifestSecret(secret *corev1.Secret, yamlData string) { 222 secret.Type = corev1.SecretTypeOpaque 223 secret.Data = map[string][]byte{ 224 constants2.YamlKey: []byte(yamlData), 225 } 226 } 227 228 // createOrUpdateCASecret creates or updates the secret containing the managed cluster CA cert 229 func (r *VerrazzanoManagedClusterReconciler) createOrUpdateCASecret(vmc *clusterapi.VerrazzanoManagedCluster, caCert string) (controllerutil.OperationResult, error) { 230 var secret corev1.Secret 231 secret.Namespace = vmc.Namespace 232 secret.Name = getCASecretName(vmc.Name) 233 234 return controllerutil.CreateOrUpdate(context.TODO(), r.Client, &secret, func() error { 235 r.mutateCASecret(&secret, caCert) 236 // This SetControllerReference call will trigger garbage collection i.e. the secret 237 // will automatically get deleted when the VerrazzanoManagedCluster is deleted 238 return controllerutil.SetControllerReference(vmc, &secret, r.Scheme) 239 }) 240 } 241 242 // mutateCASecret mutates the CA secret, setting the CA cert data 243 func (r *VerrazzanoManagedClusterReconciler) mutateCASecret(secret *corev1.Secret, caCert string) { 244 secret.Type = corev1.SecretTypeOpaque 245 secret.Data = map[string][]byte{ 246 caCertSecretKey: []byte(caCert), 247 } 248 } 249 250 // Get the specified secret then convert to YAML. 251 func (r *VerrazzanoManagedClusterReconciler) getSecretAsYaml(name string, namespace string, targetName string, targetNamespace string) (yamlData []byte, err error) { 252 var secret corev1.Secret 253 secretNsn := types.NamespacedName{ 254 Namespace: namespace, 255 Name: name, 256 } 257 if err := r.Get(context.TODO(), secretNsn, &secret); err != nil { 258 return []byte(""), fmt.Errorf("Failed to fetch the service account secret %s/%s, %v", namespace, name, err) 259 } 260 // Create a new ObjectMeta with target name and namespace 261 secret.ObjectMeta = metav1.ObjectMeta{ 262 Namespace: targetNamespace, 263 Name: targetName, 264 } 265 yamlData, err = yaml.Marshal(secret) 266 return yamlData, err 267 } 268 269 // GetManifestSecretName returns the manifest secret name 270 func GetManifestSecretName(vmcName string) string { 271 const manifestSecretSuffix = "-manifest" 272 return generateManagedResourceName(vmcName) + manifestSecretSuffix 273 } 274 275 // getCASecretName returns the CA secret name 276 func getCASecretName(vmcName string) string { 277 return "ca-secret-" + vmcName 278 }