github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/registry/reconciler/grpc.go (about)

     1  package reconciler
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    11  
    12  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    13  	controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
    14  	hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash"
    15  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    16  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
    17  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    18  	pkgerrors "github.com/pkg/errors"
    19  	"github.com/sirupsen/logrus"
    20  	corev1 "k8s.io/api/core/v1"
    21  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    22  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    23  	"k8s.io/apimachinery/pkg/labels"
    24  	"k8s.io/apimachinery/pkg/util/intstr"
    25  )
    26  
    27  const (
    28  	CatalogSourceUpdateKey      = "catalogsource.operators.coreos.com/update"
    29  	ServiceHashLabelKey         = "olm.service-spec-hash"
    30  	CatalogPollingRequeuePeriod = 30 * time.Second
    31  )
    32  
    33  // grpcCatalogSourceDecorator wraps CatalogSource to add additional methods
    34  type grpcCatalogSourceDecorator struct {
    35  	*v1alpha1.CatalogSource
    36  	createPodAsUser int64
    37  	opmImage        string
    38  	utilImage       string
    39  }
    40  
    41  type UpdateNotReadyErr struct {
    42  	catalogName string
    43  	podName     string
    44  }
    45  
    46  func (u UpdateNotReadyErr) Error() string {
    47  	return fmt.Sprintf("catalog polling: %s not ready for update: update pod %s has not yet reported ready", u.catalogName, u.podName)
    48  }
    49  
    50  func (s *grpcCatalogSourceDecorator) Selector() labels.Selector {
    51  	return labels.SelectorFromValidatedSet(map[string]string{
    52  		CatalogSourceLabelKey: s.GetName(),
    53  	})
    54  }
    55  
    56  func (s *grpcCatalogSourceDecorator) SelectorForUpdate() labels.Selector {
    57  	return labels.SelectorFromValidatedSet(map[string]string{
    58  		CatalogSourceUpdateKey: s.GetName(),
    59  	})
    60  }
    61  
    62  func (s *grpcCatalogSourceDecorator) Labels() map[string]string {
    63  	return map[string]string{
    64  		CatalogSourceLabelKey:      s.GetName(),
    65  		install.OLMManagedLabelKey: install.OLMManagedLabelValue,
    66  	}
    67  }
    68  
    69  func (s *grpcCatalogSourceDecorator) Annotations() map[string]string {
    70  	// TODO: Maybe something better than just a copy of all annotations would be to have a specific 'podMetadata' section in the CatalogSource?
    71  	return s.GetAnnotations()
    72  }
    73  
    74  func (s *grpcCatalogSourceDecorator) Service() (*corev1.Service, error) {
    75  	svc := &corev1.Service{
    76  		ObjectMeta: metav1.ObjectMeta{
    77  			Name:      strings.ReplaceAll(s.GetName(), ".", "-"),
    78  			Namespace: s.GetNamespace(),
    79  		},
    80  		Spec: corev1.ServiceSpec{
    81  			Ports: []corev1.ServicePort{
    82  				{
    83  					Name:       "grpc",
    84  					Port:       50051,
    85  					TargetPort: intstr.FromInt(50051),
    86  				},
    87  			},
    88  			Selector: s.Labels(),
    89  		},
    90  	}
    91  
    92  	labels := map[string]string{}
    93  	hash, err := hashutil.DeepHashObject(&svc.Spec)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  	labels[ServiceHashLabelKey] = hash
    98  	labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue
    99  	svc.SetLabels(labels)
   100  	ownerutil.AddOwner(svc, s.CatalogSource, false, false)
   101  	return svc, nil
   102  }
   103  
   104  func (s *grpcCatalogSourceDecorator) ServiceAccount() *corev1.ServiceAccount {
   105  	var secrets []corev1.LocalObjectReference
   106  	blockOwnerDeletion := true
   107  	isController := true
   108  	for _, secretName := range s.CatalogSource.Spec.Secrets {
   109  		if secretName == "" {
   110  			continue
   111  		}
   112  		secrets = append(secrets, corev1.LocalObjectReference{Name: secretName})
   113  	}
   114  	return &corev1.ServiceAccount{
   115  		ObjectMeta: metav1.ObjectMeta{
   116  			Name:      s.GetName(),
   117  			Namespace: s.GetNamespace(),
   118  			Labels:    map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
   119  			OwnerReferences: []metav1.OwnerReference{
   120  				{
   121  					Name:               s.GetName(),
   122  					Kind:               v1alpha1.CatalogSourceKind,
   123  					APIVersion:         v1alpha1.CatalogSourceCRDAPIVersion,
   124  					UID:                s.GetUID(),
   125  					Controller:         &isController,
   126  					BlockOwnerDeletion: &blockOwnerDeletion,
   127  				},
   128  			},
   129  		},
   130  		ImagePullSecrets: secrets,
   131  	}
   132  }
   133  
   134  func (s *grpcCatalogSourceDecorator) Pod(serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig) (*corev1.Pod, error) {
   135  	pod, err := Pod(s.CatalogSource, "registry-server", s.opmImage, s.utilImage, s.Spec.Image, serviceAccount, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser, defaultPodSecurityConfig)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  	ownerutil.AddOwner(pod, s.CatalogSource, false, true)
   140  	return pod, nil
   141  }
   142  
   143  type GrpcRegistryReconciler struct {
   144  	now             nowFunc
   145  	Lister          operatorlister.OperatorLister
   146  	OpClient        operatorclient.ClientInterface
   147  	SSAClient       *controllerclient.ServerSideApplier
   148  	createPodAsUser int64
   149  	opmImage        string
   150  	utilImage       string
   151  }
   152  
   153  var _ RegistryReconciler = &GrpcRegistryReconciler{}
   154  
   155  func (c *GrpcRegistryReconciler) currentService(source grpcCatalogSourceDecorator) (*corev1.Service, error) {
   156  	protoService, err := source.Service()
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	serviceName := protoService.GetName()
   161  	service, err := c.Lister.CoreV1().ServiceLister().Services(source.GetNamespace()).Get(serviceName)
   162  	if err != nil {
   163  		logrus.WithField("service", serviceName).Debug("couldn't find service in cache")
   164  		return nil, nil
   165  	}
   166  	return service, nil
   167  }
   168  
   169  func (c *GrpcRegistryReconciler) currentServiceAccount(source grpcCatalogSourceDecorator) *corev1.ServiceAccount {
   170  	serviceAccountName := source.ServiceAccount().GetName()
   171  	serviceAccount, err := c.Lister.CoreV1().ServiceAccountLister().ServiceAccounts(source.GetNamespace()).Get(serviceAccountName)
   172  	if err != nil {
   173  		logrus.WithField("serviceAccount", serviceAccount).Debug("couldn't find serviceAccount in cache")
   174  		return nil
   175  	}
   176  	return serviceAccount
   177  }
   178  
   179  func (c *GrpcRegistryReconciler) currentPods(logger *logrus.Entry, source grpcCatalogSourceDecorator) []*corev1.Pod {
   180  	pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(source.Selector())
   181  	if err != nil {
   182  		logger.WithError(err).Warn("couldn't find pod in cache")
   183  		return nil
   184  	}
   185  	if len(pods) > 1 {
   186  		logger.WithField("selector", source.Selector()).Info("multiple pods found for selector")
   187  	}
   188  	return pods
   189  }
   190  
   191  func (c *GrpcRegistryReconciler) currentUpdatePods(logger *logrus.Entry, source grpcCatalogSourceDecorator) []*corev1.Pod {
   192  	pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(source.SelectorForUpdate())
   193  	if err != nil {
   194  		logger.WithError(err).Warn("couldn't find pod in cache")
   195  		return nil
   196  	}
   197  	if len(pods) > 1 {
   198  		logger.WithField("selector", source.Selector()).Info("multiple update pods found for selector")
   199  	}
   200  	return pods
   201  }
   202  
   203  func (c *GrpcRegistryReconciler) currentPodsWithCorrectImageAndSpec(logger *logrus.Entry, source grpcCatalogSourceDecorator, serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig) ([]*corev1.Pod, error) {
   204  	logger.Info("searching for current pods")
   205  	pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(labels.SelectorFromValidatedSet(source.Labels()))
   206  	if err != nil {
   207  		logger.WithError(err).Warn("couldn't find pod in cache")
   208  		return nil, nil
   209  	}
   210  	found := []*corev1.Pod{}
   211  	newPod, err := source.Pod(serviceAccount, defaultPodSecurityConfig)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	for _, p := range pods {
   216  		images, hash := correctImages(source, p), podHashMatch(p, newPod)
   217  		logger = logger.WithFields(logrus.Fields{
   218  			"current-pod.namespace": p.Namespace, "current-pod.name": p.Name,
   219  			"correctImages": images, "correctHash": hash,
   220  		})
   221  		logger.Info("evaluating current pod")
   222  		if !hash {
   223  			logger.Infof("pod spec diff: %s", cmp.Diff(p.Spec, newPod.Spec))
   224  		}
   225  		if correctImages(source, p) && podHashMatch(p, newPod) {
   226  			found = append(found, p)
   227  		}
   228  	}
   229  	logger.Infof("of %d pods matching label selector, %d have the correct images and matching hash", len(pods), len(found))
   230  	return found, nil
   231  }
   232  
   233  func correctImages(source grpcCatalogSourceDecorator, pod *corev1.Pod) bool {
   234  	if source.CatalogSource.Spec.GrpcPodConfig != nil && source.CatalogSource.Spec.GrpcPodConfig.ExtractContent != nil {
   235  		if len(pod.Spec.InitContainers) != 2 {
   236  			return false
   237  		}
   238  		if len(pod.Spec.Containers) != 1 {
   239  			return false
   240  		}
   241  		return pod.Spec.InitContainers[0].Image == source.utilImage &&
   242  			pod.Spec.InitContainers[1].Image == source.CatalogSource.Spec.Image &&
   243  			pod.Spec.Containers[0].Image == source.opmImage
   244  	}
   245  	return pod.Spec.Containers[0].Image == source.CatalogSource.Spec.Image
   246  }
   247  
   248  // EnsureRegistryServer ensures that all components of registry server are up to date.
   249  func (c *GrpcRegistryReconciler) EnsureRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) error {
   250  	source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage, utilImage: c.utilImage}
   251  
   252  	// if service status is nil, we force create every object to ensure they're created the first time
   253  	valid, err := isRegistryServiceStatusValid(&source)
   254  	if err != nil {
   255  		return err
   256  	}
   257  	overwrite := !valid
   258  	if overwrite {
   259  		logger.Info("registry service status invalid, need to overwrite")
   260  	}
   261  
   262  	//TODO: if any of these error out, we should write a status back (possibly set RegistryServiceStatus to nil so they get recreated)
   263  	sa, err := c.ensureSA(source)
   264  	if err != nil && !apierrors.IsAlreadyExists(err) {
   265  		return pkgerrors.Wrapf(err, "error ensuring service account: %s", source.GetName())
   266  	}
   267  
   268  	sa, err = c.OpClient.GetServiceAccount(sa.GetNamespace(), sa.GetName())
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	defaultPodSecurityConfig, err := getDefaultPodContextConfig(c.OpClient, catalogSource.GetNamespace())
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	// recreate the pod if no existing pod is serving the latest image or correct spec
   279  	current, err := c.currentPodsWithCorrectImageAndSpec(logger, source, sa, defaultPodSecurityConfig)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	overwritePod := overwrite || len(current) == 0
   284  	if overwritePod {
   285  		logger.Info("registry pods invalid, need to overwrite")
   286  	}
   287  
   288  	pod, err := source.Pod(sa, defaultPodSecurityConfig)
   289  	if err != nil {
   290  		return err
   291  	}
   292  	if err := c.ensurePod(logger, source, sa, defaultPodSecurityConfig, overwritePod); err != nil {
   293  		return pkgerrors.Wrapf(err, "error ensuring pod: %s", pod.GetName())
   294  	}
   295  	if err := c.ensureUpdatePod(logger, sa, defaultPodSecurityConfig, source); err != nil {
   296  		if _, ok := err.(UpdateNotReadyErr); ok {
   297  			return err
   298  		}
   299  		return pkgerrors.Wrapf(err, "error ensuring updated catalog source pod: %s", pod.GetName())
   300  	}
   301  	service, err := source.Service()
   302  	if err != nil {
   303  		return err
   304  	}
   305  	if err := c.ensureService(source, overwrite); err != nil {
   306  		return pkgerrors.Wrapf(err, "error ensuring service: %s", service.GetName())
   307  	}
   308  
   309  	if overwritePod {
   310  		now := c.now()
   311  		service, err := source.Service()
   312  		if err != nil {
   313  			return err
   314  		}
   315  		catalogSource.Status.RegistryServiceStatus = &v1alpha1.RegistryServiceStatus{
   316  			CreatedAt:        now,
   317  			Protocol:         "grpc",
   318  			ServiceName:      service.GetName(),
   319  			ServiceNamespace: source.GetNamespace(),
   320  			Port:             getPort(service),
   321  		}
   322  	}
   323  	return nil
   324  }
   325  
   326  func getPort(service *corev1.Service) string {
   327  	return fmt.Sprintf("%d", service.Spec.Ports[0].Port)
   328  }
   329  
   330  func isRegistryServiceStatusValid(source *grpcCatalogSourceDecorator) (bool, error) {
   331  	service, err := source.Service()
   332  	if err != nil {
   333  		return false, err
   334  	}
   335  	if source.Status.RegistryServiceStatus == nil ||
   336  		source.Status.RegistryServiceStatus.ServiceName != service.GetName() ||
   337  		source.Status.RegistryServiceStatus.ServiceNamespace != service.GetNamespace() ||
   338  		source.Status.RegistryServiceStatus.Port != getPort(service) ||
   339  		source.Status.RegistryServiceStatus.Protocol != "grpc" {
   340  		return false, nil
   341  	}
   342  	return true, nil
   343  }
   344  
   345  func (c *GrpcRegistryReconciler) ensurePod(logger *logrus.Entry, source grpcCatalogSourceDecorator, serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig, overwrite bool) error {
   346  	// currentPods refers to the current pod instances of the catalog source
   347  	currentPods := c.currentPods(logger, source)
   348  	if len(currentPods) > 0 {
   349  		if !overwrite {
   350  			return nil
   351  		}
   352  		for _, p := range currentPods {
   353  			logger.WithFields(logrus.Fields{"pod.namespace": source.GetNamespace(), "pod.name": p.GetName()}).Info("deleting current pod")
   354  			if err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Delete(context.TODO(), p.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) {
   355  				return pkgerrors.Wrapf(err, "error deleting old pod: %s", p.GetName())
   356  			}
   357  		}
   358  	}
   359  	desiredPod, err := source.Pod(serviceAccount, defaultPodSecurityConfig)
   360  	if err != nil {
   361  		return err
   362  	}
   363  	logger.WithFields(logrus.Fields{"pod.namespace": desiredPod.GetNamespace(), "pod.name": desiredPod.GetName()}).Info("creating desired pod")
   364  	_, err = c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Create(context.TODO(), desiredPod, metav1.CreateOptions{})
   365  	if err != nil {
   366  		return pkgerrors.Wrapf(err, "error creating new pod: %s", desiredPod.GetGenerateName())
   367  	}
   368  
   369  	return nil
   370  }
   371  
   372  // ensureUpdatePod checks that for the same catalog source version the same container imageID is running
   373  func (c *GrpcRegistryReconciler) ensureUpdatePod(logger *logrus.Entry, serviceAccount *corev1.ServiceAccount, podSecurityConfig v1alpha1.SecurityConfig, source grpcCatalogSourceDecorator) error {
   374  	if !source.Poll() {
   375  		logger.Info("polling not enabled, no update pod will be created")
   376  		return nil
   377  	}
   378  
   379  	currentLivePods := c.currentPods(logger, source)
   380  	currentUpdatePods := c.currentUpdatePods(logger, source)
   381  
   382  	if source.Update() && len(currentUpdatePods) == 0 {
   383  		logger.Infof("catalog update required at %s", time.Now().String())
   384  		pod, err := c.createUpdatePod(source, serviceAccount, podSecurityConfig)
   385  		if err != nil {
   386  			return pkgerrors.Wrapf(err, "creating update catalog source pod")
   387  		}
   388  		source.SetLastUpdateTime()
   389  		return UpdateNotReadyErr{catalogName: source.GetName(), podName: pod.GetName()}
   390  	}
   391  
   392  	// check if update pod is ready - if not requeue the sync
   393  	// if update pod failed (potentially due to a bad catalog image) delete it
   394  	for _, p := range currentUpdatePods {
   395  		fail, err := c.podFailed(p)
   396  		if err != nil {
   397  			return err
   398  		}
   399  		if fail {
   400  			return fmt.Errorf("update pod %s in a %s state: deleted update pod", p.GetName(), p.Status.Phase)
   401  		}
   402  		if !podReady(p) {
   403  			return UpdateNotReadyErr{catalogName: source.GetName(), podName: p.GetName()}
   404  		}
   405  	}
   406  
   407  	for _, updatePod := range currentUpdatePods {
   408  		// if container imageID IDs are different, switch the serving pods
   409  		if imageChanged(logger, updatePod, currentLivePods) {
   410  			err := c.promoteCatalog(updatePod, source.GetName())
   411  			if err != nil {
   412  				return fmt.Errorf("detected imageID change: error during update: %s", err)
   413  			}
   414  			// remove old catalog source pod
   415  			for _, p := range currentLivePods {
   416  				logger.WithFields(logrus.Fields{"live-pod.namespace": source.GetNamespace(), "live-pod.name": p.Name}).Info("deleting current live pods")
   417  				if err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Delete(context.TODO(), p.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) {
   418  					return pkgerrors.Wrapf(pkgerrors.Wrapf(err, "error deleting pod: %s", p.GetName()), "detected imageID change: error deleting old catalog source pod")
   419  				}
   420  			}
   421  			// done syncing
   422  			logger.Infof("detected imageID change: catalogsource pod updated at %s", time.Now().String())
   423  			return nil
   424  		}
   425  		// delete update pod right away, since the digest match, to prevent long-lived duplicate catalog pods
   426  		logger.WithFields(logrus.Fields{"update-pod.namespace": updatePod.Namespace, "update-pod.name": updatePod.Name}).Debug("catalog polling result: no update; removing duplicate update pod")
   427  		if err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Delete(context.TODO(), updatePod.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) {
   428  			return pkgerrors.Wrapf(pkgerrors.Wrapf(err, "error deleting pod: %s", updatePod.GetName()), "duplicate catalog polling pod")
   429  		}
   430  	}
   431  
   432  	return nil
   433  }
   434  
   435  func (c *GrpcRegistryReconciler) ensureService(source grpcCatalogSourceDecorator, overwrite bool) error {
   436  	service, err := source.Service()
   437  	if err != nil {
   438  		return err
   439  	}
   440  	svc, err := c.currentService(source)
   441  	if err != nil {
   442  		return err
   443  	}
   444  	if svc != nil {
   445  		if !overwrite && ServiceHashMatch(svc, service) {
   446  			return nil
   447  		}
   448  		// TODO(tflannag): Do we care about force deleting services?
   449  		if err := c.OpClient.DeleteService(service.GetNamespace(), service.GetName(), metav1.NewDeleteOptions(0)); err != nil && !apierrors.IsNotFound(err) {
   450  			return err
   451  		}
   452  	}
   453  	_, err = c.OpClient.CreateService(service)
   454  	return err
   455  }
   456  
   457  func (c *GrpcRegistryReconciler) ensureSA(source grpcCatalogSourceDecorator) (*corev1.ServiceAccount, error) {
   458  	sa := source.ServiceAccount()
   459  	if _, err := c.OpClient.CreateServiceAccount(sa); err != nil {
   460  		return sa, err
   461  	}
   462  	return sa, nil
   463  }
   464  
   465  // ServiceHashMatch will check the hash info in existing Service to ensure its
   466  // hash info matches the desired Service's hash.
   467  func ServiceHashMatch(existing, new *corev1.Service) bool {
   468  	labels := existing.GetLabels()
   469  	newLabels := new.GetLabels()
   470  	if len(labels) == 0 || len(newLabels) == 0 {
   471  		return false
   472  	}
   473  
   474  	existingSvcSpecHash, ok := labels[ServiceHashLabelKey]
   475  	if !ok {
   476  		return false
   477  	}
   478  
   479  	newSvcSpecHash, ok := newLabels[ServiceHashLabelKey]
   480  	if !ok {
   481  		return false
   482  	}
   483  
   484  	if existingSvcSpecHash != newSvcSpecHash {
   485  		return false
   486  	}
   487  
   488  	return true
   489  }
   490  
   491  // createUpdatePod is an internal method that creates a pod using the latest catalog source.
   492  func (c *GrpcRegistryReconciler) createUpdatePod(source grpcCatalogSourceDecorator, serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig) (*corev1.Pod, error) {
   493  	// remove label from pod to ensure service does not accidentally route traffic to the pod
   494  	p, err := source.Pod(serviceAccount, defaultPodSecurityConfig)
   495  	if err != nil {
   496  		return nil, err
   497  	}
   498  	p = swapLabels(p, "", source.Name)
   499  
   500  	pod, err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Create(context.TODO(), p, metav1.CreateOptions{})
   501  	if err != nil {
   502  		logrus.WithField("pod", p.GetName()).Warn("couldn't create new catalogsource pod")
   503  		return nil, err
   504  	}
   505  
   506  	return pod, nil
   507  }
   508  
   509  // checkUpdatePodDigest checks update pod to get Image ID and see if it matches the serving (live) pod ImageID
   510  func imageChanged(logger *logrus.Entry, updatePod *corev1.Pod, servingPods []*corev1.Pod) bool {
   511  	updatedCatalogSourcePodImageID := imageID(updatePod)
   512  	if updatedCatalogSourcePodImageID == "" {
   513  		logger.WithField("update-pod.name", updatePod.GetName()).Warn("pod status unknown, cannot get the updated pod's imageID")
   514  		return false
   515  	}
   516  	for _, servingPod := range servingPods {
   517  		servingCatalogSourcePodImageID := imageID(servingPod)
   518  		if servingCatalogSourcePodImageID == "" {
   519  			logger.WithField("serving-pod.name", servingPod.GetName()).Warn("pod status unknown, cannot get the current pod's imageID")
   520  			return false
   521  		}
   522  		if updatedCatalogSourcePodImageID != servingCatalogSourcePodImageID {
   523  			logger.WithField("serving-pod.name", servingPod.GetName()).Infof("catalog image changed: serving pod %s update pod %s", servingCatalogSourcePodImageID, updatedCatalogSourcePodImageID)
   524  			return true
   525  		}
   526  	}
   527  
   528  	return false
   529  }
   530  
   531  func isPodDead(pod *corev1.Pod) bool {
   532  	for _, check := range []func(*corev1.Pod) bool{
   533  		isPodDeletedByTaintManager,
   534  	} {
   535  		if check(pod) {
   536  			return true
   537  		}
   538  	}
   539  	return false
   540  }
   541  
   542  func isPodDeletedByTaintManager(pod *corev1.Pod) bool {
   543  	if pod.DeletionTimestamp == nil {
   544  		return false
   545  	}
   546  	for _, condition := range pod.Status.Conditions {
   547  		if condition.Type == corev1.DisruptionTarget && condition.Reason == "DeletionByTaintManager" && condition.Status == corev1.ConditionTrue {
   548  			return true
   549  		}
   550  	}
   551  	return false
   552  }
   553  
   554  // imageID returns the ImageID of the primary catalog source container or an empty string if the image ID isn't available yet.
   555  // Note: the pod must be running and the container in a ready status to return a valid ImageID.
   556  func imageID(pod *corev1.Pod) string {
   557  	if len(pod.Status.InitContainerStatuses) == 2 && len(pod.Status.ContainerStatuses) == 1 {
   558  		// spec.grpcPodConfig.extractContent mode was used for this pod
   559  		return pod.Status.InitContainerStatuses[1].ImageID
   560  	}
   561  	if len(pod.Status.InitContainerStatuses) == 0 && len(pod.Status.ContainerStatuses) == 1 {
   562  		// spec.grpcPodConfig.extractContent mode was NOT used for this pod (i.e. we're just running the catalog image directly)
   563  		return pod.Status.ContainerStatuses[0].ImageID
   564  	}
   565  	if len(pod.Status.InitContainerStatuses) == 0 && len(pod.Status.ContainerStatuses) == 0 {
   566  		logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod has not yet populated initContainer and container status")
   567  	} else {
   568  		logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod contains unexpected initContainer and container configuration")
   569  	}
   570  	return ""
   571  }
   572  
   573  func (c *GrpcRegistryReconciler) removePods(pods []*corev1.Pod, namespace string) error {
   574  	for _, p := range pods {
   575  		if err := c.OpClient.KubernetesInterface().CoreV1().Pods(namespace).Delete(context.TODO(), p.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) {
   576  			return pkgerrors.Wrapf(err, "error deleting pod: %s", p.GetName())
   577  		}
   578  	}
   579  	return nil
   580  }
   581  
   582  // CheckRegistryServer returns true if the given CatalogSource is considered healthy; false otherwise.
   583  func (c *GrpcRegistryReconciler) CheckRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) (bool, error) {
   584  	source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage, utilImage: c.utilImage}
   585  
   586  	// The CheckRegistryServer function is called by the CatalogSoruce controller before the registry resources are created,
   587  	// returning a IsNotFound error will cause the controller to exit and never create the resources, so we should
   588  	// only return an error if it is something other than a NotFound error.
   589  	serviceAccount := source.ServiceAccount()
   590  	serviceAccount, err := c.OpClient.GetServiceAccount(serviceAccount.GetNamespace(), serviceAccount.GetName())
   591  	if err != nil {
   592  		if !apierrors.IsNotFound(err) {
   593  			return false, err
   594  		}
   595  		return false, nil
   596  	}
   597  
   598  	registryPodSecurityConfig, err := getDefaultPodContextConfig(c.OpClient, catalogSource.GetNamespace())
   599  	if err != nil {
   600  		return false, err
   601  	}
   602  
   603  	// Check on registry resources
   604  	// TODO: add gRPC health check
   605  	service, err := c.currentService(source)
   606  	if err != nil {
   607  		return false, err
   608  	}
   609  	currentPods, err := c.currentPodsWithCorrectImageAndSpec(logger, source, serviceAccount, registryPodSecurityConfig)
   610  	if err != nil {
   611  		return false, err
   612  	}
   613  	if len(currentPods) < 1 ||
   614  		service == nil || c.currentServiceAccount(source) == nil {
   615  		return false, nil
   616  	}
   617  	podsAreLive, e := detectAndDeleteDeadPods(logger, c.OpClient, currentPods, source.GetNamespace())
   618  	if e != nil {
   619  		return false, fmt.Errorf("error deleting dead pods: %v", e)
   620  	}
   621  	return podsAreLive, nil
   622  }
   623  
   624  // promoteCatalog swaps the labels on the update pod so that the update pod is now reachable by the catalog service.
   625  // By updating the catalog on cluster it promotes the update pod to act as the new version of the catalog on-cluster.
   626  func (c *GrpcRegistryReconciler) promoteCatalog(updatePod *corev1.Pod, key string) error {
   627  	// Update the update pod to promote it to serving pod via the SSA client
   628  	err := c.SSAClient.Apply(context.TODO(), updatePod, func(p *corev1.Pod) error {
   629  		p.Labels[CatalogSourceLabelKey] = key
   630  		p.Labels[CatalogSourceUpdateKey] = ""
   631  		return nil
   632  	})()
   633  
   634  	return err
   635  }
   636  
   637  // podReady returns true if the given Pod has a ready status condition.
   638  func podReady(pod *corev1.Pod) bool {
   639  	if pod.Status.Conditions == nil {
   640  		return false
   641  	}
   642  	for _, cond := range pod.Status.Conditions {
   643  		if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue {
   644  			return true
   645  		}
   646  	}
   647  	return false
   648  }
   649  
   650  func swapLabels(pod *corev1.Pod, labelKey, updateKey string) *corev1.Pod {
   651  	pod.Labels[CatalogSourceLabelKey] = labelKey
   652  	pod.Labels[CatalogSourceUpdateKey] = updateKey
   653  	return pod
   654  }
   655  
   656  // podFailed checks whether the pod status is in a failed or unknown state, and deletes the pod if so.
   657  func (c *GrpcRegistryReconciler) podFailed(pod *corev1.Pod) (bool, error) {
   658  	if pod.Status.Phase == corev1.PodFailed || pod.Status.Phase == corev1.PodUnknown {
   659  		logrus.WithField("UpdatePod", pod.GetName()).Infof("catalog polling result: update pod %s failed to start", pod.GetName())
   660  		err := c.removePods([]*corev1.Pod{pod}, pod.GetNamespace())
   661  		if err != nil {
   662  			return true, pkgerrors.Wrapf(err, "error deleting failed catalog polling pod: %s", pod.GetName())
   663  		}
   664  		return true, nil
   665  	}
   666  	return false, nil
   667  }
   668  
   669  // podHashMatch will check the hash info in existing pod to ensure its
   670  // hash info matches the desired Service's hash.
   671  func podHashMatch(existing, new *corev1.Pod) bool {
   672  	labels := existing.GetLabels()
   673  	newLabels := new.GetLabels()
   674  	// If both new & existing pods don't have labels, consider it not matched
   675  	if len(labels) == 0 || len(newLabels) == 0 {
   676  		return false
   677  	}
   678  
   679  	existingPodSpecHash, ok := labels[PodHashLabelKey]
   680  	if !ok {
   681  		return false
   682  	}
   683  
   684  	newPodSpecHash, ok := newLabels[PodHashLabelKey]
   685  	if !ok {
   686  		return false
   687  	}
   688  
   689  	if existingPodSpecHash != newPodSpecHash {
   690  		return false
   691  	}
   692  
   693  	return true
   694  }