github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/scoped/syncer.go (about)

     1  package scoped
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"reflect"
     7  
     8  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
     9  	"github.com/sirupsen/logrus"
    10  	corev1 "k8s.io/api/core/v1"
    11  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    12  	"k8s.io/apimachinery/pkg/api/meta"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/runtime"
    15  	corev1applyconfigurations "k8s.io/client-go/applyconfigurations/core/v1"
    16  	"k8s.io/client-go/tools/reference"
    17  	"k8s.io/utils/clock"
    18  
    19  	v1 "github.com/operator-framework/api/pkg/operators/v1"
    20  	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
    21  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    22  )
    23  
    24  // NewUserDefinedServiceAccountSyncer returns a new instance of UserDefinedServiceAccountSyncer.
    25  func NewUserDefinedServiceAccountSyncer(logger *logrus.Logger, scheme *runtime.Scheme, client operatorclient.ClientInterface, versioned versioned.Interface) *UserDefinedServiceAccountSyncer {
    26  	return &UserDefinedServiceAccountSyncer{
    27  		logger:    logger,
    28  		versioned: versioned,
    29  		client:    client,
    30  		clock:     &clock.RealClock{},
    31  		scheme:    scheme,
    32  	}
    33  }
    34  
    35  // UserDefinedServiceAccountSyncer syncs an operator group appropriately when
    36  // a user defined service account is specified.
    37  type UserDefinedServiceAccountSyncer struct {
    38  	versioned versioned.Interface
    39  	client    operatorclient.ClientInterface
    40  	logger    *logrus.Logger
    41  	clock     clock.Clock
    42  	scheme    *runtime.Scheme
    43  }
    44  
    45  const (
    46  	// All logs should in this package should have the following field to make
    47  	// it easy to comb through logs.
    48  	logFieldName  = "mode"
    49  	logFieldValue = "scoped"
    50  )
    51  
    52  // SyncOperatorGroup takes appropriate actions when a user specifies a service account.
    53  func (s *UserDefinedServiceAccountSyncer) SyncOperatorGroup(in *v1.OperatorGroup) (out *v1.OperatorGroup, err error) {
    54  	out = in
    55  	namespace := in.GetNamespace()
    56  	serviceAccountName := in.Spec.ServiceAccountName
    57  
    58  	logger := s.logger.WithFields(logrus.Fields{
    59  		"operatorGroup": in.GetName(),
    60  		"namespace":     in.GetNamespace(),
    61  		logFieldName:    logFieldValue,
    62  	})
    63  
    64  	if serviceAccountName == "" {
    65  		if in.Status.ServiceAccountRef == nil {
    66  			return
    67  		}
    68  
    69  		// Remove ServiceAccount condition if existed
    70  		meta.RemoveStatusCondition(&in.Status.Conditions, v1.OperatorGroupServiceAccountCondition)
    71  
    72  		// User must have removed ServiceAccount from the spec. We need to
    73  		// rest Status to a nil reference.
    74  		out, err = s.update(in, nil)
    75  		if err != nil {
    76  			err = fmt.Errorf("failed to reset status.serviceAccount, sa=%s %v", serviceAccountName, err)
    77  		}
    78  		return
    79  	}
    80  
    81  	// A service account has been specified, we need to update the status.
    82  	sa, err := s.client.KubernetesInterface().CoreV1().ServiceAccounts(namespace).Get(context.TODO(), serviceAccountName, metav1.GetOptions{})
    83  	if err != nil {
    84  		if apierrors.IsNotFound(err) {
    85  			// Set OG's status condition to indicate SA is not found
    86  			cond := metav1.Condition{
    87  				Type:    v1.OperatorGroupServiceAccountCondition,
    88  				Status:  metav1.ConditionTrue,
    89  				Reason:  v1.OperatorGroupServiceAccountReason,
    90  				Message: fmt.Sprintf("ServiceAccount %s not found", serviceAccountName),
    91  			}
    92  
    93  			meta.SetStatusCondition(&in.Status.Conditions, cond)
    94  			_, uerr := s.update(in, nil)
    95  			if uerr != nil {
    96  				logger.Warnf("fail to upgrade operator group status og=%s with condition %+v: %s", in.GetName(), cond, uerr.Error())
    97  			}
    98  		}
    99  		err = fmt.Errorf("failed to get service account, sa=%s %v", serviceAccountName, err)
   100  		return
   101  	}
   102  
   103  	// A service account has been specified, but likely does not have the labels we require it to have to ensure it
   104  	// shows up in our listers, so let's add that and queue again later
   105  	config := corev1applyconfigurations.ServiceAccount(serviceAccountName, namespace)
   106  	config.Labels = map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}
   107  	if _, err := s.client.KubernetesInterface().CoreV1().ServiceAccounts(namespace).Apply(context.TODO(), config, metav1.ApplyOptions{FieldManager: "operator-lifecycle-manager"}); err != nil {
   108  		return out, fmt.Errorf("failed to apply labels[%s]=%s to serviceaccount %s/%s: %w", install.OLMManagedLabelKey, install.OLMManagedLabelValue, namespace, serviceAccountName, err)
   109  	}
   110  
   111  	ref, err := reference.GetReference(s.scheme, sa)
   112  	if err != nil {
   113  		return
   114  	}
   115  
   116  	if reflect.DeepEqual(in.Status.ServiceAccountRef, ref) {
   117  		logger.Debugf("status.serviceAccount is in sync with spec sa=%s", serviceAccountName)
   118  		return
   119  	}
   120  
   121  	// Remove SA not found condition if found
   122  	if c := meta.FindStatusCondition(in.Status.Conditions, v1.OperatorGroupServiceAccountCondition); c != nil {
   123  		meta.RemoveStatusCondition(&in.Status.Conditions, v1.OperatorGroupServiceAccountCondition)
   124  	}
   125  
   126  	out, err = s.update(in, ref)
   127  	if err != nil {
   128  		err = fmt.Errorf("failed to set status.serviceAccount, sa=%s %v", serviceAccountName, err)
   129  	}
   130  
   131  	return
   132  }
   133  
   134  func (s *UserDefinedServiceAccountSyncer) update(in *v1.OperatorGroup, ref *corev1.ObjectReference) (out *v1.OperatorGroup, err error) {
   135  	out = in
   136  
   137  	status := out.Status.DeepCopy()
   138  	status.ServiceAccountRef = ref
   139  	now := metav1.NewTime(s.clock.Now())
   140  	status.LastUpdated = &now
   141  
   142  	out.Status = *status
   143  
   144  	out, err = s.versioned.OperatorsV1().OperatorGroups(out.GetNamespace()).UpdateStatus(context.TODO(), out, metav1.UpdateOptions{})
   145  	return
   146  }