Repository: operator-framework/operator-lifecycle-manager
Pull request #1414 (2020-03-27 15:58:29)

Bug 1817833: Add spec validation and improved status for CatalogSources
- Add spec validation for CatalogSources that surfaces invalid empty sourceType/address/image/configmap field combinations as `status.reason: InvalidSpecError`
- Prevent a panic from a nil pointer access when checking the health of a CatalogSource for Subscription status updates.

Panic:

```
time="2020-03-27T14:02:04Z" level=info msg=syncing event=update reconciling="*v1alpha1.Subscription" selflink=/apis/operators.coreos.com/v1alpha1/namespaces/local-storage/subscriptions/local-storage-operator
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x15a4748]

goroutine 307 [running]:
github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/subscription.(*catalogHealthReconciler).healthy(0xc000585540, 0xc000ffc3f0, 0xc000ffc1f8, 0xc00347a0e0, 0x0)
	/build/pkg/controller/operators/catalog/subscription/reconciler.go:185 +0x48
github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/subscription.(*catalogHealthReconciler).health(0xc000585540, 0xc0002fc560, 0xc000ffc3f0, 0xc0002fc780, 0x0, 0x0)
	/build/pkg/controller/operators/catalog/subscription/reconciler.go:159 +0x39
github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/subscription.(*catalogHealthReconciler).catalogHealth(0xc000585540, 0xc0000c5e50, 0xd, 0x1ca2fa0, 0xc00094d970, 0x1529800700c701, 0xc0033f70e8, 0xc0033f7060)
	/build/pkg/controller/operators/catalog/subscription/reconciler.go:137 +0x2ba
github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/subscription.(*catalogHealthReconciler).Reconcile(0xc000585540, 0x1c729a0, 0xc00043c140, 0x7f5ee5367630, 0xc00094d950, 0x7f5ee5367630, 0xc00094d950, 0x0, 0x0)
	/build/pkg/controller/operators/catalog/subscription/reconciler.go:82 +0x21c
github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubestate.ReconcilerChain.Reconcile(0xc00052cfc0, 0x3, 0x4, 0x1c729a0, 0xc00043c140, 0x7f5ee5367250, 0xc00094d910, 0x0, 0x0, 0x70763750545a6742, ...)
	/build/pkg/lib/kubestate/kubestate.go:128 +0xa5
github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/catalog/subscription.(*subscriptionSyncer).Sync(0xc0004518f0, 0x1c729a0, 0xc00043c140, 0x1c4e620, 0xc0004b6ba0, 0xc003811c01, 0x0)
	/build/pkg/controller/operators/catalog/subscription/syncer.go:75 +0x596
github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer.(*QueueInformer).Sync(...)
	/build/pkg/lib/queueinformer/queueinformer.go:36
github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer.(*operator).processNextWorkItem(0xc0000d73f0, 0x1c729a0, 0xc00043c140, 0xc00054bf20, 0x45c900)
	/build/pkg/lib/queueinformer/queueinformer_operator.go:287 +0x32b
github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer.(*operator).worker(0xc0000d73f0, 0x1c729a0, 0xc00043c140, 0xc00054bf20)
	/build/pkg/lib/queueinformer/queueinformer_operator.go:231 +0x49
created by github.com/operator-framework/operator-lifecycle-manager/pkg/lib/queueinformer.(*operator).start
	/build/pkg/lib/queueinformer/queueinformer_operator.go:221 +0x455
```


openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/1414#" title="Author self-approved">njhale</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)~~ [njhale]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/1414#pullrequestreview-382992736" title="LGTM">awgreene</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/1414#" title="Author self-approved">njhale</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)~~ [njhale]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci-robot: New changes are detected. LGTM label has been removed.
openshift-ci-robot: @njhale: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | 81d6e622612e577d1e54d1e852f665cf1abc8f6c | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/1414/pull-ci-operator-framework-operator-lifecycle-manager-master-unit/3490) | `/test unit`

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=operator-framework&repo=operator-lifecycle-manager&pr=1414). [Your PR dashboard](https://prow.svc.ci.openshift.org/pr?query=is:pr+state:open+author:njhale). Please help us cut down on flakes by [linking to](https://github.com/kubernetes/community/blob/master/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/operator-framework/operator-lifecycle-manager/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci-robot: @njhale: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | 81d6e622612e577d1e54d1e852f665cf1abc8f6c | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/1414/pull-ci-operator-framework-operator-lifecycle-manager-master-unit/3490) | `/test unit`
ci/prow/verify | 81d6e622612e577d1e54d1e852f665cf1abc8f6c | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/1414/pull-ci-operator-framework-operator-lifecycle-manager-master-verify/3183) | `/test verify`

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=operator-framework&repo=operator-lifecycle-manager&pr=1414). [Your PR dashboard](https://prow.svc.ci.openshift.org/pr?query=is:pr+state:open+author:njhale). Please help us cut down on flakes by [linking to](https://github.com/kubernetes/community/blob/master/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/operator-framework/operator-lifecycle-manager/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci-robot: @njhale: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | 81d6e622612e577d1e54d1e852f665cf1abc8f6c | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/1414/pull-ci-operator-framework-operator-lifecycle-manager-master-unit/3490) | `/test unit`
ci/prow/verify | 81d6e622612e577d1e54d1e852f665cf1abc8f6c | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/1414/pull-ci-operator-framework-operator-lifecycle-manager-master-verify/3183) | `/test verify`
ci/prow/images | 81d6e622612e577d1e54d1e852f665cf1abc8f6c | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/1414/pull-ci-operator-framework-operator-lifecycle-manager-master-images/3284) | `/test images`

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=operator-framework&repo=operator-lifecycle-manager&pr=1414). [Your PR dashboard](https://prow.svc.ci.openshift.org/pr?query=is:pr+state:open+author:njhale). Please help us cut down on flakes by [linking to](https://github.com/kubernetes/community/blob/master/contributors/devel/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/operator-framework/operator-lifecycle-manager/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/pkg/api/apis/operators/v1/operatorgroup_types.go b/pkg/api/apis/operators/v1/operatorgroup_types.go
index 5c3cd61aa..5e6fd3cc2 100644
--- a/pkg/api/apis/operators/v1/operatorgroup_types.go
+++ b/pkg/api/apis/operators/v1/operatorgroup_types.go
@@ -1,7 +1,6 @@
 package v1
 
 import (
-	"fmt"
 	"sort"
 	"strings"
 
@@ -16,9 +15,6 @@ const (
 	OperatorGroupProvidedAPIsAnnotationKey = "olm.providedAPIs"
 
 	OperatorGroupKind = "OperatorGroup"
-
-	operatorGroupLabelPrefix   = "olm.operatorgroup/"
-	operatorGroupLabelTemplate = operatorGroupLabelPrefix + "%s.%s"
 )
 
 // OperatorGroupSpec is the spec for an OperatorGroup resource.
@@ -103,14 +99,3 @@ func (o *OperatorGroup) HasServiceAccountSynced() bool {
 
 	return false
 }
-
-// getOperatorGroupLabel returns a label that is applied to Namespaces to signify that the
-// namespace is a part of the OperatorGroup using selectors.
-func (o *OperatorGroup) GetLabel() string {
-	return fmt.Sprintf(operatorGroupLabelTemplate, o.GetNamespace(), o.GetName())
-}
-
-// IsOperatorGroupLabel returns true if the label is an OperatorGroup label.
-func IsOperatorGroupLabel(label string) bool {
-	return strings.HasPrefix(label, operatorGroupLabelPrefix)
-}
diff --git a/pkg/api/apis/operators/v1alpha1/catalogsource_types.go b/pkg/api/apis/operators/v1alpha1/catalogsource_types.go
index 00f45c11a..b89412dd3 100644
--- a/pkg/api/apis/operators/v1alpha1/catalogsource_types.go
+++ b/pkg/api/apis/operators/v1alpha1/catalogsource_types.go
@@ -29,7 +29,11 @@ const (
 )
 
 const (
-	CatalogSourceConfigMapError      ConditionReason = "ConfigMapError"
+	// CatalogSourceSpecInvalidError denotes when fields on the spec of the CatalogSource are not valid.
+	CatalogSourceSpecInvalidError ConditionReason = "SpecInvalidError"
+	// CatalogSourceConfigMapError denotes when there is an issue extracting manifests from the specified ConfigMap.
+	CatalogSourceConfigMapError ConditionReason = "ConfigMapError"
+	// CatalogSourceRegistryServerError denotes when there is an issue querying the specified registry server.
 	CatalogSourceRegistryServerError ConditionReason = "RegistryServerError"
 )
 
@@ -104,10 +108,10 @@ type GRPCConnectionState struct {
 }
 
 type CatalogSourceStatus struct {
-	// A human readable message indicating details about why the ClusterServiceVersion is in this condition.
+	// A human readable message indicating details about why the CatalogSource is in this condition.
 	// +optional
 	Message string `json:"message,omitempty"`
-	// Reason is the reason the Subscription was transitioned to its current state.
+	// Reason is the reason the CatalogSource was transitioned to its current state.
 	// +optional
 	Reason ConditionReason `json:"reason,omitempty"`
 
@@ -177,7 +181,6 @@ func (c *CatalogSource) Update() bool {
 		logrus.WithField("CatalogSource", c.Name).Debugf("latest poll %v", *c.Status.LatestImageRegistryPoll)
 	}
 
-
 	if c.Status.LatestImageRegistryPoll.IsZero() {
 		logrus.WithField("CatalogSource", c.Name).Debugf("creation timestamp plus interval before now %t", c.CreationTimestamp.Add(interval).Before(time.Now()))
 		if c.CreationTimestamp.Add(interval).Before(time.Now()) {
diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go
index bf2be8bc5..330977850 100644
--- a/pkg/controller/operators/catalog/operator.go
+++ b/pkg/controller/operators/catalog/operator.go
@@ -485,6 +485,35 @@ func (o *Operator) handleCatSrcDeletion(obj interface{}) {
 	o.logger.WithField("source", sourceKey).Info("removed client for deleted catalogsource")
 }
 
+func validateSourceType(logger *logrus.Entry, in *v1alpha1.CatalogSource) (out *v1alpha1.CatalogSource, continueSync bool, _ error) {
+	out = in
+	var err error
+	switch sourceType := out.Spec.SourceType; sourceType {
+	case v1alpha1.SourceTypeInternal, v1alpha1.SourceTypeConfigmap:
+		if out.Spec.ConfigMap == "" {
+			err = fmt.Errorf("configmap name unset: must be set for sourcetype: %s", sourceType)
+		}
+	case v1alpha1.SourceTypeGrpc:
+		if out.Spec.Image == "" && out.Spec.Address == "" {
+			err = fmt.Errorf("image and address unset: at least one must be set for sourcetype: %s", sourceType)
+		}
+	default:
+		err = fmt.Errorf("unknown sourcetype: %s", sourceType)
+	}
+	if err != nil {
+		out.SetError(v1alpha1.CatalogSourceSpecInvalidError, err)
+		return
+	}
+
+	// The sourceType is valid, clear (all) status if there's existing invalid spec reason
+	if out.Status.Reason == v1alpha1.CatalogSourceSpecInvalidError {
+		out.Status = v1alpha1.CatalogSourceStatus{}
+	}
+	continueSync = true
+
+	return
+}
+
 func (o *Operator) syncConfigMap(logger *logrus.Entry, in *v1alpha1.CatalogSource) (out *v1alpha1.CatalogSource, continueSync bool, syncError error) {
 	out = in
 	if !(in.Spec.SourceType == v1alpha1.SourceTypeInternal || in.Spec.SourceType == v1alpha1.SourceTypeConfigmap) {
@@ -705,6 +734,7 @@ func (o *Operator) syncCatalogSources(obj interface{}) (syncError error) {
 	}
 
 	chain := []CatalogSourceSyncFunc{
+		validateSourceType,
 		o.syncConfigMap,
 		o.syncRegistryServer,
 		o.syncConnection,
@@ -715,8 +745,7 @@ func (o *Operator) syncCatalogSources(obj interface{}) (syncError error) {
 
 	out, syncError := syncFunc(in, chain)
 
-	if equalFunc(&in.Status, &out.Status) {
-		logger.Debug("no change in status, skipping status update")
+	if equalFunc(&catsrc.Status, &out.Status) {
 		return
 	}
 
diff --git a/pkg/controller/operators/catalog/subscription/reconciler.go b/pkg/controller/operators/catalog/subscription/reconciler.go
index 6cb9b2cb3..d716e6dd0 100644
--- a/pkg/controller/operators/catalog/subscription/reconciler.go
+++ b/pkg/controller/operators/catalog/subscription/reconciler.go
@@ -182,7 +182,18 @@ func (c *catalogHealthReconciler) health(now *metav1.Time, catalog *v1alpha1.Cat
 // healthy returns true if the given catalog is healthy, false otherwise, and any error encountered
 // while checking the catalog's registry server.
 func (c *catalogHealthReconciler) healthy(catalog *v1alpha1.CatalogSource) (bool, error) {
-	return c.registryReconcilerFactory.ReconcilerForSource(catalog).CheckRegistryServer(catalog)
+	if catalog.Status.Reason == v1alpha1.CatalogSourceSpecInvalidError {
+		// The catalog's spec is bad, mark unhealthy
+		return false, nil
+	}
+
+	// Check connection health
+	rec := c.registryReconcilerFactory.ReconcilerForSource(catalog)
+	if rec == nil {
+		return false, fmt.Errorf("could not get reconciler for catalog: %#v", catalog)
+	}
+
+	return rec.CheckRegistryServer(catalog)
 }
 
 // installPlanReconciler reconciles InstallPlan status for Subscriptions.
diff --git a/pkg/controller/operators/olm/operator.go b/pkg/controller/operators/olm/operator.go
index 16e194b92..f8feebab1 100644
--- a/pkg/controller/operators/olm/operator.go
+++ b/pkg/controller/operators/olm/operator.go
@@ -68,7 +68,6 @@ type Operator struct {
 	csvCopyQueueSet       *queueinformer.ResourceQueueSet
 	csvGCQueueSet         *queueinformer.ResourceQueueSet
 	objGCQueueSet         *queueinformer.ResourceQueueSet
-	nsQueueSet            workqueue.RateLimitingInterface
 	apiServiceQueue       workqueue.RateLimitingInterface
 	csvIndexers           map[string]cache.Indexer
 	recorder              record.EventRecorder
@@ -395,7 +394,6 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
 	// register namespace queueinformer
 	namespaceInformer := k8sInformerFactory.Core().V1().Namespaces()
 	op.lister.CoreV1().RegisterNamespaceLister(namespaceInformer.Lister())
-	op.nsQueueSet = workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "resolver")
 	namespaceInformer.Informer().AddEventHandler(
 		&cache.ResourceEventHandlerFuncs{
 			DeleteFunc: op.namespaceAddedOrRemoved,
@@ -405,9 +403,8 @@ func newOperatorWithConfig(ctx context.Context, config *operatorConfig) (*Operat
 	namespaceQueueInformer, err := queueinformer.NewQueueInformer(
 		ctx,
 		queueinformer.WithLogger(op.logger),
-		queueinformer.WithQueue(op.nsQueueSet),
 		queueinformer.WithInformer(namespaceInformer.Informer()),
-		queueinformer.WithSyncer(queueinformer.LegacySyncHandler(op.syncNamespace).ToSyncer()),
+		queueinformer.WithSyncer(queueinformer.LegacySyncHandler(op.syncObject).ToSyncer()),
 	)
 	if err != nil {
 		return nil, err
@@ -739,49 +736,6 @@ func (a *Operator) namespaceAddedOrRemoved(obj interface{}) {
 	return
 }
 
-func (a *Operator) syncNamespace(obj interface{}) error {
-	// Check to see if any operator groups are associated with this namespace
-	namespace, ok := obj.(*corev1.Namespace)
-	if !ok {
-		a.logger.Debugf("wrong type: %#v\n", obj)
-		return fmt.Errorf("casting Namespace failed")
-	}
-
-	logger := a.logger.WithFields(logrus.Fields{
-		"name": namespace.GetName(),
-	})
-
-	// Remove existing OperatorGroup labels
-	for label := range namespace.GetLabels() {
-		if v1.IsOperatorGroupLabel(label) {
-			delete(namespace.Labels, label)
-		}
-	}
-
-	operatorGroupList, err := a.lister.OperatorsV1().OperatorGroupLister().List(labels.Everything())
-	if err != nil {
-		logger.WithError(err).Warn("lister failed")
-		return err
-	}
-
-	for _, group := range operatorGroupList {
-		namespaceSet := resolver.NewNamespaceSet(group.Status.Namespaces)
-
-		// Apply the label if not an All Namespaces OperatorGroup.
-		if namespaceSet.Contains(namespace.GetName()) && !namespaceSet.IsAllNamespaces() {
-			if namespace.Labels == nil {
-				namespace.Labels = make(map[string]string, 1)
-			}
-			namespace.Labels[group.GetLabel()] = ""
-		}
-	}
-
-	// Update the Namespace
-	_, err = a.opClient.KubernetesInterface().CoreV1().Namespaces().Update(namespace)
-
-	return err
-}
-
 func (a *Operator) handleClusterServiceVersionDeletion(obj interface{}) {
 	clusterServiceVersion, ok := obj.(*v1alpha1.ClusterServiceVersion)
 	if !ok {
@@ -1939,12 +1893,3 @@ func (a *Operator) ensureLabels(in *v1alpha1.ClusterServiceVersion, labelSets ..
 	out, err := a.client.OperatorsV1alpha1().ClusterServiceVersions(out.GetNamespace()).Update(out)
 	return out, err
 }
-
-func containsString(list []string, item string) bool {
-	for i := range list {
-		if list[i] == item {
-			return true
-		}
-	}
-	return false
-}
diff --git a/pkg/controller/operators/olm/operatorgroup.go b/pkg/controller/operators/olm/operatorgroup.go
index addbd4ffc..424c34253 100644
--- a/pkg/controller/operators/olm/operatorgroup.go
+++ b/pkg/controller/operators/olm/operatorgroup.go
@@ -27,6 +27,8 @@ const (
 	AdminSuffix = "admin"
 	EditSuffix  = "edit"
 	ViewSuffix  = "view"
+
+	operatorGroupLabelTemplate = "olm.operatorgroup/%s.%s"
 )
 
 var (
@@ -98,8 +100,9 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
 	logger.WithField("targetNamespaces", targetNamespaces).Debug("updated target namespaces")
 
 	if namespacesChanged(targetNamespaces, op.Status.Namespaces) {
-		logger.Debug("OperatorGroup namespaces change detected")
-		outOfSyncNamespaces := namespacesAddedOrRemoved(op.Spec.TargetNamespaces, op.Status.Namespaces)
+		// Labels are only applied to namespaces that are not empty strings.
+		prunedStatusNamespaces := pruneEmptyStrings(op.Status.Namespaces)
+		prunedTargetNamespaces := pruneEmptyStrings(targetNamespaces)
 
 		// Update operatorgroup target namespace selection
 		logger.WithField("targets", targetNamespaces).Debug("namespace change detected")
@@ -113,15 +116,25 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
 			return err
 		}
 
-		logger.Debug("operatorgroup status updated")
-
-		// Requeueing out of sync namespaces
-		logger.Debug("Requeueing out of sync namespaces")
-		for _, ns := range outOfSyncNamespaces {
-			logger.WithField("namespace", ns).Debug("requeueing")
-			a.nsQueueSet.Add(ns)
+		// Remove labels from targetNamespaces that were dropped.
+		ogLabel := getOperatorGroupLabel(*op)
+		for _, namespaceName := range setDifference(prunedStatusNamespaces, prunedTargetNamespaces) {
+			err = a.removeNamespaceLabel(namespaceName, ogLabel)
+			if err != nil {
+				logger.WithError(err).Warnf("failed to remove operatorgroup label from namespace %s", namespaceName)
+				return err
+			}
 		}
 
+		// Add labels to targetNamespaces that were added.
+		for _, namespaceName := range setDifference(prunedTargetNamespaces, prunedStatusNamespaces) {
+			err = a.addNamespaceLabel(namespaceName, ogLabel, "")
+			if err != nil {
+				logger.WithError(err).Warnf("failed to add operatorgroup to from namespace %s", namespaceName)
+				return err
+			}
+		}
+		logger.Debug("namespace change detected and operatorgroup status updated")
 		// CSV requeue is handled by the succeeding sync in `annotateCSVs`
 		return nil
 	}
@@ -172,6 +185,43 @@ func (a *Operator) syncOperatorGroups(obj interface{}) error {
 	return nil
 }
 
+// pruneEmptyStrings removes any items from a list that are empty
+func pruneEmptyStrings(strings []string) []string {
+	prunedStrings := []string{}
+	for _, str := range strings {
+		if str != "" {
+			prunedStrings = append(prunedStrings, str)
+		}
+	}
+	return prunedStrings
+}
+
+// setDifference implements Set Difference: A - B
+// https://en.wikipedia.org/wiki/Complement_(set_theory)#In_programming_languages
+func setDifference(a, b []string) (diff []string) {
+	if len(a) == 0 {
+		return diff
+	}
+
+	if len(b) == 0 {
+		return a
+	}
+
+	// Find the Set Difference.
+	m := make(map[string]bool)
+
+	for _, item := range b {
+		m[item] = true
+	}
+
+	for _, item := range a {
+		if _, ok := m[item]; !ok {
+			diff = append(diff, item)
+		}
+	}
+	return diff
+}
+
 func (a *Operator) operatorGroupDeleted(obj interface{}) {
 	op, ok := obj.(*v1.OperatorGroup)
 	if !ok {
@@ -196,12 +246,52 @@ func (a *Operator) operatorGroupDeleted(obj interface{}) {
 		}
 	}
 
-	// Trigger a sync on namespaces
-	logger.Debug("OperatorGroup deleted, requeueing out of sync namespaces")
-	for _, ns := range op.Status.Namespaces {
-		logger.WithField("namespace", ns).Debug("requeueing")
-		a.nsQueueSet.Add(ns)
+	// Remove OperatorGroup label from targeNamespaces.
+	ogLabel := getOperatorGroupLabel(*op)
+	for _, namespaceName := range op.Spec.TargetNamespaces {
+		a.removeNamespaceLabel(namespaceName, ogLabel)
+		if err != nil {
+			logger.WithError(err).Error("failed to remove OperatorGroup Label from Namespace during garbage collection")
+		}
+	}
+}
+
+func (a *Operator) updateNamespace(namespaceName string, namespaceUpdateFunc func(*corev1.Namespace)) error {
+	namespace, err := a.opClient.KubernetesInterface().CoreV1().Namespaces().Get(namespaceName, metav1.GetOptions{})
+	if err != nil {
+		return err
+	}
+
+	namespaceUpdateFunc(namespace)
+
+	_, err = a.opClient.KubernetesInterface().CoreV1().Namespaces().Update(namespace)
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+func (a *Operator) removeNamespaceLabel(namespaceName, key string) error {
+	namespaceUpdateFunc := func(namespace *corev1.Namespace) {
+		delete(namespace.Labels, key)
+	}
+	return a.updateNamespace(namespaceName, namespaceUpdateFunc)
+}
+
+func (a *Operator) addNamespaceLabel(namespaceName string, key, value string) error {
+	namespaceUpdateFunc := func(namespace *corev1.Namespace) {
+		if namespace.Labels == nil {
+			namespace.Labels = make(map[string]string, 1)
+		}
+		namespace.Labels[key] = value
 	}
+	return a.updateNamespace(namespaceName, namespaceUpdateFunc)
+}
+
+// getOperatorGroupLabel returns a label that is applied to Namespaces to signify that the
+// namespace is a part of the OperatorGroup using selectors.
+func getOperatorGroupLabel(og v1.OperatorGroup) string {
+	return fmt.Sprintf(operatorGroupLabelTemplate, og.GetNamespace(), og.GetName())
 }
 
 func (a *Operator) annotateCSVs(group *v1.OperatorGroup, targetNamespaces []string, logger *logrus.Entry) error {
@@ -985,32 +1075,3 @@ func (a *Operator) findCSVsThatProvideAnyOf(provide resolver.APISet) ([]*v1alpha
 
 	return providers, nil
 }
-
-// namespacesAddedOrRemoved returns the union of:
-// - the set of elements in A but not in B
-// - the set of elements in B but not in A
-func namespacesAddedOrRemoved(a, b []string) []string {
-	check := make(map[string]struct{})
-
-	for _, namespace := range a {
-		check[namespace] = struct{}{}
-	}
-
-	for _, namespace := range b {
-		if _, ok := check[namespace]; !ok {
-			check[namespace] = struct{}{}
-		} else {
-			delete(check, namespace)
-		}
-	}
-
-	// Remove global namespace name if added
-	delete(check, "")
-
-	var keys []string
-	for key := range check {
-		keys = append(keys, key)
-	}
-
-	return keys
-}
diff --git a/pkg/package-server/apis/operators/packagemanifest.go b/pkg/package-server/apis/operators/packagemanifest.go
index 52402f7f0..56a31bd20 100644
--- a/pkg/package-server/apis/operators/packagemanifest.go
+++ b/pkg/package-server/apis/operators/packagemanifest.go
@@ -1,19 +1,9 @@
 package operators
 
-import (
-	"encoding/json"
-
-	operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
-	"github.com/operator-framework/operator-registry/pkg/registry"
-)
-
-const (
-	// The yaml attribute that specifies the related images of the ClusterServiceVersion
-	relatedImages = "relatedImages"
-)
+import operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 
 // CreateCSVDescription creates a CSVDescription from a given CSV
-func CreateCSVDescription(csv *operatorsv1alpha1.ClusterServiceVersion, csvJSON string) CSVDescription {
+func CreateCSVDescription(csv *operatorsv1alpha1.ClusterServiceVersion) CSVDescription {
 	desc := CSVDescription{
 		DisplayName: csv.Spec.DisplayName,
 		Version:     csv.Spec.Version,
@@ -32,9 +22,6 @@ func CreateCSVDescription(csv *operatorsv1alpha1.ClusterServiceVersion, csvJSON
 			Owned:    descriptionsForAPIServices(csv.Spec.APIServiceDefinitions.Owned),
 			Required: descriptionsForAPIServices(csv.Spec.APIServiceDefinitions.Required),
 		},
-		NativeAPIs:     csv.Spec.NativeAPIs,
-		MinKubeVersion: csv.Spec.MinKubeVersion,
-		RelatedImages:  GetImages(csvJSON),
 	}
 
 	icons := make([]Icon, len(csv.Spec.Icon))
@@ -82,34 +69,3 @@ func descriptionsForAPIServices(apis []operatorsv1alpha1.APIServiceDescription)
 	}
 	return descriptions
 }
-
-// GetImages returns a list of images listed in CSV (spec and deployments)
-func GetImages(csvJSON string) []string {
-	var images []string
-
-	csv := &registry.ClusterServiceVersion{}
-	err := json.Unmarshal([]byte(csvJSON), &csv)
-	if err != nil {
-		return images
-	}
-
-	imageSet, err := csv.GetOperatorImages()
-	if err != nil {
-		return images
-	}
-
-	relatedImgSet, err := csv.GetRelatedImages()
-	if err != nil {
-		return images
-	}
-
-	for k := range relatedImgSet {
-		imageSet[k] = struct{}{}
-	}
-
-	for k := range imageSet {
-		images = append(images, k)
-	}
-
-	return images
-}
diff --git a/pkg/package-server/apis/operators/packagemanifest_types.go b/pkg/package-server/apis/operators/packagemanifest_types.go
index 80f4fa0ef..3361565bd 100644
--- a/pkg/package-server/apis/operators/packagemanifest_types.go
+++ b/pkg/package-server/apis/operators/packagemanifest_types.go
@@ -107,13 +107,6 @@ type CSVDescription struct {
 
 	CustomResourceDefinitions operatorv1alpha1.CustomResourceDefinitions
 	APIServiceDefinitions     operatorv1alpha1.APIServiceDefinitions
-	NativeAPIs                []metav1.GroupVersionKind `json:"nativeApis,omitempty"`
-
-	// Minimum Kubernetes version for operator installation
-	MinKubeVersion string `json:"minKubeVersion,omitempty"`
-
-	// List of related images
-	RelatedImages []string `json:"relatedImages,omitempty"`
 }
 
 // AppLink defines a link to an application
diff --git a/pkg/package-server/apis/operators/v1/packagemanifest.go b/pkg/package-server/apis/operators/v1/packagemanifest.go
index bab5883c9..3a522ab45 100644
--- a/pkg/package-server/apis/operators/v1/packagemanifest.go
+++ b/pkg/package-server/apis/operators/v1/packagemanifest.go
@@ -1,19 +1,9 @@
 package v1
 
-import (
-	"encoding/json"
-
-	operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
-	"github.com/operator-framework/operator-registry/pkg/registry"
-)
-
-const (
-	// The yaml attribute that specifies the related images of the ClusterServiceVersion
-	relatedImages = "relatedImages"
-)
+import operatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 
 // CreateCSVDescription creates a CSVDescription from a given CSV
-func CreateCSVDescription(csv *operatorsv1alpha1.ClusterServiceVersion, csvJSON string) CSVDescription {
+func CreateCSVDescription(csv *operatorsv1alpha1.ClusterServiceVersion) CSVDescription {
 	desc := CSVDescription{
 		DisplayName: csv.Spec.DisplayName,
 		Version:     csv.Spec.Version,
@@ -26,9 +16,6 @@ func CreateCSVDescription(csv *operatorsv1alpha1.ClusterServiceVersion, csvJSON
 		InstallModes:              csv.Spec.InstallModes,
 		CustomResourceDefinitions: csv.Spec.CustomResourceDefinitions,
 		APIServiceDefinitions:     csv.Spec.APIServiceDefinitions,
-		NativeAPIs:                csv.Spec.NativeAPIs,
-		MinKubeVersion:            csv.Spec.MinKubeVersion,
-		RelatedImages:             GetImages(csvJSON),
 	}
 
 	icons := make([]Icon, len(csv.Spec.Icon))
@@ -45,34 +32,3 @@ func CreateCSVDescription(csv *operatorsv1alpha1.ClusterServiceVersion, csvJSON
 
 	return desc
 }
-
-// GetImages returns a list of images listed in CSV (spec and deployments)
-func GetImages(csvJSON string) []string {
-	var images []string
-
-	csv := &registry.ClusterServiceVersion{}
-	err := json.Unmarshal([]byte(csvJSON), &csv)
-	if err != nil {
-		return images
-	}
-
-	imageSet, err := csv.GetOperatorImages()
-	if err != nil {
-		return images
-	}
-
-	relatedImgSet, err := csv.GetRelatedImages()
-	if err != nil {
-		return images
-	}
-
-	for k := range relatedImgSet {
-		imageSet[k] = struct{}{}
-	}
-
-	for k := range imageSet {
-		images = append(images, k)
-	}
-
-	return images
-}
diff --git a/pkg/package-server/apis/operators/v1/packagemanifest_types.go b/pkg/package-server/apis/operators/v1/packagemanifest_types.go
index 2190610e9..f6678806c 100644
--- a/pkg/package-server/apis/operators/v1/packagemanifest_types.go
+++ b/pkg/package-server/apis/operators/v1/packagemanifest_types.go
@@ -109,13 +109,6 @@ type CSVDescription struct {
 
 	CustomResourceDefinitions operatorv1alpha1.CustomResourceDefinitions `json:"customresourcedefinitions,omitempty"`
 	APIServiceDefinitions     operatorv1alpha1.APIServiceDefinitions     `json:"apiservicedefinitions,omitempty"`
-	NativeAPIs                []metav1.GroupVersionKind                  `json:"nativeApis,omitempty"`
-
-	// Minimum Kubernetes version for operator installation
-	MinKubeVersion string `json:"minKubeVersion,omitempty"`
-
-	// List of related images
-	RelatedImages []string `json:"relatedImages,omitempty"`
 }
 
 // AppLink defines a link to an application
diff --git a/pkg/package-server/apis/operators/v1/zz_generated.conversion.go b/pkg/package-server/apis/operators/v1/zz_generated.conversion.go
index 9e1d6aa19..18fcb6404 100644
--- a/pkg/package-server/apis/operators/v1/zz_generated.conversion.go
+++ b/pkg/package-server/apis/operators/v1/zz_generated.conversion.go
@@ -25,7 +25,6 @@ import (
 
 	v1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 	operators "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	conversion "k8s.io/apimachinery/pkg/conversion"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
@@ -154,9 +153,6 @@ func autoConvert_v1_CSVDescription_To_operators_CSVDescription(in *CSVDescriptio
 	out.InstallModes = *(*[]v1alpha1.InstallMode)(unsafe.Pointer(&in.InstallModes))
 	out.CustomResourceDefinitions = in.CustomResourceDefinitions
 	out.APIServiceDefinitions = in.APIServiceDefinitions
-	out.NativeAPIs = *(*[]metav1.GroupVersionKind)(unsafe.Pointer(&in.NativeAPIs))
-	out.MinKubeVersion = in.MinKubeVersion
-	out.RelatedImages = *(*[]string)(unsafe.Pointer(&in.RelatedImages))
 	return nil
 }
 
@@ -177,9 +173,6 @@ func autoConvert_operators_CSVDescription_To_v1_CSVDescription(in *operators.CSV
 	out.InstallModes = *(*[]v1alpha1.InstallMode)(unsafe.Pointer(&in.InstallModes))
 	out.CustomResourceDefinitions = in.CustomResourceDefinitions
 	out.APIServiceDefinitions = in.APIServiceDefinitions
-	out.NativeAPIs = *(*[]metav1.GroupVersionKind)(unsafe.Pointer(&in.NativeAPIs))
-	out.MinKubeVersion = in.MinKubeVersion
-	out.RelatedImages = *(*[]string)(unsafe.Pointer(&in.RelatedImages))
 	return nil
 }
 
diff --git a/pkg/package-server/apis/operators/v1/zz_generated.deepcopy.go b/pkg/package-server/apis/operators/v1/zz_generated.deepcopy.go
index c01b9c373..9486549db 100644
--- a/pkg/package-server/apis/operators/v1/zz_generated.deepcopy.go
+++ b/pkg/package-server/apis/operators/v1/zz_generated.deepcopy.go
@@ -22,7 +22,6 @@ package v1
 
 import (
 	v1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -66,16 +65,6 @@ func (in *CSVDescription) DeepCopyInto(out *CSVDescription) {
 	}
 	in.CustomResourceDefinitions.DeepCopyInto(&out.CustomResourceDefinitions)
 	in.APIServiceDefinitions.DeepCopyInto(&out.APIServiceDefinitions)
-	if in.NativeAPIs != nil {
-		in, out := &in.NativeAPIs, &out.NativeAPIs
-		*out = make([]metav1.GroupVersionKind, len(*in))
-		copy(*out, *in)
-	}
-	if in.RelatedImages != nil {
-		in, out := &in.RelatedImages, &out.RelatedImages
-		*out = make([]string, len(*in))
-		copy(*out, *in)
-	}
 	return
 }
 
diff --git a/pkg/package-server/apis/operators/zz_generated.deepcopy.go b/pkg/package-server/apis/operators/zz_generated.deepcopy.go
index e6e4957dc..6dfa657a5 100644
--- a/pkg/package-server/apis/operators/zz_generated.deepcopy.go
+++ b/pkg/package-server/apis/operators/zz_generated.deepcopy.go
@@ -22,7 +22,6 @@ package operators
 
 import (
 	v1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -66,16 +65,6 @@ func (in *CSVDescription) DeepCopyInto(out *CSVDescription) {
 	}
 	in.CustomResourceDefinitions.DeepCopyInto(&out.CustomResourceDefinitions)
 	in.APIServiceDefinitions.DeepCopyInto(&out.APIServiceDefinitions)
-	if in.NativeAPIs != nil {
-		in, out := &in.NativeAPIs, &out.NativeAPIs
-		*out = make([]v1.GroupVersionKind, len(*in))
-		copy(*out, *in)
-	}
-	if in.RelatedImages != nil {
-		in, out := &in.RelatedImages, &out.RelatedImages
-		*out = make([]string, len(*in))
-		copy(*out, *in)
-	}
 	return
 }
 
diff --git a/pkg/package-server/client/openapi/zz_generated.openapi.go b/pkg/package-server/client/openapi/zz_generated.openapi.go
index 18f96702e..1f84c8897 100644
--- a/pkg/package-server/client/openapi/zz_generated.openapi.go
+++ b/pkg/package-server/client/openapi/zz_generated.openapi.go
@@ -808,44 +808,11 @@ func schema_package_server_apis_operators_v1_CSVDescription(ref common.Reference
 							Ref: ref("github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1.APIServiceDefinitions"),
 						},
 					},
-					"nativeApis": {
-						SchemaProps: spec.SchemaProps{
-							Type: []string{"array"},
-							Items: &spec.SchemaOrArray{
-								Schema: &spec.Schema{
-									SchemaProps: spec.SchemaProps{
-										Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionKind"),
-									},
-								},
-							},
-						},
-					},
-					"minKubeVersion": {
-						SchemaProps: spec.SchemaProps{
-							Description: "Minimum Kubernetes version for operator installation",
-							Type:        []string{"string"},
-							Format:      "",
-						},
-					},
-					"relatedImages": {
-						SchemaProps: spec.SchemaProps{
-							Description: "List of related images",
-							Type:        []string{"array"},
-							Items: &spec.SchemaOrArray{
-								Schema: &spec.Schema{
-									SchemaProps: spec.SchemaProps{
-										Type:   []string{"string"},
-										Format: "",
-									},
-								},
-							},
-						},
-					},
 				},
 			},
 		},
 		Dependencies: []string{
-			"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1.APIServiceDefinitions", "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1.CustomResourceDefinitions", "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1.InstallMode", "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/version.OperatorVersion", "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1.AppLink", "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1.Icon", "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionKind"},
+			"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1.APIServiceDefinitions", "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1.CustomResourceDefinitions", "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1.InstallMode", "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/version.OperatorVersion", "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1.AppLink", "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1.Icon"},
 	}
 }
 
diff --git a/pkg/package-server/provider/registry.go b/pkg/package-server/provider/registry.go
index 98ccb6a7c..eb6b57a52 100644
--- a/pkg/package-server/provider/registry.go
+++ b/pkg/package-server/provider/registry.go
@@ -495,7 +495,7 @@ func newPackageManifest(ctx context.Context, logger *logrus.Entry, pkg *api.Pack
 		manifest.Status.Channels = append(manifest.Status.Channels, operators.PackageChannel{
 			Name:           pkgChannel.GetName(),
 			CurrentCSV:     csv.GetName(),
-			CurrentCSVDesc: operators.CreateCSVDescription(&csv, bundle.GetCsvJson()),
+			CurrentCSVDesc: operators.CreateCSVDescription(&csv),
 		})
 
 		if manifest.Status.DefaultChannel != "" && pkgChannel.GetName() == manifest.Status.DefaultChannel || !providerSet {


Test patch:
diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go
index 36a886863..4c9f22149 100644
--- a/pkg/controller/operators/catalog/operator_test.go
+++ b/pkg/controller/operators/catalog/operator_test.go
@@ -719,23 +719,13 @@ func TestSyncCatalogSources(t *testing.T) {
 					UID:       types.UID("catalog-uid"),
 				},
 				Spec: v1alpha1.CatalogSourceSpec{
-					ConfigMap:  "cool-configmap",
 					SourceType: "nope",
 				},
 			},
-			k8sObjs: []runtime.Object{
-				&corev1.ConfigMap{
-					ObjectMeta: metav1.ObjectMeta{
-						Name:            "cool-configmap",
-						Namespace:       "cool-namespace",
-						UID:             types.UID("configmap-uid"),
-						ResourceVersion: "resource-version",
-					},
-					Data: fakeConfigMapData(),
-				},
+			expectedStatus: &v1alpha1.CatalogSourceStatus{
+				Message: "unknown sourcetype: nope",
+				Reason:  v1alpha1.CatalogSourceSpecInvalidError,
 			},
-			expectedStatus: nil,
-			expectedError:  fmt.Errorf("no reconciler for source type nope"),
 		},
 		{
 			testName:      "CatalogSourceWithBackingConfigMap",
@@ -877,6 +867,66 @@ func TestSyncCatalogSources(t *testing.T) {
 				pod(*grpcCatalog),
 			},
 		},
+		{
+			testName:  "CatalogSourceWithGrpcType/EnsuresImageOrAddressIsSet",
+			namespace: "cool-namespace",
+			catalogSource: &v1alpha1.CatalogSource{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "invalid-spec-catalog",
+					Namespace: "cool-namespace",
+					UID:       types.UID("catalog-uid"),
+					Labels:    map[string]string{"olm.catalogSource": "invalid-spec-catalog"},
+				},
+				Spec: v1alpha1.CatalogSourceSpec{
+					SourceType: v1alpha1.SourceTypeGrpc,
+				},
+			},
+			expectedStatus: &v1alpha1.CatalogSourceStatus{
+				Message: fmt.Sprintf("image and address unset: at least one must be set for sourcetype: %s", v1alpha1.SourceTypeGrpc),
+				Reason:  v1alpha1.CatalogSourceSpecInvalidError,
+			},
+			expectedError: nil,
+		},
+		{
+			testName:  "CatalogSourceWithInternalType/EnsuresConfigMapIsSet",
+			namespace: "cool-namespace",
+			catalogSource: &v1alpha1.CatalogSource{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "invalid-spec-catalog",
+					Namespace: "cool-namespace",
+					UID:       types.UID("catalog-uid"),
+					Labels:    map[string]string{"olm.catalogSource": "invalid-spec-catalog"},
+				},
+				Spec: v1alpha1.CatalogSourceSpec{
+					SourceType: v1alpha1.SourceTypeInternal,
+				},
+			},
+			expectedStatus: &v1alpha1.CatalogSourceStatus{
+				Message: fmt.Sprintf("configmap name unset: must be set for sourcetype: %s", v1alpha1.SourceTypeInternal),
+				Reason:  v1alpha1.CatalogSourceSpecInvalidError,
+			},
+			expectedError: nil,
+		},
+		{
+			testName:  "CatalogSourceWithConfigMapType/EnsuresConfigMapIsSet",
+			namespace: "cool-namespace",
+			catalogSource: &v1alpha1.CatalogSource{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "invalid-spec-catalog",
+					Namespace: "cool-namespace",
+					UID:       types.UID("catalog-uid"),
+					Labels:    map[string]string{"olm.catalogSource": "invalid-spec-catalog"},
+				},
+				Spec: v1alpha1.CatalogSourceSpec{
+					SourceType: v1alpha1.SourceTypeConfigmap,
+				},
+			},
+			expectedStatus: &v1alpha1.CatalogSourceStatus{
+				Message: fmt.Sprintf("configmap name unset: must be set for sourcetype: %s", v1alpha1.SourceTypeConfigmap),
+				Reason:  v1alpha1.CatalogSourceSpecInvalidError,
+			},
+			expectedError: nil,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.testName, func(t *testing.T) {
diff --git a/pkg/controller/operators/catalog/subscription/reconciler_test.go b/pkg/controller/operators/catalog/subscription/reconciler_test.go
index 215d7356f..953e1f2d8 100644
--- a/pkg/controller/operators/catalog/subscription/reconciler_test.go
+++ b/pkg/controller/operators/catalog/subscription/reconciler_test.go
@@ -2,6 +2,7 @@ package subscription
 
 import (
 	"context"
+	"fmt"
 	"testing"
 	"time"
 
@@ -100,6 +101,76 @@ func TestCatalogHealthReconcile(t *testing.T) {
 				),
 			},
 		},
+		{
+			description: "ExistsToUnhealthy/InvalidCatalogSpec",
+			fields: fields{
+				config: &fakeReconcilerConfig{
+					now:                    nowFunc,
+					globalCatalogNamespace: "global",
+					existingObjs: existingObjs{
+						clientObjs: []runtime.Object{
+							func() *v1alpha1.CatalogSource {
+								cs := catalogSource("ns", "cs-0")
+								cs.Spec = v1alpha1.CatalogSourceSpec{
+									SourceType: v1alpha1.SourceTypeGrpc,
+								}
+								cs.Status = v1alpha1.CatalogSourceStatus{
+									Reason:  v1alpha1.CatalogSourceSpecInvalidError,
+									Message: fmt.Sprintf("image and address unset: at least one must be set for sourcetype: %s", v1alpha1.SourceTypeGrpc),
+								}
+
+								return cs
+							}(),
+							&v1alpha1.Subscription{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:      "sub",
+									Namespace: "ns",
+								},
+								Spec: &v1alpha1.SubscriptionSpec{
+									CatalogSourceNamespace: "ns",
+									CatalogSource:          "cs-0",
+								},
+							},
+						},
+					},
+				},
+			},
+			args: args{
+				in: newSubscriptionExistsState(
+					&v1alpha1.Subscription{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "sub",
+							Namespace: "ns",
+						},
+						Spec: &v1alpha1.SubscriptionSpec{
+							CatalogSourceNamespace: "ns",
+							CatalogSource:          "cs-0",
+						},
+					},
+				),
+			},
+			want: want{
+				out: newCatalogUnhealthyState(&v1alpha1.Subscription{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "sub",
+						Namespace: "ns",
+					},
+					Spec: &v1alpha1.SubscriptionSpec{
+						CatalogSourceNamespace: "ns",
+						CatalogSource:          "cs-0",
+					},
+					Status: v1alpha1.SubscriptionStatus{
+						CatalogHealth: []v1alpha1.SubscriptionCatalogHealth{
+							catalogHealth("ns", "cs-0", &now, false),
+						},
+						Conditions: []v1alpha1.SubscriptionCondition{
+							catalogUnhealthyCondition(corev1.ConditionTrue, v1alpha1.CatalogSourcesAdded, "targeted catalogsource ns/cs-0 unhealthy", &now),
+						},
+						LastUpdated: now,
+					},
+				}),
+			},
+		},
 		{
 			description: "ExistsToUnhealthy/Catalogs/NoChanges",
 			fields: fields{
@@ -342,6 +413,94 @@ func TestCatalogHealthReconcile(t *testing.T) {
 				}),
 			},
 		},
+		{
+			description: "HealthyToUnhealthy/InvalidCatalogSpec",
+			fields: fields{
+				config: &fakeReconcilerConfig{
+					now:                    nowFunc,
+					globalCatalogNamespace: "global",
+					existingObjs: existingObjs{
+						clientObjs: []runtime.Object{
+							func() *v1alpha1.CatalogSource {
+								cs := catalogSource("ns", "cs-0")
+								cs.Spec = v1alpha1.CatalogSourceSpec{
+									SourceType: v1alpha1.SourceTypeGrpc,
+								}
+								cs.Status = v1alpha1.CatalogSourceStatus{
+									Reason:  v1alpha1.CatalogSourceSpecInvalidError,
+									Message: fmt.Sprintf("image and address unset: at least one must be set for sourcetype: %s", v1alpha1.SourceTypeGrpc),
+								}
+
+								return cs
+							}(),
+							&v1alpha1.Subscription{
+								ObjectMeta: metav1.ObjectMeta{
+									Name:      "sub",
+									Namespace: "ns",
+								},
+								Spec: &v1alpha1.SubscriptionSpec{
+									CatalogSourceNamespace: "ns",
+									CatalogSource:          "cs-0",
+								},
+								Status: v1alpha1.SubscriptionStatus{
+									CatalogHealth: []v1alpha1.SubscriptionCatalogHealth{
+										catalogHealth("ns", "cs-0", &earlier, true),
+									},
+									Conditions: []v1alpha1.SubscriptionCondition{
+										catalogUnhealthyCondition(corev1.ConditionFalse, v1alpha1.AllCatalogSourcesHealthy, "all available catalogsources are healthy", &earlier),
+									},
+									LastUpdated: earlier,
+								},
+							},
+						},
+					},
+				},
+			},
+			args: args{
+				in: newSubscriptionExistsState(
+					&v1alpha1.Subscription{
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      "sub",
+							Namespace: "ns",
+						},
+						Spec: &v1alpha1.SubscriptionSpec{
+							CatalogSourceNamespace: "ns",
+							CatalogSource:          "cs-0",
+						},
+						Status: v1alpha1.SubscriptionStatus{
+							CatalogHealth: []v1alpha1.SubscriptionCatalogHealth{
+								catalogHealth("ns", "cs-0", &earlier, true),
+							},
+							Conditions: []v1alpha1.SubscriptionCondition{
+								catalogUnhealthyCondition(corev1.ConditionFalse, v1alpha1.AllCatalogSourcesHealthy, "all available catalogsources are healthy", &earlier),
+							},
+							LastUpdated: earlier,
+						},
+					},
+				),
+			},
+			want: want{
+				out: newCatalogUnhealthyState(&v1alpha1.Subscription{
+					ObjectMeta: metav1.ObjectMeta{
+						Name:      "sub",
+						Namespace: "ns",
+					},
+					Spec: &v1alpha1.SubscriptionSpec{
+						CatalogSourceNamespace: "ns",
+						CatalogSource:          "cs-0",
+					},
+					Status: v1alpha1.SubscriptionStatus{
+						CatalogHealth: []v1alpha1.SubscriptionCatalogHealth{
+							catalogHealth("ns", "cs-0", &now, false),
+						},
+						Conditions: []v1alpha1.SubscriptionCondition{
+							catalogUnhealthyCondition(corev1.ConditionTrue, v1alpha1.CatalogSourcesUpdated, "targeted catalogsource ns/cs-0 unhealthy", &now),
+						},
+						LastUpdated: now,
+					},
+				}),
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.description, func(t *testing.T) {
diff --git a/pkg/package-server/provider/registry_test.go b/pkg/package-server/provider/registry_test.go
index 2d5703b38..1d4410cb1 100644
--- a/pkg/package-server/provider/registry_test.go
+++ b/pkg/package-server/provider/registry_test.go
@@ -8,14 +8,13 @@ import (
 	"encoding/json"
 	"fmt"
 	"io"
+	"k8s.io/apimachinery/pkg/selection"
 	"net"
 	"os"
 	"path/filepath"
 	"testing"
 	"time"
 
-	"k8s.io/apimachinery/pkg/selection"
-
 	"github.com/operator-framework/operator-registry/pkg/api"
 	registryserver "github.com/operator-framework/operator-registry/pkg/server"
 	"github.com/operator-framework/operator-registry/pkg/sqlite"
@@ -196,7 +195,7 @@ func TestToPackageManifest(t *testing.T) {
 							CurrentCSVDesc: func() operators.CSVDescription {
 								csv := operatorsv1alpha1.ClusterServiceVersion{}
 								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+								return operators.CreateCSVDescription(&csv)
 							}(),
 						},
 					},
@@ -258,7 +257,7 @@ func TestToPackageManifest(t *testing.T) {
 							CurrentCSVDesc: func() operators.CSVDescription {
 								csv := operatorsv1alpha1.ClusterServiceVersion{}
 								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+								return operators.CreateCSVDescription(&csv)
 							}(),
 						},
 					},
@@ -323,7 +322,7 @@ func TestToPackageManifest(t *testing.T) {
 							CurrentCSVDesc: func() operators.CSVDescription {
 								csv := operatorsv1alpha1.ClusterServiceVersion{}
 								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+								return operators.CreateCSVDescription(&csv)
 							}(),
 						},
 					},
@@ -388,7 +387,7 @@ func TestToPackageManifest(t *testing.T) {
 							CurrentCSVDesc: func() operators.CSVDescription {
 								csv := operatorsv1alpha1.ClusterServiceVersion{}
 								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+								return operators.CreateCSVDescription(&csv)
 							}(),
 						},
 					},
@@ -508,7 +507,7 @@ func TestRegistryProviderGet(t *testing.T) {
 							CurrentCSVDesc: func() operators.CSVDescription {
 								csv := operatorsv1alpha1.ClusterServiceVersion{}
 								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+								return operators.CreateCSVDescription(&csv)
 							}(),
 						},
 					},
@@ -557,7 +556,7 @@ func TestRegistryProviderGet(t *testing.T) {
 							CurrentCSVDesc: func() operators.CSVDescription {
 								csv := operatorsv1alpha1.ClusterServiceVersion{}
 								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+								return operators.CreateCSVDescription(&csv)
 							}(),
 						},
 					},
@@ -605,7 +604,7 @@ func TestRegistryProviderGet(t *testing.T) {
 							CurrentCSVDesc: func() operators.CSVDescription {
 								csv := operatorsv1alpha1.ClusterServiceVersion{}
 								require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-								return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+								return operators.CreateCSVDescription(&csv)
 							}(),
 						},
 					},
@@ -692,7 +691,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -726,7 +725,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -772,7 +771,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -806,7 +805,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -852,7 +851,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -886,7 +885,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -920,7 +919,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -954,7 +953,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -1043,7 +1042,7 @@ func TestRegistryProviderList(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -1135,7 +1134,7 @@ func TestRegistryProviderListLabels(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -1169,7 +1168,7 @@ func TestRegistryProviderListLabels(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -1218,7 +1217,7 @@ func TestRegistryProviderListLabels(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -1252,7 +1251,7 @@ func TestRegistryProviderListLabels(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -1311,7 +1310,7 @@ func TestRegistryProviderListLabels(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(prometheusCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, prometheusCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
@@ -1345,7 +1344,7 @@ func TestRegistryProviderListLabels(t *testing.T) {
 								CurrentCSVDesc: func() operators.CSVDescription {
 									csv := operatorsv1alpha1.ClusterServiceVersion{}
 									require.NoError(t, json.Unmarshal([]byte(etcdCSVJSON), &csv))
-									return operators.CreateCSVDescription(&csv, etcdCSVJSON)
+									return operators.CreateCSVDescription(&csv)
 								}(),
 							},
 						},
diff --git a/test/e2e/packagemanifest_e2e_test.go b/test/e2e/packagemanifest_e2e_test.go
index 9b97e71bd..9fbabd25f 100644
--- a/test/e2e/packagemanifest_e2e_test.go
+++ b/test/e2e/packagemanifest_e2e_test.go
@@ -1,7 +1,6 @@
 package e2e
 
 import (
-	"encoding/json"
 	"time"
 
 	"github.com/blang/semver"
@@ -45,8 +44,6 @@ var _ = Describe("Package Manifest", func() {
 		namedStrategy := newNginxInstallStrategy(genName("dep-"), nil, nil)
 		csv := newCSV(packageStable, testNamespace, "", semver.MustParse("0.1.0"), []apiextensions.CustomResourceDefinition{crd}, nil, namedStrategy)
 		csv.SetLabels(map[string]string{"projected": "label"})
-		csv.Spec.NativeAPIs = []metav1.GroupVersionKind{{Group: "kubenative.io", Version: "v1", Kind: "Native"}}
-		csvJSON, _ := json.Marshal(csv)
 		c := newKubeClient(GinkgoT())
 		crc := newCRClient(GinkgoT())
 		pmc := newPMClient(GinkgoT())
@@ -59,7 +56,7 @@ var _ = Describe("Package Manifest", func() {
 				{
 					Name:           stableChannel,
 					CurrentCSV:     packageStable,
-					CurrentCSVDesc: packagev1.CreateCSVDescription(&csv, string(csvJSON)),
+					CurrentCSVDesc: packagev1.CreateCSVDescription(&csv),
 				},
 			},
 			DefaultChannel: stableChannel,
@@ -88,9 +85,6 @@ var _ = Describe("Package Manifest", func() {
 		require.NotNil(GinkgoT(), pm)
 		require.Equal(GinkgoT(), packageName, pm.GetName())
 		require.Equal(GinkgoT(), expectedStatus, pm.Status)
-		require.Equal(GinkgoT(), "0.0.0", pm.Status.Channels[0].CurrentCSVDesc.MinKubeVersion)
-		require.Equal(GinkgoT(), *dummyImage, pm.Status.Channels[0].CurrentCSVDesc.RelatedImages[0])
-		require.Equal(GinkgoT(), csv.Spec.NativeAPIs, pm.Status.Channels[0].CurrentCSVDesc.NativeAPIs)
 		require.Equal(GinkgoT(), "label", pm.GetLabels()["projected"])
 		require.Equal(GinkgoT(), "supported", pm.GetLabels()["operatorframework.io/arch.amd64"])
 		require.Equal(GinkgoT(), "supported", pm.GetLabels()["operatorframework.io/os.linux"])
@@ -101,72 +95,6 @@ var _ = Describe("Package Manifest", func() {
 		require.NotNil(GinkgoT(), pmList.ListMeta, "package manifest list metadata empty")
 		require.NotNil(GinkgoT(), pmList.Items)
 	})
-	It("loading relatedImages", func() {
-
-		defer cleaner.NotifyTestComplete(GinkgoT(), true)
-
-		sourceName := genName("catalog-")
-		packageName := "etcd-test"
-		image := "quay.io/olmtest/catsrc-update-test:related"
-
-		crc := newCRClient(GinkgoT())
-		pmc := newPMClient(GinkgoT())
-
-		source := &v1alpha1.CatalogSource{
-			TypeMeta: metav1.TypeMeta{
-				Kind:       v1alpha1.CatalogSourceKind,
-				APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      sourceName,
-				Namespace: testNamespace,
-				Labels:    map[string]string{"olm.catalogSource": sourceName},
-			},
-			Spec: v1alpha1.CatalogSourceSpec{
-				SourceType: v1alpha1.SourceTypeGrpc,
-				Image:      image,
-			},
-		}
-
-		expectedRelatedImages := map[string]string{
-			"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84":          "",
-			"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f":          "",
-			"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2": "",
-		}
-
-		source, err := crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Create(source)
-		require.NoError(GinkgoT(), err)
-		defer func() {
-			require.NoError(GinkgoT(), crc.OperatorsV1alpha1().CatalogSources(source.GetNamespace()).Delete(source.GetName(), &metav1.DeleteOptions{}))
-		}()
-
-		// Wait for package-server to be ready
-		err = wait.Poll(pollInterval, 1*time.Minute, func() (bool, error) {
-			GinkgoT().Logf("Polling package-server...")
-			_, err := pmc.OperatorsV1().PackageManifests(testNamespace).List(metav1.ListOptions{})
-			if err == nil {
-				return true, nil
-			}
-			return false, nil
-		})
-		require.NoError(GinkgoT(), err, "package-server not available")
-
-		_, err = fetchCatalogSource(GinkgoT(), crc, source.GetName(), testNamespace, catalogSourceRegistryPodSynced)
-		require.NoError(GinkgoT(), err)
-
-		pm, err := fetchPackageManifest(GinkgoT(), pmc, testNamespace, packageName, packageManifestHasStatus)
-		require.NoError(GinkgoT(), err, "error getting package manifest")
-		require.NotNil(GinkgoT(), pm)
-		require.Equal(GinkgoT(), packageName, pm.GetName())
-
-		relatedImages := pm.Status.Channels[0].CurrentCSVDesc.RelatedImages
-		require.Equal(GinkgoT(), len(expectedRelatedImages), len(relatedImages))
-
-		for _, v := range relatedImages {
-			_, ok := expectedRelatedImages[v]
-			require.True(GinkgoT(), ok, "Expect this image %s to exist in the related images list\n", v)
-		}
-	})
 })
 
 type packageManifestCheckFunc func(*packagev1.PackageManifest) bool
diff --git a/test/e2e/subscription_e2e_test.go b/test/e2e/subscription_e2e_test.go
index 7b224a891..101332e2a 100644
--- a/test/e2e/subscription_e2e_test.go
+++ b/test/e2e/subscription_e2e_test.go
@@ -6,21 +6,13 @@ import (
 	"reflect"
 	"strings"
 	"sync"
-
 	"time"
 
 	"github.com/blang/semver"
 	"github.com/ghodss/yaml"
 	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
 	configv1client "github.com/openshift/client-go/config/clientset/versioned/typed/config/v1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/comparison"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
-	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/version"
 	"github.com/stretchr/testify/require"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -30,6 +22,15 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/tools/clientcmd"
+
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/comparison"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/version"
 )
 
 var _ = Describe("Subscription", func() {
@@ -574,47 +575,294 @@ var _ = Describe("Subscription", func() {
 		require.NoError(GinkgoT(), err)
 	})
 
-	// TestSubscriptionStatusMissingTargetCatalogSource ensures that a Subscription has the appropriate status condition when
-	// its target catalog is missing.
-	//
-	// Steps:
-	// 1. Generate an initial CatalogSource in the target namespace
-	// 2. Generate Subscription, "sub", targetting non-existent CatalogSource, "missing"
-	// 3. Wait for sub status to show SubscriptionCatalogSourcesUnhealthy with status True, reason CatalogSourcesUpdated, and appropriate missing message
-	// 4. Update sub's spec to target the "mysubscription"
-	// 5. Wait for sub's status to show SubscriptionCatalogSourcesUnhealthy with status False, reason AllCatalogSourcesHealthy, and reason "all available catalogsources are healthy"
-	// 6. Wait for sub to succeed
-	It("status missing target catalog source", func() {
-
-		defer cleaner.NotifyTestComplete(GinkgoT(), true)
-
-		c := newKubeClient(GinkgoT())
-		crc := newCRClient(GinkgoT())
-		defer func() {
-			require.NoError(GinkgoT(), crc.OperatorsV1alpha1().Subscriptions(testNamespace).DeleteCollection(&metav1.DeleteOptions{}, metav1.ListOptions{}))
-		}()
-		require.NoError(GinkgoT(), initCatalog(GinkgoT(), c, crc))
-
-		missingName := "missing"
-		cleanup := createSubscriptionForCatalog(GinkgoT(), crc, testNamespace, testSubscriptionName, missingName, testPackageName, betaChannel, "", v1alpha1.ApprovalAutomatic)
-		defer cleanup()
+	Describe("puppeting CatalogSource health status", func() {
+		var (
+			c          operatorclient.ClientInterface
+			crc        versioned.Interface
+			getOpts    metav1.GetOptions
+			deleteOpts *metav1.DeleteOptions
+		)
+
+		BeforeEach(func() {
+			c = newKubeClient(GinkgoT())
+			crc = newCRClient(GinkgoT())
+			getOpts = metav1.GetOptions{}
+			deleteOpts = &metav1.DeleteOptions{}
+		})
 
-		sub, err := fetchSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, subscriptionHasCondition(v1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionTrue, v1alpha1.UnhealthyCatalogSourceFound, fmt.Sprintf("targeted catalogsource %s/%s missing", testNamespace, missingName)))
-		require.NoError(GinkgoT(), err)
-		require.NotNil(GinkgoT(), sub)
+		AfterEach(func() {
+			defer cleaner.NotifyTestComplete(GinkgoT(), true)
+			err := crc.OperatorsV1alpha1().Subscriptions(testNamespace).DeleteCollection(&metav1.DeleteOptions{}, metav1.ListOptions{})
+			Expect(err).NotTo(HaveOccurred())
+		})
 
-		// Update sub to target an existing CatalogSource
-		sub.Spec.CatalogSource = catalogSourceName
-		_, err = crc.OperatorsV1alpha1().Subscriptions(testNamespace).Update(sub)
-		require.NoError(GinkgoT(), err)
+		When("missing target catalog", func() {
+			// TestSubscriptionStatusMissingTargetCatalogSource ensures that a Subscription has the appropriate status condition when
+			// its target catalog is missing.
+			//
+			// Steps:
+			// 1. Generate an initial CatalogSource in the target namespace
+			// 2. Generate Subscription, "sub", targetting non-existent CatalogSource, "missing"
+			// 3. Wait for sub status to show SubscriptionCatalogSourcesUnhealthy with status True, reason CatalogSourcesUpdated, and appropriate missing message
+			// 4. Update sub's spec to target the "mysubscription"
+			// 5. Wait for sub's status to show SubscriptionCatalogSourcesUnhealthy with status False, reason AllCatalogSourcesHealthy, and reason "all available catalogsources are healthy"
+			// 6. Wait for sub to succeed
+			It("should surface the missing catalog", func() {
+				err := initCatalog(GinkgoT(), c, crc)
+				Expect(err).NotTo(HaveOccurred())
+
+				missingName := "missing"
+				cleanup := createSubscriptionForCatalog(GinkgoT(), crc, testNamespace, testSubscriptionName, missingName, testPackageName, betaChannel, "", v1alpha1.ApprovalAutomatic)
+				defer cleanup()
+
+				By("detecting its absence")
+				sub, err := fetchSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, subscriptionHasCondition(v1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionTrue, v1alpha1.UnhealthyCatalogSourceFound, fmt.Sprintf("targeted catalogsource %s/%s missing", testNamespace, missingName)))
+				Expect(err).NotTo(HaveOccurred())
+				Expect(sub).ToNot(BeNil())
+
+				// Update sub to target an existing CatalogSource
+				sub.Spec.CatalogSource = catalogSourceName
+				_, err = crc.OperatorsV1alpha1().Subscriptions(testNamespace).Update(sub)
+				Expect(err).NotTo(HaveOccurred())
+
+				// Wait for SubscriptionCatalogSourcesUnhealthy to be false
+				By("detecting a new existing target")
+				_, err = fetchSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, subscriptionHasCondition(v1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionFalse, v1alpha1.AllCatalogSourcesHealthy, "all available catalogsources are healthy"))
+				Expect(err).NotTo(HaveOccurred())
+
+				// Wait for success
+				_, err = fetchSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, subscriptionStateAtLatestChecker)
+				Expect(err).NotTo(HaveOccurred())
+			})
+		})
 
-		// Wait for SubscriptionCatalogSourcesUnhealthy to be false
-		_, err = fetchSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, subscriptionHasCondition(v1alpha1.SubscriptionCatalogSourcesUnhealthy, corev1.ConditionFalse, v1alpha1.AllCatalogSourcesHealthy, "all available catalogsources are healthy"))
-		require.NoError(GinkgoT(), err)
+		When("the target catalog's sourceType", func() {
+			Context("is unknown", func() {
+				It("should surface catalog health", func() {
+					cs := &v1alpha1.CatalogSource{
+						TypeMeta: metav1.TypeMeta{
+							Kind:       v1alpha1.CatalogSourceKind,
+							APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
+						},
+						ObjectMeta: metav1.ObjectMeta{
+							Name: "cs",
+						},
+						Spec: v1alpha1.CatalogSourceSpec{
+							SourceType: "goose",
+						},
+					}
+
+					var err error
+					cs, err = crc.OperatorsV1alpha1().CatalogSources(testNamespace).Create(cs)
+					defer func() {
+						err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(cs.GetName(), deleteOpts)
+						Expect(err).ToNot(HaveOccurred())
+					}()
+
+					subName := genName("sub-")
+					cleanup := createSubscriptionForCatalog(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						cs.GetName(),
+						testPackageName,
+						betaChannel,
+						"",
+						v1alpha1.ApprovalManual,
+					)
+					defer cleanup()
+
+					var sub *v1alpha1.Subscription
+					sub, err = fetchSubscription(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						subscriptionHasCondition(
+							v1alpha1.SubscriptionCatalogSourcesUnhealthy,
+							corev1.ConditionTrue,
+							v1alpha1.UnhealthyCatalogSourceFound,
+							fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()),
+						),
+					)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(sub).ToNot(BeNil())
+
+					// Get the latest CatalogSource
+					cs, err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Get(cs.GetName(), getOpts)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(cs).ToNot(BeNil())
+				})
+			})
+
+			Context("is grpc and its spec is missing the address and image fields", func() {
+				It("should surface catalog health", func() {
+					// Create a CatalogSource pointing to the grpc pod
+					cs := &v1alpha1.CatalogSource{
+						TypeMeta: metav1.TypeMeta{
+							Kind:       v1alpha1.CatalogSourceKind,
+							APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
+						},
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      genName("cs-"),
+							Namespace: testNamespace,
+						},
+						Spec: v1alpha1.CatalogSourceSpec{
+							SourceType: v1alpha1.SourceTypeGrpc,
+						},
+					}
+
+					var err error
+					cs, err = crc.OperatorsV1alpha1().CatalogSources(testNamespace).Create(cs)
+					defer func() {
+						err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(cs.GetName(), deleteOpts)
+						Expect(err).ToNot(HaveOccurred())
+					}()
+
+					subName := genName("sub-")
+					cleanup := createSubscriptionForCatalog(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						cs.GetName(),
+						testPackageName,
+						betaChannel,
+						"",
+						v1alpha1.ApprovalManual,
+					)
+					defer cleanup()
+
+					var sub *v1alpha1.Subscription
+					sub, err = fetchSubscription(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						subscriptionHasCondition(
+							v1alpha1.SubscriptionCatalogSourcesUnhealthy,
+							corev1.ConditionTrue,
+							v1alpha1.UnhealthyCatalogSourceFound,
+							fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()),
+						),
+					)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(sub).ToNot(BeNil())
+				})
+			})
+
+			Context("is internal and its spec is missing the configmap reference", func() {
+				It("should surface catalog health", func() {
+					cs := &v1alpha1.CatalogSource{
+						TypeMeta: metav1.TypeMeta{
+							Kind:       v1alpha1.CatalogSourceKind,
+							APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
+						},
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      genName("cs-"),
+							Namespace: testNamespace,
+						},
+						Spec: v1alpha1.CatalogSourceSpec{
+							SourceType: v1alpha1.SourceTypeInternal,
+						},
+					}
+
+					var err error
+					cs, err = crc.OperatorsV1alpha1().CatalogSources(testNamespace).Create(cs)
+					defer func() {
+						err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(cs.GetName(), deleteOpts)
+						Expect(err).ToNot(HaveOccurred())
+					}()
+
+					subName := genName("sub-")
+					cleanup := createSubscriptionForCatalog(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						cs.GetName(),
+						testPackageName,
+						betaChannel,
+						"",
+						v1alpha1.ApprovalManual,
+					)
+					defer cleanup()
+
+					var sub *v1alpha1.Subscription
+					sub, err = fetchSubscription(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						subscriptionHasCondition(
+							v1alpha1.SubscriptionCatalogSourcesUnhealthy,
+							corev1.ConditionTrue,
+							v1alpha1.UnhealthyCatalogSourceFound,
+							fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()),
+						),
+					)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(sub).ToNot(BeNil())
+				})
+			})
+
+			Context("is configmap and its spec is missing the configmap reference", func() {
+				It("should surface catalog health", func() {
+					cs := &v1alpha1.CatalogSource{
+						TypeMeta: metav1.TypeMeta{
+							Kind:       v1alpha1.CatalogSourceKind,
+							APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
+						},
+						ObjectMeta: metav1.ObjectMeta{
+							Name:      genName("cs-"),
+							Namespace: testNamespace,
+						},
+						Spec: v1alpha1.CatalogSourceSpec{
+							SourceType: v1alpha1.SourceTypeInternal,
+						},
+					}
+
+					var err error
+					cs, err = crc.OperatorsV1alpha1().CatalogSources(testNamespace).Create(cs)
+					defer func() {
+						err = crc.OperatorsV1alpha1().CatalogSources(cs.GetNamespace()).Delete(cs.GetName(), deleteOpts)
+						Expect(err).ToNot(HaveOccurred())
+					}()
+
+					subName := genName("sub-")
+					cleanup := createSubscriptionForCatalog(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						cs.GetName(),
+						testPackageName,
+						betaChannel,
+						"",
+						v1alpha1.ApprovalAutomatic,
+					)
+					defer cleanup()
+
+					var sub *v1alpha1.Subscription
+					sub, err = fetchSubscription(
+						GinkgoT(),
+						crc,
+						cs.GetNamespace(),
+						subName,
+						subscriptionHasCondition(
+							v1alpha1.SubscriptionCatalogSourcesUnhealthy,
+							corev1.ConditionTrue,
+							v1alpha1.UnhealthyCatalogSourceFound,
+							fmt.Sprintf("targeted catalogsource %s/%s unhealthy", cs.GetNamespace(), cs.GetName()),
+						),
+					)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(sub).ToNot(BeNil())
+				})
+			})
+		})
 
-		// Wait for success
-		_, err = fetchSubscription(GinkgoT(), crc, testNamespace, testSubscriptionName, subscriptionStateAtLatestChecker)
-		require.NoError(GinkgoT(), err)
 	})
 
 	// TestSubscriptionInstallPlanStatus ensures that a Subscription has the appropriate status conditions for possible referenced
@@ -920,7 +1168,7 @@ var _ = Describe("Subscription", func() {
 		}
 
 		podTolerations := []corev1.Toleration{
-			corev1.Toleration{
+			{
 				Key:      "my-toleration-key",
 				Value:    "my-toleration-value",
 				Effect:   corev1.TaintEffectNoSchedule,