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  }