github.com/verrazzano/verrazzano@v1.7.0/application-operator/mcagent/mcagent_k8ssecret.go (about)

     1  // Copyright (c) 2021, 2022, 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 mcagent
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"strings"
    10  
    11  	"github.com/verrazzano/verrazzano/application-operator/constants"
    12  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    13  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    14  
    15  	vzstring "github.com/verrazzano/verrazzano/pkg/string"
    16  
    17  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    18  	corev1 "k8s.io/api/core/v1"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"sigs.k8s.io/controller-runtime/pkg/client"
    21  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    22  )
    23  
    24  // Synchronize Secret objects to the local cluster
    25  func (s *Syncer) syncSecretObjects(namespace string) error {
    26  	// Get all the MultiClusterApplicationConfiguration objects from the admin cluster
    27  	allAdminMCAppConfigs := clustersv1alpha1.MultiClusterApplicationConfigurationList{}
    28  	listOptions := &client.ListOptions{Namespace: namespace}
    29  	err := s.AdminClient.List(s.Context, &allAdminMCAppConfigs, listOptions)
    30  	// When placements are changed a forbidden error can be returned.  In this case,
    31  	// we want to fall through and delete orphaned resources.
    32  	if err != nil && !apierrors.IsNotFound(err) && !apierrors.IsForbidden(err) {
    33  		return err
    34  	}
    35  
    36  	// Write each of the secrets that are targeted for the local cluster
    37  	for _, mcAppConfig := range allAdminMCAppConfigs.Items {
    38  		if s.isThisCluster(mcAppConfig.Spec.Placement) {
    39  			for _, adminSecret := range mcAppConfig.Spec.Secrets {
    40  				secret := corev1.Secret{}
    41  				namespacedName := types.NamespacedName{Name: adminSecret, Namespace: namespace}
    42  				err := s.AdminClient.Get(s.Context, namespacedName, &secret)
    43  				if err != nil {
    44  					return err
    45  				}
    46  				_, err = s.createOrUpdateSecret(secret, mcAppConfig.Name)
    47  				if err != nil {
    48  					s.Log.Errorw(fmt.Sprintf("Failed syncing object: %v", err),
    49  						"Secret",
    50  						types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name})
    51  				}
    52  			}
    53  		}
    54  	}
    55  
    56  	// Cleanup orphaned or no longer placed Secret resources.
    57  	// Get the list of Secret resources on the local cluster and compare to the list received from the admin cluster.
    58  	// The admin cluster is the source of truth.
    59  	allLocalSecrets := corev1.SecretList{}
    60  	listOptions = &client.ListOptions{Namespace: namespace}
    61  	err = s.LocalClient.List(s.Context, &allLocalSecrets, listOptions)
    62  	if err != nil {
    63  		s.Log.Errorf("Failed to list Secrets on local cluster: %v", err)
    64  		return nil
    65  	}
    66  	for i, secret := range allLocalSecrets.Items {
    67  		appConfigs, found := secret.Labels[mcAppConfigsLabel]
    68  		// Only look at the secrets we have synced
    69  		if !found {
    70  			continue
    71  		}
    72  		// Delete Secret object if it is no longer placed on this local cluster
    73  		if !s.k8sSecretPlacedOnCluster(secret, &allAdminMCAppConfigs) {
    74  			err := s.LocalClient.Delete(s.Context, &allLocalSecrets.Items[i])
    75  			if err != nil {
    76  				s.Log.Errorf("Failed to delete Secret with name %s and namespace %s: %v", secret.Name, secret.Namespace, err)
    77  			}
    78  		} else {
    79  			// Update the secrets label if the secret was shared across app configs and one of the app configs
    80  			// was deleted.
    81  			secretAppConfigs := strings.Split(appConfigs, ",")
    82  			var actualAppConfigs []string
    83  			for _, mcAppConfig := range allAdminMCAppConfigs.Items {
    84  				for _, cluster := range mcAppConfig.Spec.Placement.Clusters {
    85  					if cluster.Name == s.ManagedClusterName {
    86  						for _, appConfigSecret := range mcAppConfig.Spec.Secrets {
    87  							// Save the name of the MultiClusterApplicationConfiguration if we have a secret match
    88  							if appConfigSecret == secret.Name {
    89  								actualAppConfigs = append(actualAppConfigs, mcAppConfig.Name)
    90  							}
    91  						}
    92  					}
    93  				}
    94  			}
    95  			if !reflect.DeepEqual(secretAppConfigs, actualAppConfigs) {
    96  				secret.Labels[mcAppConfigsLabel] = strings.Join(actualAppConfigs, ",")
    97  				err := s.LocalClient.Update(s.Context, &allLocalSecrets.Items[i])
    98  				if err != nil {
    99  					s.Log.Errorf("Failed to update Secret with name %s and namespace %s: %v", secret.Name, secret.Namespace, err)
   100  				}
   101  			}
   102  		}
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  // Create or update a Secret
   109  func (s *Syncer) createOrUpdateSecret(secret corev1.Secret, mcAppConfigName string) (controllerutil.OperationResult, error) {
   110  	var secretNew corev1.Secret
   111  	secretNew.Namespace = secret.Namespace
   112  	secretNew.Name = secret.Name
   113  	// Create or update on the local cluster
   114  	return controllerutil.CreateOrUpdate(s.Context, s.LocalClient, &secretNew, func() error {
   115  		mutateSecret(s.ManagedClusterName, mcAppConfigName, secret, &secretNew)
   116  		return nil
   117  	})
   118  }
   119  
   120  // mutateSecret mutates the Secret to reflect the contents of the parent Secret
   121  func mutateSecret(managedClusterName string, mcAppConfigName string, secret corev1.Secret, secretNew *corev1.Secret) {
   122  	if secretNew.Labels == nil {
   123  		secretNew.Labels = make(map[string]string)
   124  		if secret.Labels != nil {
   125  			secretNew.Labels = secret.Labels
   126  		}
   127  	}
   128  
   129  	secretNew.Labels[mcAppConfigsLabel] = vzstring.AppendToCommaSeparatedString(secretNew.Labels[mcAppConfigsLabel], mcAppConfigName)
   130  	secretNew.Labels[managedClusterLabel] = managedClusterName
   131  	// Mark the secret synced from Admin cluster with verrazzano-managed=true, to distinguish
   132  	// those directly created by user on managed cluster
   133  	secretNew.Labels[vzconst.VerrazzanoManagedLabelKey] = constants.LabelVerrazzanoManagedDefault
   134  	secretNew.Annotations = secret.Annotations
   135  	secretNew.Type = secret.Type
   136  	secretNew.Immutable = secret.Immutable
   137  	secretNew.Data = secret.Data
   138  	secretNew.StringData = secret.StringData
   139  }
   140  
   141  // k8sSecretPlacedOnCluster returns boolean indicating if the secret is placed on the local cluster
   142  func (s *Syncer) k8sSecretPlacedOnCluster(secret corev1.Secret, allAdminMCAppConfigs *clustersv1alpha1.MultiClusterApplicationConfigurationList) bool {
   143  	for _, mcAppConfig := range allAdminMCAppConfigs.Items {
   144  		// Both a matching application configuration label and a matching cluster label be found for the
   145  		// secret to be placed on the local cluster.
   146  		if vzstring.CommaSeparatedStringContains(secret.Labels[mcAppConfigsLabel], mcAppConfig.Name) {
   147  			for _, cluster := range mcAppConfig.Spec.Placement.Clusters {
   148  				if cluster.Name == secret.Labels[managedClusterLabel] {
   149  					return true
   150  				}
   151  			}
   152  		}
   153  	}
   154  
   155  	return false
   156  }