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

     1  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -o ../../../fakes/fake_reconciler.go . RegistryReconciler
     2  package reconciler
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  
     9  	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
    10  	hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash"
    11  	pkgerrors "github.com/pkg/errors"
    12  	"github.com/sirupsen/logrus"
    13  	corev1 "k8s.io/api/core/v1"
    14  	rbacv1 "k8s.io/api/rbac/v1"
    15  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/labels"
    18  	"k8s.io/apimachinery/pkg/util/intstr"
    19  	"k8s.io/utils/ptr"
    20  
    21  	"github.com/operator-framework/api/pkg/operators/v1alpha1"
    22  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
    23  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
    24  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
    25  )
    26  
    27  // configMapCatalogSourceDecorator wraps CatalogSource to add additional methods
    28  type configMapCatalogSourceDecorator struct {
    29  	*v1alpha1.CatalogSource
    30  	runAsUser int64
    31  }
    32  
    33  const (
    34  	// ConfigMapServerPostfix is a postfix appended to the names of resources generated for a ConfigMap server.
    35  	ConfigMapServerPostfix string = "-configmap-server"
    36  )
    37  
    38  func (s *configMapCatalogSourceDecorator) serviceAccountName() string {
    39  	return s.GetName() + ConfigMapServerPostfix
    40  }
    41  
    42  func (s *configMapCatalogSourceDecorator) roleName() string {
    43  	return s.GetName() + "-configmap-reader"
    44  }
    45  
    46  func (s *configMapCatalogSourceDecorator) Selector() map[string]string {
    47  	return map[string]string{
    48  		CatalogSourceLabelKey: s.GetName(),
    49  	}
    50  }
    51  
    52  const (
    53  	// ConfigMapRVLabelKey is the key for a label used to track the resource version of a related ConfigMap.
    54  	ConfigMapRVLabelKey string = "olm.configMapResourceVersion"
    55  )
    56  
    57  func (s *configMapCatalogSourceDecorator) Labels() map[string]string {
    58  	labels := map[string]string{
    59  		CatalogSourceLabelKey:      s.GetName(),
    60  		install.OLMManagedLabelKey: install.OLMManagedLabelValue,
    61  	}
    62  	if s.Spec.SourceType == v1alpha1.SourceTypeInternal || s.Spec.SourceType == v1alpha1.SourceTypeConfigmap {
    63  		labels[ConfigMapRVLabelKey] = s.Status.ConfigMapResource.ResourceVersion
    64  	}
    65  	return labels
    66  }
    67  
    68  func (s *configMapCatalogSourceDecorator) Annotations() map[string]string {
    69  	// TODO: Maybe something better than just a copy of all annotations would be to have a specific 'podMetadata' section in the CatalogSource?
    70  	return s.GetAnnotations()
    71  }
    72  
    73  func (s *configMapCatalogSourceDecorator) ConfigMapChanges(configMap *corev1.ConfigMap) bool {
    74  	if s.Status.ConfigMapResource == nil {
    75  		return true
    76  	}
    77  	if s.Status.ConfigMapResource.ResourceVersion == configMap.GetResourceVersion() {
    78  		return false
    79  	}
    80  	return true
    81  }
    82  
    83  func (s *configMapCatalogSourceDecorator) Service() (*corev1.Service, error) {
    84  	svc := &corev1.Service{
    85  		ObjectMeta: metav1.ObjectMeta{
    86  			Name:      s.GetName(),
    87  			Namespace: s.GetNamespace(),
    88  		},
    89  		Spec: corev1.ServiceSpec{
    90  			Ports: []corev1.ServicePort{
    91  				{
    92  					Name:       "grpc",
    93  					Port:       50051,
    94  					TargetPort: intstr.FromInt(50051),
    95  				},
    96  			},
    97  			Selector: s.Selector(),
    98  		},
    99  	}
   100  
   101  	labels := map[string]string{
   102  		install.OLMManagedLabelKey: install.OLMManagedLabelValue,
   103  	}
   104  	hash, err := hashutil.DeepHashObject(&svc.Spec)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	labels[ServiceHashLabelKey] = hash
   109  	svc.SetLabels(labels)
   110  	ownerutil.AddOwner(svc, s.CatalogSource, false, false)
   111  	return svc, nil
   112  }
   113  
   114  func (s *configMapCatalogSourceDecorator) Pod(image string, defaultPodSecurityConfig v1alpha1.SecurityConfig) (*corev1.Pod, error) {
   115  	pod, err := Pod(s.CatalogSource, "configmap-registry-server", "", "", image, nil, s.Labels(), s.Annotations(), 5, 5, s.runAsUser, defaultPodSecurityConfig)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	pod.Spec.ServiceAccountName = s.GetName() + ConfigMapServerPostfix
   120  	pod.Spec.Containers[0].Command = []string{"configmap-server", "-c", s.Spec.ConfigMap, "-n", s.GetNamespace()}
   121  	ownerutil.AddOwner(pod, s.CatalogSource, false, true)
   122  	return pod, nil
   123  }
   124  
   125  func (s *configMapCatalogSourceDecorator) ServiceAccount() *corev1.ServiceAccount {
   126  	sa := &corev1.ServiceAccount{
   127  		ObjectMeta: metav1.ObjectMeta{
   128  			Name:      s.serviceAccountName(),
   129  			Namespace: s.GetNamespace(),
   130  			Labels: map[string]string{
   131  				install.OLMManagedLabelKey: install.OLMManagedLabelValue,
   132  			},
   133  		},
   134  	}
   135  	ownerutil.AddOwner(sa, s.CatalogSource, false, false)
   136  	return sa
   137  }
   138  
   139  func (s *configMapCatalogSourceDecorator) Role() *rbacv1.Role {
   140  	role := &rbacv1.Role{
   141  		ObjectMeta: metav1.ObjectMeta{
   142  			Name:      s.roleName(),
   143  			Namespace: s.GetNamespace(),
   144  			Labels: map[string]string{
   145  				install.OLMManagedLabelKey: install.OLMManagedLabelValue,
   146  			},
   147  		},
   148  		Rules: []rbacv1.PolicyRule{
   149  			{
   150  				Verbs:         []string{"get"},
   151  				APIGroups:     []string{""},
   152  				Resources:     []string{"configmaps"},
   153  				ResourceNames: []string{s.Spec.ConfigMap},
   154  			},
   155  		},
   156  	}
   157  	ownerutil.AddOwner(role, s.CatalogSource, false, false)
   158  	return role
   159  }
   160  
   161  func (s *configMapCatalogSourceDecorator) RoleBinding() *rbacv1.RoleBinding {
   162  	rb := &rbacv1.RoleBinding{
   163  		ObjectMeta: metav1.ObjectMeta{
   164  			Name:      s.GetName() + "-server-configmap-reader",
   165  			Namespace: s.GetNamespace(),
   166  			Labels: map[string]string{
   167  				install.OLMManagedLabelKey: install.OLMManagedLabelValue,
   168  			},
   169  		},
   170  		Subjects: []rbacv1.Subject{
   171  			{
   172  				Kind:      "ServiceAccount",
   173  				Name:      s.serviceAccountName(),
   174  				Namespace: s.GetNamespace(),
   175  			},
   176  		},
   177  		RoleRef: rbacv1.RoleRef{
   178  			APIGroup: "rbac.authorization.k8s.io",
   179  			Kind:     "Role",
   180  			Name:     s.roleName(),
   181  		},
   182  	}
   183  	ownerutil.AddOwner(rb, s.CatalogSource, false, false)
   184  	return rb
   185  }
   186  
   187  type ConfigMapRegistryReconciler struct {
   188  	now             nowFunc
   189  	Lister          operatorlister.OperatorLister
   190  	OpClient        operatorclient.ClientInterface
   191  	Image           string
   192  	createPodAsUser int64
   193  }
   194  
   195  var _ RegistryEnsurer = &ConfigMapRegistryReconciler{}
   196  var _ RegistryChecker = &ConfigMapRegistryReconciler{}
   197  var _ RegistryReconciler = &ConfigMapRegistryReconciler{}
   198  
   199  func (c *ConfigMapRegistryReconciler) currentService(source configMapCatalogSourceDecorator) (*corev1.Service, error) {
   200  	protoService, err := source.Service()
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  	serviceName := protoService.GetName()
   205  	service, err := c.Lister.CoreV1().ServiceLister().Services(source.GetNamespace()).Get(serviceName)
   206  	if err != nil {
   207  		logrus.WithField("service", serviceName).Debug("couldn't find service in cache")
   208  		return nil, nil
   209  	}
   210  	return service, nil
   211  }
   212  
   213  func (c *ConfigMapRegistryReconciler) currentServiceAccount(source configMapCatalogSourceDecorator) *corev1.ServiceAccount {
   214  	serviceAccountName := source.ServiceAccount().GetName()
   215  	serviceAccount, err := c.Lister.CoreV1().ServiceAccountLister().ServiceAccounts(source.GetNamespace()).Get(serviceAccountName)
   216  	if err != nil {
   217  		logrus.WithField("serviceAccouint", serviceAccountName).WithError(err).Debug("couldn't find service account in cache")
   218  		return nil
   219  	}
   220  	return serviceAccount
   221  }
   222  
   223  func (c *ConfigMapRegistryReconciler) currentRole(source configMapCatalogSourceDecorator) *rbacv1.Role {
   224  	roleName := source.Role().GetName()
   225  	role, err := c.Lister.RbacV1().RoleLister().Roles(source.GetNamespace()).Get(roleName)
   226  	if err != nil {
   227  		logrus.WithField("role", roleName).WithError(err).Debug("couldn't find role in cache")
   228  		return nil
   229  	}
   230  	return role
   231  }
   232  
   233  func (c *ConfigMapRegistryReconciler) currentRoleBinding(source configMapCatalogSourceDecorator) *rbacv1.RoleBinding {
   234  	roleBindingName := source.RoleBinding().GetName()
   235  	roleBinding, err := c.Lister.RbacV1().RoleBindingLister().RoleBindings(source.GetNamespace()).Get(roleBindingName)
   236  	if err != nil {
   237  		logrus.WithField("roleBinding", roleBindingName).WithError(err).Debug("couldn't find role binding in cache")
   238  		return nil
   239  	}
   240  	return roleBinding
   241  }
   242  
   243  func (c *ConfigMapRegistryReconciler) currentPods(source configMapCatalogSourceDecorator, image string, defaultPodSecurityConfig v1alpha1.SecurityConfig) ([]*corev1.Pod, error) {
   244  	protoPod, err := source.Pod(image, defaultPodSecurityConfig)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	podName := protoPod.GetName()
   249  	pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(labels.SelectorFromSet(source.Selector()))
   250  	if err != nil {
   251  		logrus.WithField("pod", podName).WithError(err).Debug("couldn't find pod in cache")
   252  		return nil, nil
   253  	}
   254  	if len(pods) > 1 {
   255  		logrus.WithField("selector", source.Selector()).Debug("multiple pods found for selector")
   256  	}
   257  	return pods, nil
   258  }
   259  
   260  func (c *ConfigMapRegistryReconciler) currentPodsWithCorrectResourceVersion(source configMapCatalogSourceDecorator, image string, defaultPodSecurityConfig v1alpha1.SecurityConfig) ([]*corev1.Pod, error) {
   261  	protoPod, err := source.Pod(image, defaultPodSecurityConfig)
   262  	if err != nil {
   263  		return nil, err
   264  	}
   265  	podName := protoPod.GetName()
   266  	pods, err := c.Lister.CoreV1().PodLister().Pods(source.GetNamespace()).List(labels.SelectorFromValidatedSet(source.Labels()))
   267  	if err != nil {
   268  		logrus.WithField("pod", podName).WithError(err).Debug("couldn't find pod in cache")
   269  		return nil, nil
   270  	}
   271  	if len(pods) > 1 {
   272  		logrus.WithField("selector", source.Labels()).Debug("multiple pods found for selector")
   273  	}
   274  	return pods, nil
   275  }
   276  
   277  // EnsureRegistryServer ensures that all components of registry server are up to date.
   278  func (c *ConfigMapRegistryReconciler) EnsureRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) error {
   279  	source := configMapCatalogSourceDecorator{catalogSource, c.createPodAsUser}
   280  
   281  	image := c.Image
   282  	if source.Spec.SourceType == "grpc" {
   283  		image = source.Spec.Image
   284  	}
   285  	if image == "" {
   286  		return fmt.Errorf("no image for registry")
   287  	}
   288  
   289  	// if service status is nil, we force create every object to ensure they're created the first time
   290  	overwrite := source.Status.RegistryServiceStatus == nil
   291  	overwritePod := overwrite
   292  
   293  	defaultPodSecurityConfig, err := getDefaultPodContextConfig(c.OpClient, catalogSource.GetNamespace())
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	if source.Spec.SourceType == v1alpha1.SourceTypeConfigmap || source.Spec.SourceType == v1alpha1.SourceTypeInternal {
   299  		// fetch configmap first, exit early if we can't find it
   300  		// we use the live client here instead of a lister since our listers are scoped to objects with the olm.managed label,
   301  		// and this configmap is a user-provided input to the catalog source and will not have that label
   302  		configMap, err := c.OpClient.KubernetesInterface().CoreV1().ConfigMaps(source.GetNamespace()).Get(context.TODO(), source.Spec.ConfigMap, metav1.GetOptions{})
   303  		if err != nil {
   304  			return fmt.Errorf("unable to find configmap %s/%s: %w", source.GetNamespace(), source.Spec.ConfigMap, err)
   305  		}
   306  
   307  		if source.ConfigMapChanges(configMap) {
   308  			catalogSource.Status.ConfigMapResource = &v1alpha1.ConfigMapResourceReference{
   309  				Name:            configMap.GetName(),
   310  				Namespace:       configMap.GetNamespace(),
   311  				UID:             configMap.GetUID(),
   312  				ResourceVersion: configMap.GetResourceVersion(),
   313  				LastUpdateTime:  c.now(),
   314  			}
   315  
   316  			// recreate the pod if there are configmap changes; this causes the db to be rebuilt
   317  			overwritePod = true
   318  		}
   319  
   320  		// recreate the pod if no existing pod is serving the latest image
   321  		current, err := c.currentPodsWithCorrectResourceVersion(source, image, defaultPodSecurityConfig)
   322  		if err != nil {
   323  			return err
   324  		}
   325  		if len(current) == 0 {
   326  			overwritePod = true
   327  		}
   328  	}
   329  
   330  	//TODO: if any of these error out, we should write a status back (possibly set RegistryServiceStatus to nil so they get recreated)
   331  	if err := c.ensureServiceAccount(source, overwrite); err != nil {
   332  		return pkgerrors.Wrapf(err, "error ensuring service account: %s", source.serviceAccountName())
   333  	}
   334  	if err := c.ensureRole(source, overwrite); err != nil {
   335  		return pkgerrors.Wrapf(err, "error ensuring role: %s", source.roleName())
   336  	}
   337  	if err := c.ensureRoleBinding(source, overwrite); err != nil {
   338  		return pkgerrors.Wrapf(err, "error ensuring rolebinding: %s", source.RoleBinding().GetName())
   339  	}
   340  	pod, err := source.Pod(image, defaultPodSecurityConfig)
   341  	if err != nil {
   342  		return err
   343  	}
   344  	if err := c.ensurePod(source, defaultPodSecurityConfig, overwritePod); err != nil {
   345  		return pkgerrors.Wrapf(err, "error ensuring pod: %s", pod.GetName())
   346  	}
   347  	service, err := source.Service()
   348  	if err != nil {
   349  		return err
   350  	}
   351  	if err := c.ensureService(source, overwrite); err != nil {
   352  		return pkgerrors.Wrapf(err, "error ensuring service: %s", service.GetName())
   353  	}
   354  
   355  	if overwritePod {
   356  		now := c.now()
   357  		catalogSource.Status.RegistryServiceStatus = &v1alpha1.RegistryServiceStatus{
   358  			CreatedAt:        now,
   359  			Protocol:         "grpc",
   360  			ServiceName:      service.GetName(),
   361  			ServiceNamespace: source.GetNamespace(),
   362  			Port:             fmt.Sprintf("%d", service.Spec.Ports[0].Port),
   363  		}
   364  	}
   365  	return nil
   366  }
   367  
   368  func (c *ConfigMapRegistryReconciler) ensureServiceAccount(source configMapCatalogSourceDecorator, overwrite bool) error {
   369  	serviceAccount := source.ServiceAccount()
   370  	if c.currentServiceAccount(source) != nil {
   371  		if !overwrite {
   372  			return nil
   373  		}
   374  		if err := c.OpClient.DeleteServiceAccount(serviceAccount.GetNamespace(), serviceAccount.GetName(), metav1.NewDeleteOptions(0)); err != nil && !apierrors.IsNotFound(err) {
   375  			return err
   376  		}
   377  	}
   378  	_, err := c.OpClient.CreateServiceAccount(serviceAccount)
   379  	return err
   380  }
   381  
   382  func (c *ConfigMapRegistryReconciler) ensureRole(source configMapCatalogSourceDecorator, overwrite bool) error {
   383  	role := source.Role()
   384  	if c.currentRole(source) != nil {
   385  		if !overwrite {
   386  			return nil
   387  		}
   388  		if err := c.OpClient.DeleteRole(role.GetNamespace(), role.GetName(), metav1.NewDeleteOptions(0)); err != nil && !apierrors.IsNotFound(err) {
   389  			return err
   390  		}
   391  	}
   392  	_, err := c.OpClient.CreateRole(role)
   393  	return err
   394  }
   395  
   396  func (c *ConfigMapRegistryReconciler) ensureRoleBinding(source configMapCatalogSourceDecorator, overwrite bool) error {
   397  	roleBinding := source.RoleBinding()
   398  	if c.currentRoleBinding(source) != nil {
   399  		if !overwrite {
   400  			return nil
   401  		}
   402  		if err := c.OpClient.DeleteRoleBinding(roleBinding.GetNamespace(), roleBinding.GetName(), metav1.NewDeleteOptions(0)); err != nil && !apierrors.IsNotFound(err) {
   403  			return err
   404  		}
   405  	}
   406  	_, err := c.OpClient.CreateRoleBinding(roleBinding)
   407  	return err
   408  }
   409  
   410  func (c *ConfigMapRegistryReconciler) ensurePod(source configMapCatalogSourceDecorator, defaultPodSecurityConfig v1alpha1.SecurityConfig, overwrite bool) error {
   411  	pod, err := source.Pod(c.Image, defaultPodSecurityConfig)
   412  	if err != nil {
   413  		return err
   414  	}
   415  	currentPods, err := c.currentPods(source, c.Image, defaultPodSecurityConfig)
   416  	if err != nil {
   417  		return err
   418  	}
   419  	if len(currentPods) > 0 {
   420  		if !overwrite {
   421  			return nil
   422  		}
   423  		for _, p := range currentPods {
   424  			if err := c.OpClient.KubernetesInterface().CoreV1().Pods(pod.GetNamespace()).Delete(context.TODO(), p.GetName(), *metav1.NewDeleteOptions(1)); err != nil && !apierrors.IsNotFound(err) {
   425  				return pkgerrors.Wrapf(err, "error deleting old pod: %s", p.GetName())
   426  			}
   427  		}
   428  	}
   429  	_, err = c.OpClient.KubernetesInterface().CoreV1().Pods(pod.GetNamespace()).Create(context.TODO(), pod, metav1.CreateOptions{})
   430  	if err == nil {
   431  		return nil
   432  	}
   433  	return pkgerrors.Wrapf(err, "error creating new pod: %s", pod.GetGenerateName())
   434  }
   435  
   436  func (c *ConfigMapRegistryReconciler) ensureService(source configMapCatalogSourceDecorator, overwrite bool) error {
   437  	service, err := source.Service()
   438  	if err != nil {
   439  		return err
   440  	}
   441  	svc, err := c.currentService(source)
   442  	if err != nil {
   443  		return err
   444  	}
   445  	if svc != nil {
   446  		if !overwrite && ServiceHashMatch(svc, service) {
   447  			return nil
   448  		}
   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  // CheckRegistryServer returns true if the given CatalogSource is considered healthy; false otherwise.
   458  func (c *ConfigMapRegistryReconciler) CheckRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) (healthy bool, err error) {
   459  	source := configMapCatalogSourceDecorator{catalogSource, c.createPodAsUser}
   460  
   461  	image := c.Image
   462  	if source.Spec.SourceType == "grpc" {
   463  		image = source.Spec.Image
   464  	}
   465  	if image == "" {
   466  		err = fmt.Errorf("no image for registry")
   467  		return
   468  	}
   469  
   470  	defaultPodSecurityConfig, err := getDefaultPodContextConfig(c.OpClient, catalogSource.GetNamespace())
   471  	if err != nil {
   472  		return false, err
   473  	}
   474  
   475  	if source.Spec.SourceType == v1alpha1.SourceTypeConfigmap || source.Spec.SourceType == v1alpha1.SourceTypeInternal {
   476  		// we use the live client here instead of a lister since our listers are scoped to objects with the olm.managed label,
   477  		// and this configmap is a user-provided input to the catalog source and will not have that label
   478  		configMap, err := c.OpClient.KubernetesInterface().CoreV1().ConfigMaps(source.GetNamespace()).Get(context.TODO(), source.Spec.ConfigMap, metav1.GetOptions{})
   479  		if err != nil {
   480  			return false, fmt.Errorf("unable to find configmap %s/%s: %w", source.GetNamespace(), source.Spec.ConfigMap, err)
   481  		}
   482  
   483  		if source.ConfigMapChanges(configMap) {
   484  			return false, nil
   485  		}
   486  
   487  		// recreate the pod if no existing pod is serving the latest image
   488  		current, err := c.currentPodsWithCorrectResourceVersion(source, image, defaultPodSecurityConfig)
   489  		if err != nil {
   490  			return false, err
   491  		}
   492  		if len(current) == 0 {
   493  			return false, nil
   494  		}
   495  	}
   496  
   497  	// Check on registry resources
   498  	// TODO: more complex checks for resources
   499  	// TODO: add gRPC health check
   500  	service, err := c.currentService(source)
   501  	if err != nil {
   502  		return false, err
   503  	}
   504  	pods, err := c.currentPods(source, c.Image, defaultPodSecurityConfig)
   505  	if err != nil {
   506  		return false, err
   507  	}
   508  	if c.currentServiceAccount(source) == nil ||
   509  		c.currentRole(source) == nil ||
   510  		c.currentRoleBinding(source) == nil ||
   511  		service == nil ||
   512  		len(pods) < 1 {
   513  		healthy = false
   514  		return
   515  	}
   516  
   517  	podsAreLive, e := detectAndDeleteDeadPods(logger, c.OpClient, pods, source.GetNamespace())
   518  	if e != nil {
   519  		return false, fmt.Errorf("error deleting dead pods: %v", e)
   520  	}
   521  	return podsAreLive, nil
   522  }
   523  
   524  // detectAndDeleteDeadPods determines if there are registry client pods that are in the deleted state
   525  // but have not been removed by GC (eg the node goes down before GC can remove them), and attempts to
   526  // force delete the pods. If there are live registry pods remaining, it returns true, otherwise returns false.
   527  func detectAndDeleteDeadPods(logger *logrus.Entry, client operatorclient.ClientInterface, pods []*corev1.Pod, sourceNamespace string) (bool, error) {
   528  	var forceDeletionErrs []error
   529  	livePodFound := false
   530  	for _, pod := range pods {
   531  		if !isPodDead(pod) {
   532  			livePodFound = true
   533  			logger.WithFields(logrus.Fields{"pod.namespace": sourceNamespace, "pod.name": pod.GetName()}).Debug("pod is alive")
   534  			continue
   535  		}
   536  		logger.WithFields(logrus.Fields{"pod.namespace": sourceNamespace, "pod.name": pod.GetName()}).Info("force deleting dead pod")
   537  		if err := client.KubernetesInterface().CoreV1().Pods(sourceNamespace).Delete(context.TODO(), pod.GetName(), metav1.DeleteOptions{
   538  			GracePeriodSeconds: ptr.To[int64](0),
   539  		}); err != nil && !apierrors.IsNotFound(err) {
   540  			forceDeletionErrs = append(forceDeletionErrs, err)
   541  		}
   542  	}
   543  	if len(forceDeletionErrs) > 0 {
   544  		return false, errors.Join(forceDeletionErrs...)
   545  	}
   546  	return livePodFound, nil
   547  }