github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/loggingtrait/loggingtrait_controller.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 loggingtrait
     5  
     6  import (
     7  	"context"
     8  	errors "errors"
     9  	"fmt"
    10  	"github.com/verrazzano/verrazzano/application-operator/controllers/clusters"
    11  	"github.com/verrazzano/verrazzano/pkg/constants"
    12  	vzlogInit "github.com/verrazzano/verrazzano/pkg/log"
    13  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    14  	"os"
    15  	"strings"
    16  
    17  	oamv1alpha1 "github.com/verrazzano/verrazzano/application-operator/apis/oam/v1alpha1"
    18  	vznav "github.com/verrazzano/verrazzano/application-operator/controllers/navigation"
    19  	"go.uber.org/zap"
    20  	corev1 "k8s.io/api/core/v1"
    21  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/types"
    26  	ctrl "sigs.k8s.io/controller-runtime"
    27  	"sigs.k8s.io/controller-runtime/pkg/client"
    28  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    29  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    30  )
    31  
    32  // Reconciler constants
    33  const (
    34  	loggingNamePart           = "logging-stdout"
    35  	errLoggingResource        = "cannot add logging sidecar to the resource"
    36  	configMapAPIVersion       = "v1"
    37  	configMapKind             = "ConfigMap"
    38  	loggingMountPath          = "/fluentd/etc/custom.conf"
    39  	loggingKey                = "custom.conf"
    40  	defaultMode         int32 = 400
    41  	controllerName            = "loggingtrait"
    42  )
    43  
    44  // LoggingTraitReconciler reconciles a LoggingTrait object
    45  type LoggingTraitReconciler struct {
    46  	client.Client
    47  	Log    *zap.SugaredLogger
    48  	Scheme *runtime.Scheme
    49  }
    50  
    51  // +kubebuilder:rbac:groups=oam.verrazzano.io,resources=loggingtraits,verbs=get;list;watch;create;update;patch;delete
    52  // +kubebuilder:rbac:groups=oam.verrazzano.io,resources=loggingtraits/status,verbs=get;update;patch
    53  // +kubebuilder:rbac:groups=core.oam.dev,resources=containerizedworkloads,verbs=get;list;
    54  // +kubebuilder:rbac:groups=core.oam.dev,resources=containerizedworkloads/status,verbs=get;
    55  // +kubebuilder:rbac:groups=core.oam.dev,resources=workloaddefinitions,verbs=get;list;watch
    56  // +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;update;patch;delete
    57  // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;update;patch;delete
    58  // +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;update;patch;delete
    59  // +kubebuilder:rbac:groups=apps,resources=pods,verbs=get;list;watch;update;patch;delete
    60  
    61  func (r *LoggingTraitReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    62  	if ctx == nil {
    63  		return ctrl.Result{}, errors.New("context cannot be nil")
    64  	}
    65  
    66  	// We do not want any resource to get reconciled if it is in namespace kube-system
    67  	// This is due to a bug found in OKE, it should not affect functionality of any vz operators
    68  	// If this is the case then return success
    69  	if req.Namespace == constants.KubeSystem {
    70  		log := zap.S().With(vzlogInit.FieldResourceNamespace, req.Namespace, vzlogInit.FieldResourceName, req.Name, vzlogInit.FieldController, controllerName)
    71  		log.Infof("Logging trait resource %v should not be reconciled in kube-system namespace, ignoring", req.NamespacedName)
    72  		return reconcile.Result{}, nil
    73  	}
    74  
    75  	var err error
    76  	var trait *oamv1alpha1.LoggingTrait
    77  	if trait, err = r.fetchTrait(ctx, req.NamespacedName, zap.S()); err != nil || trait == nil {
    78  		return clusters.IgnoreNotFoundWithLog(err, zap.S())
    79  	}
    80  	log, err := clusters.GetResourceLogger("loggingtrait", req.NamespacedName, trait)
    81  	if err != nil {
    82  		zap.S().Errorf("Failed to create controller logger for logging trait resource: %v", err)
    83  		return clusters.NewRequeueWithDelay(), nil
    84  	}
    85  	log.Oncef("Reconciling logging trait resource %v, generation %v", req.NamespacedName, trait.Generation)
    86  
    87  	res, err := r.doReconcile(ctx, trait, log)
    88  	if clusters.ShouldRequeue(res) {
    89  		return res, nil
    90  	}
    91  	// Never return an error since it has already been logged and we don't want the
    92  	// controller runtime to log again (with stack trace).  Just re-queue if there is an error.
    93  	if err != nil {
    94  		return clusters.NewRequeueWithDelay(), nil
    95  	}
    96  
    97  	log.Oncef("Finished reconciling logging trait %v", req.NamespacedName)
    98  
    99  	return ctrl.Result{}, nil
   100  }
   101  
   102  // doReconcile performs the reconciliation operations for the logging trait
   103  func (r *LoggingTraitReconciler) doReconcile(ctx context.Context, trait *oamv1alpha1.LoggingTrait, log vzlog.VerrazzanoLogger) (ctrl.Result, error) {
   104  	if trait.DeletionTimestamp.IsZero() {
   105  		result, supported, err := r.reconcileTraitCreateOrUpdate(ctx, log, trait)
   106  		if err != nil {
   107  			return result, err
   108  		}
   109  		if !supported {
   110  			// If the workload kind is not supported then delete the trait
   111  			log.Debugf("Deleting trait %s because workload is not supported", trait.Name)
   112  
   113  			err = r.Client.Delete(context.TODO(), trait, &client.DeleteOptions{})
   114  
   115  		}
   116  		return result, err
   117  	}
   118  
   119  	return r.reconcileTraitDelete(ctx, log, trait)
   120  }
   121  
   122  // reconcileTraitDelete reconciles a logging trait that is being deleted.
   123  func (r *LoggingTraitReconciler) reconcileTraitDelete(ctx context.Context, log vzlog.VerrazzanoLogger, trait *oamv1alpha1.LoggingTrait) (ctrl.Result, error) {
   124  	// Retrieve the workload the trait is related to
   125  	workload, err := vznav.FetchWorkloadFromTrait(ctx, r, log, trait)
   126  	if err != nil || workload == nil {
   127  		return reconcile.Result{}, err
   128  	}
   129  	if workload.GetKind() == "VerrazzanoCoherenceWorkload" || workload.GetKind() == "VerrazzanoWebLogicWorkload" {
   130  		return reconcile.Result{}, nil
   131  	}
   132  
   133  	// Retrieve the child resources of the workload
   134  	resources, err := vznav.FetchWorkloadChildren(ctx, r, log, workload)
   135  	if err != nil {
   136  		log.Errorw(fmt.Sprintf("Failed to retrieve the workloads child resources: %v", err), "workload", workload.UnstructuredContent())
   137  	}
   138  
   139  	// If there are no child resources fallback to the workload
   140  	if len(resources) == 0 {
   141  		resources = append(resources, workload)
   142  	}
   143  
   144  	for _, resource := range resources {
   145  		isCombined := false
   146  		configMapName := loggingNamePart + "-" + resource.GetName() + "-" + strings.ToLower(resource.GetKind())
   147  
   148  		if ok, containersFieldPath := locateContainersField(resource); ok {
   149  			resourceContainers, ok, err := unstructured.NestedSlice(resource.Object, containersFieldPath...)
   150  			if !ok || err != nil {
   151  				log.Errorf("Failed to gather resource containers: %v", err)
   152  				return reconcile.Result{}, err
   153  			}
   154  
   155  			var image string
   156  			if len(trait.Spec.LoggingImage) != 0 {
   157  				image = trait.Spec.LoggingImage
   158  			} else {
   159  				image = os.Getenv("DEFAULT_FLUENTD_IMAGE")
   160  			}
   161  			envFluentd := &corev1.EnvVar{
   162  				Name:  "FLUENTD_CONF",
   163  				Value: "custom.conf",
   164  			}
   165  			loggingContainer := &corev1.Container{
   166  				Name:            loggingNamePart,
   167  				Image:           image,
   168  				ImagePullPolicy: corev1.PullPolicy(trait.Spec.ImagePullPolicy),
   169  				Env:             []corev1.EnvVar{*envFluentd},
   170  			}
   171  
   172  			repeatNo := 0
   173  			repeat := false
   174  			for i, resContainer := range resourceContainers {
   175  				if loggingContainer.Name == resContainer.(map[string]interface{})["name"] {
   176  					repeat = true
   177  					repeatNo = i
   178  					break
   179  				}
   180  			}
   181  			if repeat {
   182  				resourceContainers[repeatNo] = resourceContainers[len(resourceContainers)-1]
   183  				resourceContainers = resourceContainers[:len(resourceContainers)-1]
   184  			}
   185  			err = unstructured.SetNestedSlice(resource.Object, resourceContainers, containersFieldPath...)
   186  			if err != nil {
   187  				log.Errorf("Failed to set resource containers: %v", err)
   188  				return reconcile.Result{}, err
   189  			}
   190  
   191  			isCombined = true
   192  
   193  		}
   194  
   195  		if ok, volumesFieldPath := locateVolumesField(resource); ok {
   196  			resourceVolumes, ok, err := unstructured.NestedSlice(resource.Object, volumesFieldPath...)
   197  			if err != nil {
   198  				log.Errorf("Failed to gather resource volumes: %v", err)
   199  				return reconcile.Result{}, err
   200  			} else if !ok {
   201  				log.Debug("No volumes found")
   202  			}
   203  
   204  			loggingVolume := &corev1.Volume{
   205  				Name: configMapName,
   206  				VolumeSource: corev1.VolumeSource{
   207  					ConfigMap: &corev1.ConfigMapVolumeSource{
   208  						LocalObjectReference: corev1.LocalObjectReference{
   209  							Name: configMapName,
   210  						},
   211  						DefaultMode: func(mode int32) *int32 {
   212  							return &mode
   213  						}(defaultMode),
   214  					},
   215  				},
   216  			}
   217  
   218  			repeatNo := 0
   219  			repeat := false
   220  			for i, resVolume := range resourceVolumes {
   221  				if loggingVolume.Name == resVolume.(map[string]interface{})["name"] {
   222  					log.Debugw("Volume was discarded because of duplicate names", "volume name", loggingVolume.Name)
   223  					repeat = true
   224  					repeatNo = i
   225  					break
   226  				}
   227  			}
   228  			if repeat {
   229  				resourceVolumes[repeatNo] = resourceVolumes[len(resourceVolumes)-1]
   230  				resourceVolumes = resourceVolumes[:len(resourceVolumes)-1]
   231  			}
   232  
   233  			err = unstructured.SetNestedSlice(resource.Object, resourceVolumes, volumesFieldPath...)
   234  			if err != nil {
   235  				log.Errorf("Failed to set resource containers: %v", err)
   236  				return reconcile.Result{}, err
   237  			}
   238  
   239  			isCombined = true
   240  
   241  		}
   242  
   243  		if isCombined {
   244  			// make a copy of the resource spec since resource.Object will get overwritten in CreateOrUpdate
   245  			// if the resource exists
   246  			specCopy, _, err := unstructured.NestedFieldCopy(resource.Object, "spec")
   247  			if err != nil {
   248  				log.Errorf("Failed to make a copy of the spec: %v", err)
   249  				return reconcile.Result{}, err
   250  			}
   251  
   252  			_, err = controllerutil.CreateOrUpdate(ctx, r.Client, resource, func() error {
   253  				return unstructured.SetNestedField(resource.Object, specCopy, "spec")
   254  			})
   255  			if err != nil {
   256  				log.Errorf("Failed creating or updating resource: %v", err)
   257  				return reconcile.Result{}, err
   258  			}
   259  			log.Debugw("Successfully removed logging from resource", "resource GVK", resource.GroupVersionKind().String())
   260  		}
   261  
   262  		r.deleteLoggingConfigMap(ctx, trait, resource)
   263  
   264  	}
   265  
   266  	return reconcile.Result{}, nil
   267  }
   268  
   269  // fetchTrait attempts to get a trait given a namespaced name.
   270  // Will return nil for the trait and no error if the trait does not exist.
   271  func (r *LoggingTraitReconciler) fetchTrait(ctx context.Context, name types.NamespacedName, log *zap.SugaredLogger) (*oamv1alpha1.LoggingTrait, error) {
   272  	var trait oamv1alpha1.LoggingTrait
   273  	log.Debugw("Fetch trait", "trait", name)
   274  	if err := r.Get(ctx, name, &trait); err != nil {
   275  		if k8serrors.IsNotFound(err) {
   276  			log.Debug("Trait has been deleted")
   277  			return nil, nil
   278  		}
   279  		log.Debug("Failed to fetch trait")
   280  		return nil, err
   281  	}
   282  	return &trait, nil
   283  }
   284  
   285  func (r *LoggingTraitReconciler) reconcileTraitCreateOrUpdate(ctx context.Context, log vzlog.VerrazzanoLogger, trait *oamv1alpha1.LoggingTrait) (ctrl.Result, bool, error) {
   286  
   287  	// Retrieve the workload the trait is related to
   288  	workload, err := vznav.FetchWorkloadFromTrait(ctx, r, log, trait)
   289  	if err != nil || workload == nil {
   290  		return reconcile.Result{}, true, err
   291  	}
   292  	if workload.GetKind() == "VerrazzanoCoherenceWorkload" || workload.GetKind() == "VerrazzanoWebLogicWorkload" {
   293  		return reconcile.Result{}, true, nil
   294  	}
   295  	// Retrieve the child resources of the workload
   296  	resources, err := vznav.FetchWorkloadChildren(ctx, r, log, workload)
   297  	if err != nil {
   298  		log.Errorw(fmt.Sprintf("Failed to retrieve the workloads child resources: %v", err), "workload", workload.UnstructuredContent())
   299  	}
   300  
   301  	// If there are no child resources fallback to the workload
   302  	if len(resources) == 0 {
   303  		resources = append(resources, workload)
   304  	}
   305  
   306  	isFound := false
   307  	for _, resource := range resources {
   308  		isCombined := false
   309  		configMapName := loggingNamePart + "-" + resource.GetName() + "-" + strings.ToLower(resource.GetKind())
   310  
   311  		if ok, containersFieldPath := locateContainersField(resource); ok {
   312  			resourceContainers, ok, err := unstructured.NestedSlice(resource.Object, containersFieldPath...)
   313  			if !ok || err != nil {
   314  				log.Errorf("Failed to gather resource containers: %v", err)
   315  				return reconcile.Result{}, true, err
   316  			}
   317  			loggingVolumeMount := &corev1.VolumeMount{
   318  				MountPath: loggingMountPath,
   319  				Name:      configMapName,
   320  				SubPath:   loggingKey,
   321  				ReadOnly:  true,
   322  			}
   323  			uLoggingVolumeMount, err := struct2Unmarshal(loggingVolumeMount)
   324  			if err != nil {
   325  				log.Errorf("Failed to unmarshal a volumeMount for logging: %v", err)
   326  			}
   327  
   328  			var volumeMountFieldPath = []string{"volumeMounts"}
   329  			var resourceVolumeMounts []interface{}
   330  			for _, resContainer := range resourceContainers {
   331  				volumeMounts, ok, err := unstructured.NestedSlice(resContainer.(map[string]interface{}), volumeMountFieldPath...)
   332  				if err != nil {
   333  					log.Errorf("Failed to gather resource container volumeMounts: %v", err)
   334  					return reconcile.Result{}, true, err
   335  				} else if !ok {
   336  					log.Debug("No volumeMounts found")
   337  				}
   338  				resourceVolumeMounts = appendSliceOfInterface(resourceVolumeMounts, volumeMounts)
   339  
   340  			}
   341  			iVolumeMount := -1
   342  			for i, cVolumeMount := range resourceVolumeMounts {
   343  				if cVolumeMount.(map[string]interface{})["mountPath"] == uLoggingVolumeMount.Object["mountPath"] {
   344  					iVolumeMount = i
   345  				}
   346  			}
   347  			if iVolumeMount == -1 {
   348  				resourceVolumeMounts = append(resourceVolumeMounts, uLoggingVolumeMount.Object)
   349  			}
   350  			envFluentd := &corev1.EnvVar{
   351  				Name:  "FLUENTD_CONF",
   352  				Value: "custom.conf",
   353  			}
   354  			loggingContainer := &corev1.Container{
   355  				Name:            loggingNamePart,
   356  				Image:           trait.Spec.LoggingImage,
   357  				ImagePullPolicy: corev1.PullPolicy(trait.Spec.ImagePullPolicy),
   358  				Env:             []corev1.EnvVar{*envFluentd},
   359  			}
   360  
   361  			uLoggingContainer, err := struct2Unmarshal(loggingContainer)
   362  			if err != nil {
   363  				log.Errorf("Failed to unmarshal a container for logging: %v", err)
   364  			}
   365  
   366  			err = unstructured.SetNestedSlice(uLoggingContainer.Object, resourceVolumeMounts, volumeMountFieldPath...)
   367  			if err != nil {
   368  				log.Errorf("Failed to set container volumeMounts: %v", err)
   369  				return reconcile.Result{}, true, err
   370  			}
   371  
   372  			repeatNo := 0
   373  			repeat := false
   374  			for i, resContainer := range resourceContainers {
   375  				if loggingContainer.Name == resContainer.(map[string]interface{})["name"] {
   376  					repeat = true
   377  					repeatNo = i
   378  					break
   379  				}
   380  			}
   381  			if repeat {
   382  				resourceContainers[repeatNo] = uLoggingContainer.Object
   383  			} else {
   384  				resourceContainers = append(resourceContainers, uLoggingContainer.Object)
   385  			}
   386  
   387  			err = unstructured.SetNestedSlice(resource.Object, resourceContainers, containersFieldPath...)
   388  			if err != nil {
   389  				log.Errorf("Failed to set resource containers: %v", err)
   390  				return reconcile.Result{}, true, err
   391  			}
   392  
   393  			isCombined = true
   394  			isFound = true
   395  
   396  		}
   397  
   398  		if ok, volumesFieldPath := locateVolumesField(resource); ok {
   399  			resourceVolumes, ok, err := unstructured.NestedSlice(resource.Object, volumesFieldPath...)
   400  			if err != nil {
   401  				log.Errorf("Failed to gather resource volumes: %v", err)
   402  				return reconcile.Result{}, true, err
   403  			} else if !ok {
   404  				log.Debug("No volumes found")
   405  			}
   406  
   407  			loggingVolume := &corev1.Volume{
   408  				Name: configMapName,
   409  				VolumeSource: corev1.VolumeSource{
   410  					ConfigMap: &corev1.ConfigMapVolumeSource{
   411  						LocalObjectReference: corev1.LocalObjectReference{
   412  							Name: configMapName,
   413  						},
   414  						DefaultMode: func(mode int32) *int32 {
   415  							return &mode
   416  						}(defaultMode),
   417  					},
   418  				},
   419  			}
   420  			uLoggingVolume, err := struct2Unmarshal(loggingVolume)
   421  			if err != nil {
   422  				log.Errorf("Failed unmarshalling logging volume: %v", err)
   423  			}
   424  
   425  			repeatNo := 0
   426  			repeat := false
   427  			for i, resVolume := range resourceVolumes {
   428  				if loggingVolume.Name == resVolume.(map[string]interface{})["name"] {
   429  					log.Debugw("Volume was discarded because of duplicate names", "volume name", loggingVolume.Name)
   430  					repeat = true
   431  					repeatNo = i
   432  					break
   433  				}
   434  			}
   435  			if repeat {
   436  				resourceVolumes[repeatNo] = uLoggingVolume.Object
   437  			} else {
   438  				resourceVolumes = append(resourceVolumes, uLoggingVolume.Object)
   439  			}
   440  
   441  			err = unstructured.SetNestedSlice(resource.Object, resourceVolumes, volumesFieldPath...)
   442  			if err != nil {
   443  				log.Errorf("Failed to set resource volumes: %v", err)
   444  				return reconcile.Result{}, true, err
   445  			}
   446  
   447  			isFound = true
   448  			isCombined = true
   449  
   450  		}
   451  
   452  		if isCombined {
   453  			if isFound {
   454  
   455  				r.ensureLoggingConfigMapExists(ctx, trait, resource)
   456  			}
   457  			// make a copy of the resource spec since resource.Object will get overwritten in CreateOrUpdate
   458  			// if the resource exists
   459  			specCopy, _, err := unstructured.NestedFieldCopy(resource.Object, "spec")
   460  			if err != nil {
   461  				log.Errorf("Failed to make a copy of the spec: %v", err)
   462  				r.deleteLoggingConfigMap(ctx, trait, resource)
   463  				return reconcile.Result{}, true, err
   464  			}
   465  
   466  			_, err = controllerutil.CreateOrUpdate(ctx, r.Client, resource, func() error {
   467  				return unstructured.SetNestedField(resource.Object, specCopy, "spec")
   468  			})
   469  			if err != nil {
   470  				log.Errorf("Failed creating or updating resource: %v", err)
   471  				r.deleteLoggingConfigMap(ctx, trait, resource)
   472  				return reconcile.Result{}, true, err
   473  			}
   474  			log.Debugw("Successfully deploy logging to resource", "resource GVK", resource.GroupVersionKind().String())
   475  		}
   476  
   477  		if !isFound {
   478  			log.Debugw("Cannot locate any resource", "total resources", len(resources))
   479  			return reconcile.Result{}, false, fmt.Errorf(errLoggingResource)
   480  		}
   481  
   482  	}
   483  
   484  	return reconcile.Result{}, true, nil
   485  }
   486  
   487  // ensureLoggingConfigMapExists ensures that the FLUENTD configmap exists. If it already exists, there is nothing
   488  // to do. If it doesn't exist, create it.
   489  func (r *LoggingTraitReconciler) ensureLoggingConfigMapExists(ctx context.Context, trait *oamv1alpha1.LoggingTrait, resource *unstructured.Unstructured) error {
   490  	// check if configmap exists
   491  	configMapName := loggingNamePart + "-" + resource.GetName() + "-" + strings.ToLower(resource.GetKind())
   492  	configMapExists, err := resourceExists(ctx, r, configMapAPIVersion, configMapKind, configMapName, resource.GetNamespace())
   493  	if err != nil {
   494  		return err
   495  	}
   496  
   497  	if !configMapExists {
   498  		if err = r.Create(ctx, r.createLoggingConfigMap(trait, resource), &client.CreateOptions{}); err != nil {
   499  			return err
   500  		}
   501  	}
   502  	return err
   503  }
   504  
   505  // createLoggingConfigMap returns a configmap based on the logging trait
   506  func (r *LoggingTraitReconciler) createLoggingConfigMap(trait *oamv1alpha1.LoggingTrait, resource *unstructured.Unstructured) *corev1.ConfigMap {
   507  	configMapName := loggingNamePart + "-" + resource.GetName() + "-" + strings.ToLower(resource.GetKind())
   508  	data := make(map[string]string)
   509  	data["custom.conf"] = trait.Spec.LoggingConfig
   510  	configMap := &corev1.ConfigMap{
   511  		ObjectMeta: metav1.ObjectMeta{
   512  			Name:      configMapName,
   513  			Namespace: resource.GetNamespace(),
   514  			Labels:    resource.GetLabels(),
   515  		},
   516  		Data: data,
   517  	}
   518  	controllerutil.SetControllerReference(resource, configMap, r.Scheme)
   519  	return configMap
   520  }
   521  
   522  func (r *LoggingTraitReconciler) deleteLoggingConfigMap(ctx context.Context, trait *oamv1alpha1.LoggingTrait, resource *unstructured.Unstructured) error {
   523  	// check if configmap exists
   524  	configMapExists, err := resourceExists(ctx, r, configMapAPIVersion, configMapKind, loggingNamePart+"-"+resource.GetName()+"-"+strings.ToLower(resource.GetKind()), resource.GetNamespace())
   525  	if configMapExists {
   526  		return r.Delete(ctx, r.createLoggingConfigMap(trait, resource), &client.DeleteOptions{})
   527  	}
   528  	return err
   529  }
   530  
   531  // resourceExists determines whether or not a resource of the given kind identified by the given name and namespace exists
   532  func resourceExists(ctx context.Context, r client.Reader, apiVersion string, kind string, name string, namespace string) (bool, error) {
   533  	resources := unstructured.UnstructuredList{}
   534  	resources.SetAPIVersion(apiVersion)
   535  	resources.SetKind(kind)
   536  	options := []client.ListOption{client.InNamespace(namespace), client.MatchingFields{"metadata.name": name}}
   537  	err := r.List(ctx, &resources, options...)
   538  	return len(resources.Items) != 0, err
   539  }
   540  
   541  func (r *LoggingTraitReconciler) SetupWithManager(mgr ctrl.Manager) error {
   542  	return ctrl.NewControllerManagedBy(mgr).
   543  		For(&oamv1alpha1.LoggingTrait{}).
   544  		Complete(r)
   545  }