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 }