package k8shandler

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"reflect"
	"strings"
	"time"

	"github.com/openshift/cluster-logging-operator/internal/components/fluentd"
	"github.com/openshift/cluster-logging-operator/internal/constants"
	"github.com/openshift/cluster-logging-operator/internal/factory"
	"github.com/openshift/cluster-logging-operator/internal/utils/comparators/servicemonitor"
	"github.com/openshift/cluster-logging-operator/internal/utils/comparators/services"

	"github.com/ViaQ/logerr/log"
	"github.com/openshift/cluster-logging-operator/internal/utils"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/util/retry"

	monitoringv1 "github.com/coreos/prometheus-operator/pkg/apis/monitoring/v1"
	logging "github.com/openshift/cluster-logging-operator/apis/logging/v1"
	apps "k8s.io/api/apps/v1"
	core "k8s.io/api/core/v1"
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
	clusterLoggingPriorityClassName = "cluster-logging"
	exporterPort                    = int32(2112)
	exporterPortName                = "logfile-metrics"
	metricsPort                     = int32(24231)
	metricsPortName                 = "metrics"
	metricsVolumeName               = "collector-metrics"
	prometheusCAFile                = "/etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt"
)

var (
	retryInterval = time.Second * 30
	timeout       = time.Second * 1800
)

var serviceAccountLogCollectorUID types.UID

//CreateOrUpdateCollection component of the cluster
func (clusterRequest *ClusterLoggingRequest) CreateOrUpdateCollection() (err error) {
	cluster := clusterRequest.Cluster
	collectorConfig := ""
	collectorConfHash := ""
	log.V(9).Info("Entering CreateOrUpdateCollection")
	defer func() {
		log.V(9).Info("Leaving CreateOrUpdateCollection")
	}()

	var collectorServiceAccount *core.ServiceAccount

	// there is no easier way to check this in golang without writing a helper function
	// TODO: write a helper function to validate Type is a valid option for common setup or tear down
	if cluster.Spec.Collection != nil &&
		(cluster.Spec.Collection.Logs.Type == logging.LogCollectionTypeFluentd ||
			cluster.Spec.Collection.Logs.Type == logging.LogCollectionTypeVector) {

		var collectorType = cluster.Spec.Collection.Logs.Type

		//TODO: Remove me once fully migrated to new collector naming
		if err = clusterRequest.removeCollector(constants.FluentdName); err != nil {
			log.V(2).Info("Error removing legacy fluentd collector.  ", "err", err)
		}
		enabled, present := os.LookupEnv("ENABLE_VECTOR_COLLECTOR")
		if collectorType == logging.LogCollectionTypeVector && (!present || strings.ToLower(enabled) != "true") {
			err = errors.NewBadRequest("Vector as collector not enabled via env variable ENABLE_VECTOR_COLLECTOR")
			log.V(9).Error(err, "Vector as collector not enabled via env variable ENABLE_VECTOR_COLLECTOR")
			return err
		}

		if err = clusterRequest.createOrUpdateCollectionPriorityClass(); err != nil {
			log.V(9).Error(err, "clusterRequest.createOrUpdateCollectionPriorityClass")
			return
		}

		if collectorServiceAccount, err = clusterRequest.createOrUpdateCollectorServiceAccount(); err != nil {
			log.V(9).Error(err, "clusterRequest.createOrUpdateCollectorServiceAccount")
			return
		}

		if err = clusterRequest.createOrUpdateCollectorSecret(); err != nil {
			log.V(9).Error(err, "clusterRequest.createOrUpdateCollectorSecret")
			return
		}

		if collectorConfig, err = clusterRequest.generateCollectorConfig(); err != nil {
			log.V(9).Error(err, "clusterRequest.generateCollectorConfig")
			return
		}

		log.V(3).Info("Generated collector config", "config", collectorConfig)
		collectorConfHash, err = utils.CalculateMD5Hash(collectorConfig)
		if err != nil {
			log.Error(err, "unable to calculate MD5 hash")
			log.V(9).Error(err, "Returning from unable to calculate MD5 hash")
			return
		}
		if err = clusterRequest.reconcileCollectorService(); err != nil {
			log.V(9).Error(err, "clusterRequest.reconcileCollectorService")
			return
		}

		if err = clusterRequest.reconcileCollectorServiceMonitor(); err != nil {
			log.V(9).Error(err, "clusterRequest.reconcileCollectorServiceMonitor")
			return
		}

		if err = clusterRequest.createOrUpdateCollectorPrometheusRule(); err != nil {
			log.V(9).Error(err, "unable to create or update fluentd prometheus rule")
		}

		if err = clusterRequest.createOrUpdateCollectorConfigMap(collectorType, collectorConfig); err != nil {
			log.V(9).Error(err, "clusterRequest.createOrUpdateCollectorConfigMap")
			return
		}

		if err = clusterRequest.createOrUpdateCollectorDaemonset(collectorType, collectorConfHash); err != nil {
			log.V(9).Error(err, "clusterRequest.createOrUpdateCollectorDaemonset")
			return
		}

		if err = clusterRequest.UpdateCollectorStatus(collectorType); err != nil {
			log.V(9).Error(err, "unable to update status for the collector")
		}

		if collectorServiceAccount != nil {

			// remove our finalizer from the list and update it.
			collectorServiceAccount.ObjectMeta.Finalizers = utils.RemoveString(collectorServiceAccount.ObjectMeta.Finalizers, metav1.FinalizerDeleteDependents)
			if err = clusterRequest.Update(collectorServiceAccount); err != nil {
				log.Info("Unable to update the collector serviceaccount finalizers", "collectorServiceAccount.Name", collectorServiceAccount.Name)
				log.V(9).Error(err, "Unable to update the collector serviceaccount finalizers")
				return nil
			}
		}
	} else {
		if err = clusterRequest.RemoveServiceAccount("logcollector"); err != nil {
			return
		}

		if err = clusterRequest.RemovePriorityClass(clusterLoggingPriorityClassName); err != nil {
			return
		}

		if err = clusterRequest.removeCollector(constants.CollectorName); err != nil {
			return
		}
	}

	return nil
}

func (clusterRequest *ClusterLoggingRequest) removeCollector(name string) (err error) {
	if clusterRequest.isManaged() {

		if err = clusterRequest.RemoveService(name); err != nil {
			return
		}

		if err = clusterRequest.RemoveServiceMonitor(name); err != nil {
			return
		}

		if err = clusterRequest.RemovePrometheusRule(name); err != nil {
			return
		}

		if err = clusterRequest.RemoveConfigMap(name); err != nil {
			return
		}

		caName := fmt.Sprintf("%s-trusted-ca-bundle", name)
		if err = clusterRequest.RemoveConfigMap(caName); err != nil {
			return
		}

		if err = clusterRequest.RemoveDaemonset(name); err != nil {
			return
		}

		// Wait longer than the terminationGracePeriodSeconds
		time.Sleep(12 * time.Second)

		if err = clusterRequest.RemoveSecret(name); err != nil {
			return
		}

	}

	return nil
}

func (clusterRequest *ClusterLoggingRequest) createOrUpdateCollectorSecret() error {
	var secrets = map[string][]byte{}
	_ = Syncronize(func() error {
		secrets = map[string][]byte{}
		// Ignore errors, these files are optional depending on security settings.
		secrets["ca-bundle.crt"], _ = ioutil.ReadFile(utils.GetWorkingDirFilePath("ca.crt"))
		secrets["tls.key"], _ = ioutil.ReadFile(utils.GetWorkingDirFilePath("system.logging.fluentd.key"))
		secrets["tls.crt"], _ = ioutil.ReadFile(utils.GetWorkingDirFilePath("system.logging.fluentd.crt"))
		return nil
	})
	collectorSecret := NewSecret(
		constants.CollectorName,
		clusterRequest.Cluster.Namespace,
		secrets)

	utils.AddOwnerRefToObject(collectorSecret, utils.AsOwner(clusterRequest.Cluster))

	err := clusterRequest.CreateOrUpdateSecret(collectorSecret)
	if err != nil {
		return err
	}

	return nil
}

func (clusterRequest *ClusterLoggingRequest) reconcileCollectorService() error {
	desired := factory.NewService(
		constants.CollectorName,
		clusterRequest.Cluster.Namespace,
		constants.CollectorName,
		[]v1.ServicePort{
			{
				Port:       metricsPort,
				TargetPort: intstr.FromString(metricsPortName),
				Name:       metricsPortName,
			},
			{
				Port:       exporterPort,
				TargetPort: intstr.FromString(exporterPortName),
				Name:       exporterPortName,
			},
		},
	)

	desired.Annotations = map[string]string{
		"service.alpha.openshift.io/serving-cert-secret-name": constants.CollectorMetricSecretName,
	}

	utils.AddOwnerRefToObject(desired, utils.AsOwner(clusterRequest.Cluster))
	err := clusterRequest.Create(desired)
	if err != nil {
		if !errors.IsAlreadyExists(err) {
			return fmt.Errorf("Failure creating the collector service: %v", err)
		}
		current := &v1.Service{}
		retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
			if err = clusterRequest.Get(desired.Name, current); err != nil {
				if errors.IsNotFound(err) {
					// the object doesn't exist -- it was likely culled
					// recreate it on the next time through if necessary
					return nil
				}
				return fmt.Errorf("Failed to get %q service for %q: %v", current.Name, clusterRequest.Cluster.Name, err)
			}
			if services.AreSame(current, desired) {
				log.V(3).Info("Services are the same skipping update")
				return nil
			}
			//Explicitly copying because services are immutable
			current.Labels = desired.Labels
			current.Spec.Selector = desired.Spec.Selector
			current.Spec.Ports = desired.Spec.Ports
			return clusterRequest.Update(current)
		})
		log.V(3).Error(retryErr, "Reconcile Service retry error")
		return retryErr
	}
	return err
}

func (clusterRequest *ClusterLoggingRequest) reconcileCollectorServiceMonitor() error {

	cluster := clusterRequest.Cluster

	desired := NewServiceMonitor(constants.CollectorName, cluster.Namespace)

	endpoint := monitoringv1.Endpoint{
		Port:   metricsPortName,
		Path:   "/metrics",
		Scheme: "https",
		TLSConfig: &monitoringv1.TLSConfig{
			CAFile:     prometheusCAFile,
			ServerName: fmt.Sprintf("%s.%s.svc", constants.CollectorName, cluster.Namespace),
		},
	}
	logMetricExporterEndpoint := monitoringv1.Endpoint{
		Port:   exporterPortName,
		Path:   "/metrics",
		Scheme: "https",
		TLSConfig: &monitoringv1.TLSConfig{
			CAFile:     prometheusCAFile,
			ServerName: fmt.Sprintf("%s.%s.svc", constants.CollectorName, cluster.Namespace),
		},
	}

	labelSelector := metav1.LabelSelector{
		MatchLabels: map[string]string{
			"logging-infra": "support",
		},
	}

	desired.Spec = monitoringv1.ServiceMonitorSpec{
		JobLabel:  "monitor-collector",
		Endpoints: []monitoringv1.Endpoint{endpoint, logMetricExporterEndpoint},
		Selector:  labelSelector,
		NamespaceSelector: monitoringv1.NamespaceSelector{
			MatchNames: []string{cluster.Namespace},
		},
	}

	utils.AddOwnerRefToObject(desired, utils.AsOwner(cluster))

	err := clusterRequest.Create(desired)
	if err != nil {
		if !errors.IsAlreadyExists(err) {
			return fmt.Errorf("Failure creating the collector ServiceMonitor: %v", err)
		}
		current := &monitoringv1.ServiceMonitor{}
		retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
			if err = clusterRequest.Get(desired.Name, current); err != nil {
				if errors.IsNotFound(err) {
					// the object doesn't exist -- it was likely culled
					// recreate it on the next time through if necessary
					return nil
				}
				return fmt.Errorf("Failed to get %q service for %q: %v", current.Name, clusterRequest.Cluster.Name, err)
			}
			if servicemonitor.AreSame(current, desired) {
				log.V(3).Info("ServiceMonitor are the same skipping update")
				return nil
			}
			current.Labels = desired.Labels
			current.Spec = desired.Spec
			current.Annotations = desired.Annotations

			return clusterRequest.Update(current)
		})
		log.V(3).Error(retryErr, "Reconcile ServiceMonitor retry error")
		return retryErr
	}
	return err
}

func (clusterRequest *ClusterLoggingRequest) createOrUpdateCollectorPrometheusRule() error {
	ctx := context.TODO()
	cluster := clusterRequest.Cluster

	rule := NewPrometheusRule(constants.CollectorName, cluster.Namespace)

	spec, err := NewPrometheusRuleSpecFrom(path.Join(utils.GetShareDir(), fluentdAlertsFile))
	if err != nil {
		return fmt.Errorf("failure creating the collector PrometheusRule: %w", err)
	}

	rule.Spec = *spec

	utils.AddOwnerRefToObject(rule, utils.AsOwner(cluster))

	err = clusterRequest.Create(rule)
	if err == nil {
		return nil
	}
	if !errors.IsAlreadyExists(err) {
		return fmt.Errorf("failure creating the collector PrometheusRule: %w", err)
	}

	current := &monitoringv1.PrometheusRule{}
	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
		err = clusterRequest.Client.Get(ctx, types.NamespacedName{Name: rule.Name, Namespace: rule.Namespace}, current)
		if err != nil {
			log.V(2).Info("could not get prometheus rule", rule.Name, err)
			return err
		}
		current.Spec = rule.Spec
		if err = clusterRequest.Client.Update(ctx, current); err != nil {
			return err
		}
		log.V(3).Info("updated prometheus rules")
		return nil
	})
}

func (clusterRequest *ClusterLoggingRequest) createOrUpdateCollectorConfigMap(collectorType logging.LogCollectionType, collectorConfig string) error {
	var collectorConfigMap *corev1.ConfigMap = nil
	if collectorType == logging.LogCollectionTypeFluentd {
		collectorConfigMap = NewConfigMap(
			constants.CollectorName,
			clusterRequest.Cluster.Namespace,
			map[string]string{
				"fluent.conf": collectorConfig,
				"run.sh":      fluentd.RunScript,
			},
		)
	} else if collectorType == logging.LogCollectionTypeVector {
		collectorConfigMap = NewConfigMap(
			constants.CollectorName,
			clusterRequest.Cluster.Namespace,
			map[string]string{
				"vector.toml": collectorConfig,
			},
		)
	}

	utils.AddOwnerRefToObject(collectorConfigMap, utils.AsOwner(clusterRequest.Cluster))

	err := clusterRequest.Create(collectorConfigMap)
	if err != nil && !errors.IsAlreadyExists(err) {
		return fmt.Errorf("Failure constructing collector configmap: %v", err)
	}
	retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
		current := &v1.ConfigMap{}
		if err = clusterRequest.Get(collectorConfigMap.Name, current); err != nil {
			if errors.IsNotFound(err) {
				log.V(2).Info("Returning nil. The configmap was not found even though create previously failed.  Was it culled?", "configmap name", collectorConfigMap.Name)
				return nil
			}
			return fmt.Errorf("Failed to get %v configmap for %q: %v", collectorConfigMap.Name, clusterRequest.Cluster.Name, err)
		}
		if reflect.DeepEqual(collectorConfigMap.Data, current.Data) {
			return nil
		}
		current.Data = collectorConfigMap.Data
		return clusterRequest.Update(current)
	})

	return retryErr
}

func (clusterRequest *ClusterLoggingRequest) createOrUpdateCollectorDaemonset(collectorType logging.LogCollectionType, pipelineConfHash string) (err error) {
	var fluentdTrustBundle *v1.ConfigMap
	// Create or update cluster proxy trusted CA bundle.
	fluentdTrustBundle, err = clusterRequest.createOrGetTrustedCABundleConfigMap(constants.CollectorTrustedCAName)
	if err != nil {
		return
	}
	if collectorType == logging.LogCollectionTypeFluentd {
		return clusterRequest.createOrUpdateFluentdDaemonset(fluentdTrustBundle, pipelineConfHash)
	} else if collectorType == logging.LogCollectionTypeVector {
		return clusterRequest.createOrUpdateVectorDaemonset(fluentdTrustBundle, pipelineConfHash)
	}
	return nil
}

func (clusterRequest *ClusterLoggingRequest) UpdateCollectorStatus(collectorType logging.LogCollectionType) (err error) {
	if collectorType == logging.LogCollectionTypeFluentd {
		return clusterRequest.UpdateFluentdStatus()
	}
	return nil
}
func (clusterRequest *ClusterLoggingRequest) UpdateFluentdStatus() (err error) {

	cluster := clusterRequest.Cluster

	fluentdStatus, err := clusterRequest.getFluentdCollectorStatus()
	if err != nil {
		return fmt.Errorf("Failed to get status of the collector: %v", err)
	}

	retryErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
		if !compareFluentdCollectorStatus(fluentdStatus, cluster.Status.Collection.Logs.FluentdStatus) {
			cluster.Status.Collection.Logs.FluentdStatus = fluentdStatus
			return clusterRequest.UpdateStatus(cluster)
		}
		return nil
	})
	if retryErr != nil {
		return fmt.Errorf("Failed to update Cluster Logging Collector status: %v", retryErr)
	}

	return nil
}

func compareFluentdCollectorStatus(lhs, rhs logging.FluentdCollectorStatus) bool {
	if lhs.DaemonSet != rhs.DaemonSet {
		return false
	}

	if len(lhs.Conditions) != len(rhs.Conditions) {
		return false
	}

	if len(lhs.Conditions) > 0 {
		if !reflect.DeepEqual(lhs.Conditions, rhs.Conditions) {
			return false
		}
	}

	if len(lhs.Nodes) != len(rhs.Nodes) {
		return false
	}

	if len(lhs.Nodes) > 0 {
		if !reflect.DeepEqual(lhs.Nodes, rhs.Nodes) {

			return false
		}
	}

	if len(lhs.Pods) != len(rhs.Pods) {
		return false
	}

	if len(lhs.Pods) > 0 {
		if !reflect.DeepEqual(lhs.Pods, rhs.Pods) {
			return false
		}
	}

	return true
}

func (clusterRequest *ClusterLoggingRequest) createOrUpdateCollectionPriorityClass() error {

	collectionPriorityClass := NewPriorityClass(clusterLoggingPriorityClassName, 1000000, false, "This priority class is for the Cluster-Logging Collector")

	utils.AddOwnerRefToObject(collectionPriorityClass, utils.AsOwner(clusterRequest.Cluster))

	err := clusterRequest.Create(collectionPriorityClass)
	if err != nil && !errors.IsAlreadyExists(err) {
		return fmt.Errorf("Failure creating Collection priority class: %v", err)
	}

	return nil
}

func (clusterRequest *ClusterLoggingRequest) createOrUpdateCollectorServiceAccount() (*core.ServiceAccount, error) {

	cluster := clusterRequest.Cluster

	collectorServiceAccount := NewServiceAccount(constants.CollectorServiceAccountName, cluster.Namespace)

	utils.AddOwnerRefToObject(collectorServiceAccount, utils.AsOwner(clusterRequest.Cluster))

	delfinalizer := false
	if collectorServiceAccount.ObjectMeta.DeletionTimestamp.IsZero() {
		// This object is not being deleted.
		if !utils.ContainsString(collectorServiceAccount.ObjectMeta.Finalizers, metav1.FinalizerDeleteDependents) {
			collectorServiceAccount.ObjectMeta.Finalizers = append(collectorServiceAccount.ObjectMeta.Finalizers, metav1.FinalizerDeleteDependents)
		}
		err := clusterRequest.Create(collectorServiceAccount)
		if err != nil && !errors.IsAlreadyExists(err) {
			return nil, fmt.Errorf("Failure creating Log Collector service account: %v", err)
		}
		if len(collectorServiceAccount.ObjectMeta.UID) != 0 {
			serviceAccountLogCollectorUID = collectorServiceAccount.ObjectMeta.UID
		}
	} else if utils.ContainsString(collectorServiceAccount.ObjectMeta.Finalizers, metav1.FinalizerDeleteDependents) {
		// This object is being deleted.
		// our finalizer is present, so lets handle any dependency
		delfinalizer = true
	}

	// If needed create a custom SecurityContextConstraints object for the log collector to run with
	scc := NewSCC(LogCollectorSCCName)
	err := clusterRequest.Create(scc)
	if err != nil && !errors.IsAlreadyExists(err) {
		return nil, fmt.Errorf("Failure creating Log Collector SecurityContextConstraints: %v", err)
	}

	// Also create the role and role binding so that the service account has host read access
	collectorRole := NewRole(
		"log-collector-privileged",
		cluster.Namespace,
		NewPolicyRules(
			NewPolicyRule(
				[]string{"security.openshift.io"},
				[]string{"securitycontextconstraints"},
				[]string{LogCollectorSCCName},
				[]string{"use"},
			),
		),
	)

	utils.AddOwnerRefToObject(collectorRole, utils.AsOwner(cluster))

	err = clusterRequest.Create(collectorRole)
	if err != nil && !errors.IsAlreadyExists(err) {
		return nil, fmt.Errorf("Failure creating Log collector privileged role: %v", err)
	}

	subject := NewSubject(
		"ServiceAccount",
		"logcollector",
	)
	subject.APIGroup = ""

	collectorRoleBinding := NewRoleBinding(
		"log-collector-privileged-binding",
		cluster.Namespace,
		"log-collector-privileged",
		NewSubjects(
			subject,
		),
	)

	utils.AddOwnerRefToObject(collectorRoleBinding, utils.AsOwner(cluster))

	err = clusterRequest.Create(collectorRoleBinding)
	if err != nil && !errors.IsAlreadyExists(err) {
		return nil, fmt.Errorf("Failure creating Log collector privileged role binding: %v", err)
	}

	// create clusterrole for logcollector to retrieve metadata
	clusterrules := NewPolicyRules(
		NewPolicyRule(
			[]string{""},
			[]string{"pods", "namespaces"},
			nil,
			[]string{"get", "list", "watch"},
		),
	)
	clusterRole, err := clusterRequest.CreateClusterRole("metadata-reader", clusterrules, cluster)
	if err != nil {
		return nil, err
	}
	subject = NewSubject(
		"ServiceAccount",
		"logcollector",
	)
	subject.Namespace = cluster.Namespace
	subject.APIGroup = ""

	collectorReaderClusterRoleBinding := NewClusterRoleBinding(
		"cluster-logging-metadata-reader",
		clusterRole.Name,
		NewSubjects(
			subject,
		),
	)

	err = clusterRequest.Create(collectorReaderClusterRoleBinding)
	if err != nil && !errors.IsAlreadyExists(err) {
		return nil, fmt.Errorf("Failure creating Log collector %q cluster role binding: %v", collectorReaderClusterRoleBinding.Name, err)
	}

	if delfinalizer {
		return collectorServiceAccount, nil
	} else {
		return nil, nil
	}
}

func (clusterRequest *ClusterLoggingRequest) waitForDaemonSetReady(ds *apps.DaemonSet) error {

	err := wait.Poll(retryInterval, timeout, func() (done bool, err error) {
		err = clusterRequest.Get(ds.Name, ds)
		if err != nil {
			if errors.IsNotFound(err) {
				return false, fmt.Errorf("Failed to get Fluentd daemonset: %v", err)
			}
			return false, err
		}

		if int(ds.Status.DesiredNumberScheduled) == int(ds.Status.NumberReady) {
			return true, nil
		}

		return false, nil
	})

	if err != nil {
		return err
	}

	return nil
}

func isBufferFlushRequired(current *apps.DaemonSet, desired *apps.DaemonSet) bool {

	currImage := strings.Split(current.Spec.Template.Spec.Containers[0].Image, ":")
	desImage := strings.Split(desired.Spec.Template.Spec.Containers[0].Image, ":")

	if len(currImage) != 2 || len(desImage) != 2 {
		// we don't have versions here -- not sure how we would compare versions to determine
		// need to flush buffers
		return false
	}

	currVersion := currImage[1]
	desVersion := desImage[1]

	if strings.HasPrefix(currVersion, "v") {
		currVersion = strings.Split(currVersion, "v")[1]
	}

	if strings.HasPrefix(desVersion, "v") {
		desVersion = strings.Split(desVersion, "v")[1]
	}

	return (currVersion == "3.11" && desVersion == "4.0.0")
}

func getServiceAccountLogCollectorUID() types.UID {
	return serviceAccountLogCollectorUID
}
