github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/operators/operatorcondition_controller.go (about)

     1  package operators
     2  
     3  import (
     4  	"context"
     5  	"reflect"
     6  
     7  	"github.com/go-logr/logr"
     8  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
     9  	appsv1 "k8s.io/api/apps/v1"
    10  	corev1 "k8s.io/api/core/v1"
    11  	rbacv1 "k8s.io/api/rbac/v1"
    12  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    13  	"k8s.io/apimachinery/pkg/api/meta"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/runtime"
    16  	"k8s.io/apimachinery/pkg/types"
    17  	ctrl "sigs.k8s.io/controller-runtime"
    18  	"sigs.k8s.io/controller-runtime/pkg/client"
    19  	"sigs.k8s.io/controller-runtime/pkg/handler"
    20  	"sigs.k8s.io/controller-runtime/pkg/reconcile"
    21  
    22  	operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
    23  	operatorsv2 "github.com/operator-framework/api/pkg/operators/v2"
    24  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    25  	"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
    26  )
    27  
    28  const (
    29  	OperatorConditionEnvVarKey = "OPERATOR_CONDITION_NAME"
    30  )
    31  
    32  // OperatorConditionReconciler reconciles an OperatorCondition object.
    33  type OperatorConditionReconciler struct {
    34  	client.Client
    35  	log logr.Logger
    36  }
    37  
    38  // +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions,verbs=get;list;update;patch;delete
    39  // +kubebuilder:rbac:groups=operators.coreos.com,resources=operatorconditions/status,verbs=update;patch
    40  
    41  // SetupWithManager adds the OperatorCondition Reconciler reconciler to the given controller manager.
    42  func (r *OperatorConditionReconciler) SetupWithManager(mgr ctrl.Manager) error {
    43  	deploymentHandler := handler.EnqueueRequestsFromMapFunc(r.mapToOperatorCondition)
    44  	handler := handler.EnqueueRequestForOwner(mgr.GetScheme(), mgr.GetRESTMapper(), &operatorsv2.OperatorCondition{}, handler.OnlyControllerOwner())
    45  
    46  	return ctrl.NewControllerManagedBy(mgr).
    47  		For(&operatorsv2.OperatorCondition{}).
    48  		Watches(&rbacv1.Role{}, handler).
    49  		Watches(&rbacv1.RoleBinding{}, handler).
    50  		Watches(&appsv1.Deployment{}, deploymentHandler).
    51  		Complete(r)
    52  }
    53  
    54  func (r *OperatorConditionReconciler) mapToOperatorCondition(_ context.Context, obj client.Object) (requests []reconcile.Request) {
    55  	if obj == nil {
    56  		return nil
    57  	}
    58  
    59  	owner := ownerutil.GetOwnerByKind(obj, operatorsv1alpha1.ClusterServiceVersionKind)
    60  	if owner == nil {
    61  		return nil
    62  	}
    63  
    64  	return []reconcile.Request{
    65  		{
    66  			NamespacedName: types.NamespacedName{
    67  				Name:      owner.Name,
    68  				Namespace: obj.GetNamespace(),
    69  			},
    70  		},
    71  	}
    72  }
    73  
    74  // NewOperatorConditionReconciler constructs and returns an OperatorConditionReconciler.
    75  // As a side effect, the given scheme has operator discovery types added to it.
    76  func NewOperatorConditionReconciler(cli client.Client, log logr.Logger, scheme *runtime.Scheme) (*OperatorConditionReconciler, error) {
    77  	// Add watched types to scheme.
    78  	if err := AddToScheme(scheme); err != nil {
    79  		return nil, err
    80  	}
    81  
    82  	return &OperatorConditionReconciler{
    83  		Client: cli,
    84  		log:    log,
    85  	}, nil
    86  }
    87  
    88  // Implement reconcile.Reconciler so the controller can reconcile objects
    89  var _ reconcile.Reconciler = &OperatorConditionReconciler{}
    90  
    91  func (r *OperatorConditionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    92  	// Set up a convenient log object so we don't have to type request over and over again
    93  	log := r.log.WithValues("request", req).V(1)
    94  	log.Info("reconciling")
    95  	metrics.EmitOperatorConditionReconcile(req.Namespace, req.Name)
    96  
    97  	operatorCondition := &operatorsv2.OperatorCondition{}
    98  	if err := r.Client.Get(ctx, req.NamespacedName, operatorCondition); err != nil {
    99  		log.Info("Unable to find OperatorCondition")
   100  		return ctrl.Result{}, client.IgnoreNotFound(err)
   101  	}
   102  
   103  	if err := r.ensureOperatorConditionRole(operatorCondition); err != nil {
   104  		log.Info("Error ensuring OperatorCondition Role")
   105  		return ctrl.Result{}, err
   106  	}
   107  
   108  	if err := r.ensureOperatorConditionRoleBinding(operatorCondition); err != nil {
   109  		log.Info("Error ensuring OperatorCondition RoleBinding")
   110  		return ctrl.Result{}, err
   111  	}
   112  
   113  	if err := r.ensureDeploymentEnvVars(operatorCondition); err != nil {
   114  		log.Info("Error ensuring OperatorCondition Deployment EnvVars")
   115  		return ctrl.Result{}, err
   116  	}
   117  
   118  	if err := r.syncOperatorConditionStatus(operatorCondition); err != nil {
   119  		log.Info("Error syncing OperatorCondition Status")
   120  		return ctrl.Result{}, err
   121  	}
   122  
   123  	return ctrl.Result{}, nil
   124  }
   125  
   126  func (r *OperatorConditionReconciler) ensureOperatorConditionRole(operatorCondition *operatorsv2.OperatorCondition) error {
   127  	r.log.V(4).Info("Ensuring the Role for the OperatorCondition")
   128  	role := &rbacv1.Role{
   129  		ObjectMeta: metav1.ObjectMeta{
   130  			Name:      operatorCondition.GetName(),
   131  			Namespace: operatorCondition.GetNamespace(),
   132  			Labels: map[string]string{
   133  				install.OLMManagedLabelKey: install.OLMManagedLabelValue,
   134  			},
   135  		},
   136  		Rules: []rbacv1.PolicyRule{
   137  			{
   138  				Verbs:         []string{"get", "update", "patch"},
   139  				APIGroups:     []string{"operators.coreos.com"},
   140  				Resources:     []string{"operatorconditions"},
   141  				ResourceNames: []string{operatorCondition.GetName()},
   142  			},
   143  		},
   144  	}
   145  	ownerutil.AddOwner(role, operatorCondition, false, true)
   146  
   147  	existingRole := &rbacv1.Role{}
   148  	err := r.Client.Get(context.TODO(), client.ObjectKey{Name: role.GetName(), Namespace: role.GetNamespace()}, existingRole)
   149  	if err != nil {
   150  		if !apierrors.IsNotFound(err) {
   151  			return err
   152  		}
   153  		err = r.Client.Create(context.TODO(), role)
   154  		if apierrors.IsAlreadyExists(err) {
   155  			return r.Client.Update(context.TODO(), role)
   156  		}
   157  	}
   158  
   159  	if ownerutil.IsOwnedBy(existingRole, operatorCondition) &&
   160  		reflect.DeepEqual(role.Rules, existingRole.Rules) {
   161  		r.log.V(5).Info("Existing Role does not need to be updated")
   162  		return nil
   163  	}
   164  	r.log.V(5).Info("Existing Role needs to be updated")
   165  
   166  	existingRole.OwnerReferences = role.OwnerReferences
   167  	existingRole.Rules = role.Rules
   168  	return r.Client.Update(context.TODO(), existingRole)
   169  }
   170  
   171  func (r *OperatorConditionReconciler) ensureOperatorConditionRoleBinding(operatorCondition *operatorsv2.OperatorCondition) error {
   172  	r.log.V(4).Info("Ensuring the RoleBinding for the OperatorCondition")
   173  	subjects := []rbacv1.Subject{}
   174  	for _, serviceAccount := range operatorCondition.Spec.ServiceAccounts {
   175  		subjects = append(subjects, rbacv1.Subject{
   176  			Kind:     rbacv1.ServiceAccountKind,
   177  			Name:     serviceAccount,
   178  			APIGroup: "",
   179  		})
   180  	}
   181  
   182  	roleBinding := &rbacv1.RoleBinding{
   183  		ObjectMeta: metav1.ObjectMeta{
   184  			Name:      operatorCondition.GetName(),
   185  			Namespace: operatorCondition.GetNamespace(),
   186  			Labels: map[string]string{
   187  				install.OLMManagedLabelKey: install.OLMManagedLabelValue,
   188  			},
   189  		},
   190  		Subjects: subjects,
   191  		RoleRef: rbacv1.RoleRef{
   192  			Kind:     "Role",
   193  			Name:     operatorCondition.GetName(),
   194  			APIGroup: "rbac.authorization.k8s.io",
   195  		},
   196  	}
   197  	ownerutil.AddOwner(roleBinding, operatorCondition, false, true)
   198  
   199  	existingRoleBinding := &rbacv1.RoleBinding{}
   200  	err := r.Client.Get(context.TODO(), client.ObjectKey{Name: roleBinding.GetName(), Namespace: roleBinding.GetNamespace()}, existingRoleBinding)
   201  	if err != nil {
   202  		if !apierrors.IsNotFound(err) {
   203  			return err
   204  		}
   205  		err = r.Client.Create(context.TODO(), roleBinding)
   206  		if apierrors.IsAlreadyExists(err) {
   207  			return r.Client.Update(context.TODO(), roleBinding)
   208  		}
   209  	}
   210  
   211  	if ownerutil.IsOwnedBy(existingRoleBinding, operatorCondition) &&
   212  		existingRoleBinding.RoleRef == roleBinding.RoleRef &&
   213  		reflect.DeepEqual(roleBinding.Subjects, existingRoleBinding.Subjects) {
   214  		r.log.V(5).Info("Existing RoleBinding does not need to be updated")
   215  		return nil
   216  	}
   217  
   218  	r.log.V(5).Info("Existing RoleBinding needs to be updated")
   219  	existingRoleBinding.OwnerReferences = roleBinding.OwnerReferences
   220  	existingRoleBinding.Subjects = roleBinding.Subjects
   221  	existingRoleBinding.RoleRef = roleBinding.RoleRef
   222  
   223  	return r.Client.Update(context.TODO(), existingRoleBinding)
   224  }
   225  
   226  func (r *OperatorConditionReconciler) ensureDeploymentEnvVars(operatorCondition *operatorsv2.OperatorCondition) error {
   227  	r.log.V(4).Info("Ensuring that deployments have the OPERATOR_CONDITION_NAME variable")
   228  	for _, deploymentName := range operatorCondition.Spec.Deployments {
   229  		deployment := &appsv1.Deployment{}
   230  		err := r.Client.Get(context.TODO(), types.NamespacedName{Name: deploymentName, Namespace: operatorCondition.GetNamespace()}, deployment)
   231  		if err != nil {
   232  			return err
   233  		}
   234  
   235  		// Check the deployment is owned by a CSV with the same name as the OperatorCondition.
   236  		deploymentOwner := ownerutil.GetOwnerByKind(deployment, operatorsv1alpha1.ClusterServiceVersionKind)
   237  		if deploymentOwner == nil || deploymentOwner.Name != operatorCondition.GetName() {
   238  			continue
   239  		}
   240  
   241  		deploymentNeedsUpdate := false
   242  		for i := range deployment.Spec.Template.Spec.Containers {
   243  			envVars, containedEnvVar := ensureEnvVarIsPresent(deployment.Spec.Template.Spec.Containers[i].Env, corev1.EnvVar{Name: OperatorConditionEnvVarKey, Value: operatorCondition.GetName()})
   244  			if !containedEnvVar {
   245  				deploymentNeedsUpdate = true
   246  			}
   247  			deployment.Spec.Template.Spec.Containers[i].Env = envVars
   248  		}
   249  		if !deploymentNeedsUpdate {
   250  			r.log.V(5).Info("Existing deployment does not need to be updated")
   251  			continue
   252  		}
   253  		r.log.V(5).Info("Existing deployment needs to be updated")
   254  		err = r.Client.Update(context.TODO(), deployment)
   255  		if err != nil {
   256  			return err
   257  		}
   258  	}
   259  	return nil
   260  }
   261  
   262  func (r *OperatorConditionReconciler) syncOperatorConditionStatus(operatorCondition *operatorsv2.OperatorCondition) error {
   263  	r.log.V(4).Info("Sync operatorcondition status")
   264  	currentGen := operatorCondition.ObjectMeta.GetGeneration()
   265  	changed := false
   266  	for _, cond := range operatorCondition.Spec.Conditions {
   267  		if c := meta.FindStatusCondition(operatorCondition.Status.Conditions, cond.Type); c != nil {
   268  			if cond.Status == c.Status && c.ObservedGeneration == currentGen {
   269  				continue
   270  			}
   271  		}
   272  		cond.ObservedGeneration = currentGen
   273  		meta.SetStatusCondition(&operatorCondition.Status.Conditions, cond)
   274  		changed = true
   275  	}
   276  
   277  	if changed {
   278  		return r.Client.Status().Update(context.TODO(), operatorCondition)
   279  	}
   280  	return nil
   281  }
   282  
   283  func ensureEnvVarIsPresent(envVars []corev1.EnvVar, envVar corev1.EnvVar) ([]corev1.EnvVar, bool) {
   284  	for i, each := range envVars {
   285  		if each.Name == envVar.Name {
   286  			if each.Value == envVar.Value {
   287  				return envVars, true
   288  			}
   289  			envVars[i].Value = envVar.Value
   290  			return envVars, false
   291  		}
   292  	}
   293  	return append(envVars, envVar), false
   294  }