github.com/verrazzano/verrazzano@v1.7.1/application-operator/mcagent/mcagent_appconfig.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  	oamv1alpha2 "github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
    12  	clustersv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/clusters/v1alpha1"
    13  	"github.com/verrazzano/verrazzano/application-operator/constants"
    14  	"github.com/verrazzano/verrazzano/application-operator/controllers/clusters"
    15  	vzconst "github.com/verrazzano/verrazzano/pkg/constants"
    16  	vzstring "github.com/verrazzano/verrazzano/pkg/string"
    17  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/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 MultiClusterApplicationConfiguration objects to the local cluster
    25  func (s *Syncer) syncMCApplicationConfigurationObjects(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  	for _, mcAppConfig := range allAdminMCAppConfigs.Items {
    37  		if s.isThisCluster(mcAppConfig.Spec.Placement) {
    38  			// Synchronize the components referenced by the application
    39  			err := s.syncComponentList(mcAppConfig)
    40  			if err != nil {
    41  				s.Log.Errorw(fmt.Sprintf("Failed syncing components referenced by object: %v", err),
    42  					"MultiClusterApplicationConfiguration",
    43  					types.NamespacedName{Namespace: mcAppConfig.Namespace, Name: mcAppConfig.Name})
    44  			}
    45  			// Synchronize the MultiClusterApplicationConfiguration even if there were errors
    46  			// handling the application components.  For compatibility with v1.0.0 it is valid
    47  			// for none of the OAM Components to be found because they may all be wrapped in
    48  			// an MultiClusterComponent resource.
    49  			_, err = s.createOrUpdateMCAppConfig(mcAppConfig)
    50  			if err != nil {
    51  				s.Log.Errorw(fmt.Sprintf("Failed syncing object: %c", err),
    52  					"MultiClusterApplicationConfiguration",
    53  					types.NamespacedName{Namespace: mcAppConfig.Namespace, Name: mcAppConfig.Name})
    54  			}
    55  		}
    56  	}
    57  
    58  	// Delete orphaned MultiClusterApplicationConfiguration resources.
    59  	// Get the list of MultiClusterApplicationConfiguration resources on the
    60  	// local cluster and compare to the list received from the admin cluster.
    61  	// The admin cluster is the source of truth.
    62  	allLocalMCAppConfigs := clustersv1alpha1.MultiClusterApplicationConfigurationList{}
    63  	err = s.LocalClient.List(s.Context, &allLocalMCAppConfigs, listOptions)
    64  	if err != nil {
    65  		s.Log.Errorf("Failed to list MultiClusterApplicationConfiguration on local cluster: %v", err)
    66  		return nil
    67  	}
    68  	for i, mcAppConfig := range allLocalMCAppConfigs.Items {
    69  		// Delete each MultiClusterApplicationConfiguration object that is not on the admin cluster or no longer placed on this cluster
    70  		if !s.appConfigPlacedOnCluster(&allAdminMCAppConfigs, mcAppConfig.Name, mcAppConfig.Namespace) {
    71  			err := s.LocalClient.Delete(s.Context, &allLocalMCAppConfigs.Items[i])
    72  			if err != nil {
    73  				s.Log.Errorf("Failed to delete MultiClusterApplicationConfiguration with name %q in namespace %q: %v", mcAppConfig.Name, mcAppConfig.Namespace, err)
    74  			}
    75  		}
    76  	}
    77  
    78  	// Delete OAM components no longer associated with any MultiClusterApplicationConfiguration
    79  	err = s.deleteOrphanedComponents(namespace)
    80  	if err != nil {
    81  		s.Log.Errorf("Failed deleting orphaned OAM Components in namespace %q: %v", namespace, err)
    82  	}
    83  
    84  	return nil
    85  }
    86  
    87  func (s *Syncer) createOrUpdateMCAppConfig(mcAppConfig clustersv1alpha1.MultiClusterApplicationConfiguration) (controllerutil.OperationResult, error) {
    88  	var mcAppConfigNew clustersv1alpha1.MultiClusterApplicationConfiguration
    89  	mcAppConfigNew.Namespace = mcAppConfig.Namespace
    90  	mcAppConfigNew.Name = mcAppConfig.Name
    91  
    92  	// Create or update on the local cluster
    93  	return controllerutil.CreateOrUpdate(s.Context, s.LocalClient, &mcAppConfigNew, func() error {
    94  		mutateMCAppConfig(mcAppConfig, &mcAppConfigNew)
    95  		return nil
    96  	})
    97  }
    98  
    99  func mutateMCAppConfig(mcAppConfig clustersv1alpha1.MultiClusterApplicationConfiguration, mcAppConfigNew *clustersv1alpha1.MultiClusterApplicationConfiguration) {
   100  	mcAppConfigNew.Spec.Placement = mcAppConfig.Spec.Placement
   101  	mcAppConfigNew.Spec.Template = mcAppConfig.Spec.Template
   102  	mcAppConfigNew.Labels = mcAppConfig.Labels
   103  	// Mark the MC app config we synced from Admin cluster with verrazzano-managed=true, to
   104  	// distinguish from any (though unlikely) that the user might have created directly
   105  	if mcAppConfigNew.Labels == nil {
   106  		mcAppConfigNew.Labels = map[string]string{}
   107  	}
   108  	mcAppConfigNew.Labels[vzconst.VerrazzanoManagedLabelKey] = constants.LabelVerrazzanoManagedDefault
   109  }
   110  
   111  // appConfigPlacedOnCluster returns boolean indicating if the list contains the object with the specified name and namespace and the placement
   112  // includes the local cluster
   113  func (s *Syncer) appConfigPlacedOnCluster(mcAdminList *clustersv1alpha1.MultiClusterApplicationConfigurationList, name string, namespace string) bool {
   114  	for _, item := range mcAdminList.Items {
   115  		if item.Name == name && item.Namespace == namespace {
   116  			return s.isThisCluster(item.Spec.Placement)
   117  		}
   118  	}
   119  	return false
   120  }
   121  
   122  func (s *Syncer) updateMultiClusterAppConfigStatus(name types.NamespacedName, newCond clustersv1alpha1.Condition, newClusterStatus clustersv1alpha1.ClusterLevelStatus) error {
   123  	var fetched clustersv1alpha1.MultiClusterApplicationConfiguration
   124  	err := s.AdminClient.Get(s.Context, name, &fetched)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	fetched.Status.Conditions = append(fetched.Status.Conditions, newCond)
   129  	clusters.SetClusterLevelStatus(&fetched.Status, newClusterStatus)
   130  	return s.AdminClient.Status().Update(s.Context, &fetched)
   131  }
   132  
   133  // syncComponentList - Synchronize the list of OAM Components contained in the MultiClusterApplicationConfiguration
   134  func (s *Syncer) syncComponentList(mcAppConfig clustersv1alpha1.MultiClusterApplicationConfiguration) error {
   135  	var errorStrings []string
   136  
   137  	// Loop through the component list and get them one at a time.
   138  	for _, component := range mcAppConfig.Spec.Template.Spec.Components {
   139  		objectKey := types.NamespacedName{Name: component.ComponentName, Namespace: mcAppConfig.Namespace}
   140  		oamComp := &oamv1alpha2.Component{}
   141  		err := s.AdminClient.Get(s.Context, objectKey, oamComp)
   142  		if err != nil {
   143  			// If the OAM component object is not found then we check if the MultiClusterComponent object exists.
   144  			if apierrors.IsNotFound(err) {
   145  				mcComp := &clustersv1alpha1.MultiClusterComponent{}
   146  				errmc := s.LocalClient.Get(s.Context, objectKey, mcComp)
   147  				// Return the OAM component not found error if we fail to get the MultiClusterComponent
   148  				// with the same name.
   149  				if errmc != nil {
   150  					return err
   151  				}
   152  				// MulticlusterComponent object found so nothing to do
   153  				continue
   154  			} else {
   155  				return err
   156  			}
   157  		}
   158  		_, err = s.createOrUpdateComponent(*oamComp, mcAppConfig.Name)
   159  		if err != nil {
   160  			errorStrings = append(errorStrings, err.Error())
   161  		}
   162  	}
   163  
   164  	// Check if any errors were collected while processing the list
   165  	if len(errorStrings) > 0 {
   166  		return fmt.Errorf(strings.Join(errorStrings, "\n"))
   167  	}
   168  	return nil
   169  }
   170  
   171  // createOrUpdateComponent - create or update an OAM Component
   172  func (s *Syncer) createOrUpdateComponent(srcComp oamv1alpha2.Component, mcAppConfigName string) (controllerutil.OperationResult, error) {
   173  	var oamComp oamv1alpha2.Component
   174  	oamComp.Namespace = srcComp.Namespace
   175  	oamComp.Name = srcComp.Name
   176  
   177  	return controllerutil.CreateOrUpdate(s.Context, s.LocalClient, &oamComp, func() error {
   178  		s.mutateComponent(s.ManagedClusterName, mcAppConfigName, srcComp, &oamComp)
   179  		return nil
   180  	})
   181  }
   182  
   183  // mutateComponent mutates the OAM component to reflect the contents of the parent MultiClusterComponent
   184  func (s *Syncer) mutateComponent(managedClusterName string, mcAppConfigName string, component oamv1alpha2.Component, componentNew *oamv1alpha2.Component) {
   185  	// Initialize the labels field
   186  	if componentNew.Labels == nil {
   187  		componentNew.Labels = make(map[string]string)
   188  		if component.Labels != nil {
   189  			componentNew.Labels = component.Labels
   190  		}
   191  	}
   192  
   193  	// Add the name of this MultiClusterApplicationConfiguration to the label list
   194  	componentNew.Labels[mcAppConfigsLabel] = vzstring.AppendToCommaSeparatedString(componentNew.Labels[mcAppConfigsLabel], mcAppConfigName)
   195  
   196  	componentNew.Labels[managedClusterLabel] = managedClusterName
   197  	// Mark the component synced from Admin cluster with verrazzano-managed=true, to distinguish
   198  	// those directly created by user on managed cluster
   199  	componentNew.Labels[vzconst.VerrazzanoManagedLabelKey] = constants.LabelVerrazzanoManagedDefault
   200  	componentNew.Spec = component.Spec
   201  	componentNew.Annotations = component.Annotations
   202  }
   203  
   204  // deleteOrphanedComponents - delete OAM components that are no longer associated with any MultiClusterApplicationConfigurations.
   205  // Also update the contents of the mcAppConfigsLabel for a component if the list of applications it is shared by has changed.
   206  func (s *Syncer) deleteOrphanedComponents(namespace string) error {
   207  	// Only process OAM components that were synced to the local system
   208  	labels := &metav1.LabelSelector{
   209  		MatchLabels: map[string]string{
   210  			managedClusterLabel: s.ManagedClusterName,
   211  		},
   212  	}
   213  	selector, err := metav1.LabelSelectorAsSelector(labels)
   214  	if err != nil {
   215  		return err
   216  	}
   217  	listOptions := &client.ListOptions{Namespace: namespace, LabelSelector: selector}
   218  	oamCompList := &oamv1alpha2.ComponentList{}
   219  	err = s.LocalClient.List(s.Context, oamCompList, listOptions)
   220  	if err != nil {
   221  		return err
   222  	}
   223  
   224  	// Nothing to do if no OAM components found
   225  	if len(oamCompList.Items) == 0 {
   226  		return nil
   227  	}
   228  
   229  	// Get the list of MultiClusterApplicationConfiguration objects
   230  	listOptions2 := &client.ListOptions{Namespace: namespace}
   231  	mcAppConfigList := clustersv1alpha1.MultiClusterApplicationConfigurationList{}
   232  	err = s.LocalClient.List(s.Context, &mcAppConfigList, listOptions2)
   233  	if err != nil {
   234  		return err
   235  	}
   236  
   237  	// Process the list of OAM Components checking to see if they are part of any MultiClusterApplicationConfiguration
   238  	for i, oamComp := range oamCompList.Items {
   239  		// Don't delete OAM component objects that have a MultiClusterComponent objects. These will be deleted
   240  		// when syncing MultiClusterComponent objects.
   241  		mcComp := &clustersv1alpha1.MultiClusterComponent{}
   242  		objectKey := types.NamespacedName{Name: oamComp.Name, Namespace: namespace}
   243  		err = s.LocalClient.Get(s.Context, objectKey, mcComp)
   244  		if err == nil {
   245  			continue
   246  		}
   247  		var actualAppConfigs []string
   248  		// Loop through the MultiClusterApplicationConfiguration objects checking for a reference
   249  		for _, mcAppConfig := range mcAppConfigList.Items {
   250  			for _, component := range mcAppConfig.Spec.Template.Spec.Components {
   251  				// If we get a match, maintain a list of applications this OAM component is shared by
   252  				if component.ComponentName == oamComp.Name {
   253  					actualAppConfigs = append(actualAppConfigs, mcAppConfig.Name)
   254  				}
   255  			}
   256  		}
   257  		if len(actualAppConfigs) == 0 {
   258  			// Delete the orphaned OAM Component
   259  			s.Log.Debugf("Deleting orphaned OAM Component %s in namespace %s", oamComp.Name, oamComp.Namespace)
   260  			err = s.LocalClient.Delete(s.Context, &oamCompList.Items[i])
   261  			if err != nil {
   262  				return err
   263  			}
   264  		} else {
   265  			// Has the list of applications this component is associated with changed?  If so update the label.
   266  			if !reflect.DeepEqual(strings.Split(oamComp.Labels[mcAppConfigsLabel], ","), actualAppConfigs) {
   267  				oamComp.Labels[mcAppConfigsLabel] = strings.Join(actualAppConfigs, ",")
   268  				err = s.LocalClient.Update(s.Context, &oamCompList.Items[i])
   269  				if err != nil {
   270  					return err
   271  				}
   272  			}
   273  		}
   274  	}
   275  
   276  	return nil
   277  }