Repository: redhat-developer/service-binding-operator
Pull request #367 (2020-03-09 03:37:40)

Use any resource as a backing service
Summary:
Use any resource ( CR / non-CR )  which has a `spec` and a `status` as a backing service


### Motivation

So far, objects associated with CRDs/OLM CSVs were supported as backing services. However, in reality, binding information can be present in standalone objects like routes, services, deployments too.

### Changes

**Then**

Binding metadata was being read from the backing service's associated CRD or CSV.

Here's how the binding metadata resolution looked like:
- Look up annotations in CRD
- Look up descriptors in CSV

If the latter is present, then CRD annotations are ignored.

**Now**

Binding metadata is being read from annotations on the backing service ( like CR, Route, Service, << any kube object with a `spec` and `status` >> itself, along with associated CRD or CSV.

Here's how the binding metadata resolution looks like in this PR:
1) Look up annotations in the CR or kubernetes resource,
2) Look up annotations in CRD
3) Look up descriptors in CSV ( overrides the CRD annotations ..)

Provide cumulative annotations : (1) and (2 & 3).

### Testing

Create a Route and annotate it:

```
kind: Route
apiVersion: route.openshift.io/v1
metadata:
  name: example
  namespace: sbose
  annotations:
    openshift.io/host.generated: 'true'
    servicebindingoperator.redhat.io/spec.host: 'binding:env:attribute'
spec:
  host: example-sbose.apps.ci-ln-smyggvb-d5d6b.origin-ci-int-aws.dev.rhcloud.com
  path: /
  to:
    kind: Service
    name: example
    weight: 100
  port:
    targetPort: 80
  wildcardPolicy: None
```
Create a ServiceBindingRequest

```
apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest

metadata: 
  name: eventsource-binding-foo-bar
  namespace: service-binding-demo

spec: 
  applicationSelector: 
    group: ""
    resource: deployments
    resourceRef: my-node-app
    version: v1

  backingServiceSelectors:                                                                                                         
    - group: route.openshift.io
      version: v1
      kind: Route # <--- not NECESSARILY a CR
      resourceRef: example. 
   
```

The binding secret would contain

```
ROUTE_HOST: example-sbose.apps.ci-ln-smyggvb-d5d6b.origin-ci-int-aws.dev.rhcloud.com
```


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

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **sbose78**
You can assign the PR to them by writing `/assign @sbose78` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-developer%2Fservice-binding-operator).

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

- **[OWNERS](https://github.com/redhat-developer/service-binding-operator/blob/master/OWNERS)**

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":["sbose78"]} -->
openshift-ci-robot: @sbose78: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/4.4-lint | 420ecaeda327ebb04b2d110e7d2b335f55e2c836 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/367/pull-ci-redhat-developer-service-binding-operator-master-4.4-lint/76) | `/test 4.4-lint`

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=367). [Your PR dashboard](https://prow.svc.ci.openshift.org/pr?query=is:pr+state:open+author:sbose78). 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/redhat-developer/service-binding-operator/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: @sbose78: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/4.4-lint | 420ecaeda327ebb04b2d110e7d2b335f55e2c836 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/367/pull-ci-redhat-developer-service-binding-operator-master-4.4-lint/76) | `/test 4.4-lint`
ci/prow/lint | 420ecaeda327ebb04b2d110e7d2b335f55e2c836 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/367/pull-ci-redhat-developer-service-binding-operator-master-lint/1155) | `/test lint`

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=367). [Your PR dashboard](https://prow.svc.ci.openshift.org/pr?query=is:pr+state:open+author:sbose78). 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/redhat-developer/service-binding-operator/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 -->
codecov[bot]: # [Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367?src=pr&el=h1) Report
> :exclamation: No coverage uploaded for pull request base (`master@cbb876e`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference#section-missing-base-commit).
> The diff coverage is `95.23%`.

[![Impacted file tree graph](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367/graphs/tree.svg?width=650&token=CICBeVWlrC&height=150&src=pr)](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##             master     #367   +/-   ##
=========================================
  Coverage          ?   64.99%           
=========================================
  Files             ?       21           
  Lines             ?     1414           
  Branches          ?        0           
=========================================
  Hits              ?      919           
  Misses            ?      371           
  Partials          ?      124
```


| [Impacted Files](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [...troller/servicebindingrequest/custom\_env\_parser.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367/diff?src=pr&el=tree#diff-cGtnL2NvbnRyb2xsZXIvc2VydmljZWJpbmRpbmdyZXF1ZXN0L2N1c3RvbV9lbnZfcGFyc2VyLmdv) | `75% <ø> (ø)` | |
| [pkg/controller/servicebindingrequest/binder.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367/diff?src=pr&el=tree#diff-cGtnL2NvbnRyb2xsZXIvc2VydmljZWJpbmRpbmdyZXF1ZXN0L2JpbmRlci5nbw==) | `58.53% <ø> (ø)` | |
| [pkg/controller/servicebindingrequest/planner.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367/diff?src=pr&el=tree#diff-cGtnL2NvbnRyb2xsZXIvc2VydmljZWJpbmRpbmdyZXF1ZXN0L3BsYW5uZXIuZ28=) | `96.61% <100%> (ø)` | |
| [pkg/controller/servicebindingrequest/olm.go](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367/diff?src=pr&el=tree#diff-cGtnL2NvbnRyb2xsZXIvc2VydmljZWJpbmRpbmdyZXF1ZXN0L29sbS5nbw==) | `72.95% <93.33%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367?src=pr&el=footer). Last update [cbb876e...b3ec581](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/367?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

navidsh: @sbose78 Is this PR going to support binding to k8s objects with no group e.g. `service` or `secrets`?
sbose78: @navidsh 

That was my original intention.

I wanted to support `secrets` and `configmaps` too but then I realized that it would need further restructuring/refactoring because `secrets` and `configmaps` don't have a `spec` section. The relevant section there is `data`.

In a follow-up PR, we'll add support for that. Then we'll be able to pick up any "subscription" secret/configmap and use them as a source of binding data.
sbose78:  > Is this PR going to support binding to k8s objects with no group e.g. service or secrets?

Anything with a `spec` and `status` will be supported after this PR is in. So, `service`, yes.
navidsh: @sbose78 seems like group is required based on the CRD: https://github.com/redhat-developer/service-binding-operator/blob/58ea511ea0dabc67c2d05cff8ed422807bf278c9/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml#L117-L121
sbose78: @navidsh , it should take an empty string I think. 

```
backingServiceSelectors:                                                                                                         
    - group: ""
      version: v1
      kind: Service # <--- not NECESSARILY a CR
      resourceRef: example. 
```

I will double check, and a test for it.
navidsh: Yea, you already had it in the desc for app selector, just noticed it⚡️thx
openshift-ci-robot: @sbose78: PR needs rebase.

<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.
</details>
openshift-ci-robot: @sbose78: PR needs rebase.

<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.
</details>

Patch:
diff --git a/.travis.yml b/.travis.yml
index ca5ea17e..dd283c2e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -37,7 +37,7 @@ script:
   - if [ "$TRAVIS_BRANCH" = "master" ]; then
     make merge-to-master-release &&
     make push-to-manifest-repo &&
-    make push-bundle-to-quay || exit 1;
+    make prepare-bundle-to-quay || exit 1;
     fi
 
 after_success:
@@ -50,3 +50,5 @@ after_success:
   - git add .
   - git commit -m ${MESSAGE}
   - git push "https://${GITHUB_TOKEN}@${GH_REMOTE_REPO}" master > /dev/null 2>&1
+  - cd ..
+  - make push-bundle-to-quay
diff --git a/Makefile b/Makefile
index 64fd529e..beaebb42 100644
--- a/Makefile
+++ b/Makefile
@@ -99,9 +99,14 @@ GO_PACKAGE_ORG_NAME ?= $(shell basename $$(dirname $$PWD))
 GO_PACKAGE_REPO_NAME ?= $(shell basename $$PWD)
 GO_PACKAGE_PATH ?= github.com/${GO_PACKAGE_ORG_NAME}/${GO_PACKAGE_REPO_NAME}
 
+PROJECT_DIR ?= $(dir $(realpath $(firstword $(MAKEFILE_LIST))))
+SBR_MANIFESTS ?= ${PROJECT_DIR}service-binding-operator-manifests
+
 CGO_ENABLED ?= 0
 GO111MODULE ?= on
 GOCACHE ?= "$(shell echo ${PWD})/out/gocache"
+OS ?= linux
+ARCH ?= amd64
 
 # This variable is for artifacts to be archived by Prow jobs at OpenShift CI
 # The actual value will be set by the OpenShift CI accordingly
@@ -122,8 +127,8 @@ OPERATOR_TAG_LONG ?= $(OPERATOR_VERSION)-$(GIT_COMMIT_ID)
 OPERATOR_IMAGE_BUILDER ?= buildah
 OPERATOR_SDK_EXTRA_ARGS ?= "--debug"
 COMMIT_COUNT := $(shell git rev-list --count HEAD)
-BUNDLE_VERSION ?= $(OPERATOR_VERSION)-$(COMMIT_COUNT)
-BASE_BUNDLE_VERSION ?= 0.1.0
+BASE_BUNDLE_VERSION ?= $(OPERATOR_VERSION)
+BUNDLE_VERSION ?= $(BASE_BUNDLE_VERSION)-$(COMMIT_COUNT)
 OPERATOR_IMAGE_REF ?= $(OPERATOR_IMAGE_REL):$(GIT_COMMIT_ID)
 CSV_PACKAGE_NAME ?= $(GO_PACKAGE_REPO_NAME)
 CSV_CREATION_TIMESTAMP ?= $(shell TZ=GMT date '+%FT%TZ')
@@ -283,7 +288,7 @@ test-e2e-olm-ci:
 build: out/operator
 
 out/operator:
-	$(Q)GOARCH=amd64 GOOS=linux go build ${V_FLAG} -o $(OUTPUT_DIR)/operator cmd/manager/main.go
+	$(Q)GOARCH=$(ARCH) GOOS=$(OS) go build ${V_FLAG} -o $(OUTPUT_DIR)/operator cmd/manager/main.go
 
 ## Build-Image: using operator-sdk to build a new image
 build-image:
@@ -438,19 +443,25 @@ push-to-manifest-repo:
 	sed -i -e 's,ICON_BASE64_DATA,$(shell base64 --wrap=0 ./assets/icon/red-hat-logo.svg),g' $(MANIFESTS_TMP)/${BUNDLE_VERSION}/*.clusterserviceversion.yaml
 	sed -i -e 's,ICON_MEDIA_TYPE,image/svg+xml,g' $(MANIFESTS_TMP)/${BUNDLE_VERSION}/*.clusterserviceversion.yaml
 
-## -- Target for pushing manifest bundle to quay application --
-.PHONY: push-bundle-to-quay
-## Push manifest bundle to quay application
-push-bundle-to-quay:
+## -- Target for preparing manifest bundle to quay application --
+.PHONY: prepare-bundle-to-quay
+## Prepare manifest bundle to quay application
+prepare-bundle-to-quay:
 	$(Q)python3.7 -m venv $(OUTPUT_DIR)/venv3
 	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install --upgrade setuptools
 	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install --upgrade pip
 	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install operator-courier==2.1.2
 	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier --version
 	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier verify $(MANIFESTS_TMP)
-	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier push $(MANIFESTS_TMP) $(OPERATOR_GROUP) $(GO_PACKAGE_REPO_NAME) $(BUNDLE_VERSION) "$(QUAY_BUNDLE_TOKEN)"
 	rm -rf deploy/olm-catalog/$(GO_PACKAGE_REPO_NAME)/$(BUNDLE_VERSION)
 
+## -- Target to push bundle to quay
+.PHONY: push-bundle-to-quay
+## Push manifest bundle to quay application
+push-bundle-to-quay:
+	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier verify $(SBR_MANIFESTS)
+	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier push $(SBR_MANIFESTS) redhat-developer service-binding-operator $(BUNDLE_VERSION) "$(QUAY_BUNDLE_TOKEN)"
+
 ## -- Target for validating the operator --
 .PHONY: dev-release
 ## validating the operator by installing new quay releases
diff --git a/cmd/manager/main.go b/cmd/manager/main.go
index 873f1b9b..5564a514 100644
--- a/cmd/manager/main.go
+++ b/cmd/manager/main.go
@@ -7,8 +7,7 @@ import (
 	"os"
 	"runtime"
 
-	// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.
-	osappsv1 "github.com/openshift/api/apps/v1"
+	// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
 	"github.com/operator-framework/operator-sdk/pkg/k8sutil"
 	kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics"
 	"github.com/operator-framework/operator-sdk/pkg/leader"
@@ -112,11 +111,6 @@ func main() {
 		os.Exit(1)
 	}
 
-	if err := osappsv1.AddToScheme(mgr.GetScheme()); err != nil {
-		mainLog.Error(err, "Error on adding OS APIs to scheme")
-		os.Exit(1)
-	}
-
 	// Setup all Controllers
 	if err := controller.AddToManager(mgr); err != nil {
 		mainLog.Error(err, "Failed to setup the controller manager")
diff --git a/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml b/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml
index 8337c8ea..67b31830 100644
--- a/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml
+++ b/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml
@@ -253,8 +253,6 @@ spec:
             mountPathPrefix:
               description: MountPathPrefix is the prefix for volume mount
               type: string
-          required:
-          - applicationSelector
           type: object
         status:
           description: ServiceBindingRequestStatus defines the observed state of ServiceBindingRequest
@@ -316,6 +314,11 @@ spec:
             secret:
               description: Secret is the name of the intermediate secret
               type: string
+          required:
+          - applications
+          - bindingStatus
+          - conditions
+          - secret
           type: object
       type: object
   version: v1alpha1
diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt
new file mode 100644
index 00000000..b7c650da
--- /dev/null
+++ b/hack/boilerplate.go.txt
@@ -0,0 +1,16 @@
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
diff --git a/hack/check-installplan.sh b/hack/check-installplan.sh
new file mode 100755
index 00000000..1cb419d5
--- /dev/null
+++ b/hack/check-installplan.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+function check_installplan () {
+    for i in  {1..120} ; do
+        local install_plan=`kubectl get subscriptions service-binding-operator -n openshift-operators -o jsonpath='{.status.installPlanRef.name}'`
+        if [[ ${install_plan} != "" ]] ; then
+            return 0
+        fi
+
+        sleep 3
+    done
+
+    return 1
+}
+
+
+echo "# Searching for install plan."
+
+if ! check_installplan ; then
+    echo "Install plan doesn't exist"
+    exit 1
+fi
+
+INSTALL_PLAN=`kubectl get subscriptions service-binding-operator -n openshift-operators -o jsonpath='{.status.installPlanRef.name}'`
+
+echo "Install plan found: ${INSTALL_PLAN}"
diff --git a/hack/check-operatorsource-pods.sh b/hack/check-operatorsource-pods.sh
new file mode 100755
index 00000000..99417772
--- /dev/null
+++ b/hack/check-operatorsource-pods.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+function check_pod () {
+    for i in  {1..120} ; do
+        local running_status=`kubectl get pods -n openshift-marketplace | grep "example-operators" | awk '{print $3}'`
+        if [[ ${running_status} == "Running" ]] ; then
+            return 0
+        fi
+
+        sleep 3
+    done
+
+    return 1
+}
+
+
+echo "# Searching for operator source pod."
+
+if ! check_pod ; then
+    echo "Operator source pod is not running"
+    exit 1
+fi
+
+RUNNING_STATUS=`kubectl get pods -n openshift-marketplace | grep "example-operators" | awk '{print $3}'`
+
+echo "Operator source pod found with status: ${RUNNING_STATUS}"
diff --git a/hack/dev-release.sh b/hack/dev-release.sh
index bc214316..b2fe83dd 100755
--- a/hack/dev-release.sh
+++ b/hack/dev-release.sh
@@ -7,13 +7,20 @@ OPERATOR_SOURCE="./test/operator-hub/operator_source.yaml"
 SUBSCRIPTION="./test/operator-hub/subscription.yaml"
 INSTALL_PLAN_PRIOR=service-binding-operator.v${BUNDLE_VERSION}
 
+sed -i -e "s,REPLACE_CSV_VERSION,service-binding-operator.v${BUNDLE_VERSION},g" ${SUBSCRIPTION}
+
 kubectl apply -f ${OPERATOR_SOURCE}
-# Subscribing to the operator
-kubectl apply -f ${SUBSCRIPTION}
+./hack/check-operatorsource-pods.sh
 RUNNING_STATUS=`kubectl get pods -n openshift-marketplace | grep "example-operators" | awk '{print $3}'`
-if [ ${RUNNING_STATUS} = "Running" ] ; then
-	echo "Operator marketplace pod is running"
+if [ ${RUNNING_STATUS} != "Running" ] ; then
+	echo "Operator marketplace pod is not running"
 fi
+# Subscribing to the operator
+kubectl apply -f ${SUBSCRIPTION}
+./hack/check-installplan.sh
+INSTALL_PLAN=`kubectl get subscriptions service-binding-operator -n openshift-operators -o jsonpath='{.status.installPlanRef.name}'`
+kubectl patch installplan ${INSTALL_PLAN} -n openshift-operators --type merge --patch '{"spec": {"approved": true}}'
+
 ./hack/check-crds.sh
 ./hack/check-csvs.sh
 INSTALL_PLAN_PRIOR=service-binding-operator.v${BUNDLE_VERSION}
@@ -25,7 +32,7 @@ if [ "${VERSION_NUMBER}" = "${BUNDLE_VERSION}" ] ; then
 	if [ $? = 0 ] ; then
 		echo "CSV alm example validation succeeded"
 	fi
-	if [ `kubectl get installplans -n=openshift-operators -o jsonpath='{.items[*].status.phase}'` == "Complete" ] ; then
+	if [ `kubectl get installplans -n=openshift-operators -o jsonpath='{.items[*].status.phase}'` = "Complete" ] ; then
 		INSTALL_PLAN=`kubectl get installplans -n=openshift-operators -o jsonpath='{.items[*].spec.clusterServiceVersionNames[0]}'`
 		if [ "${INSTALL_PLAN_PRIOR}" = "${INSTALL_PLAN}" ] ; then
 			echo "Install Plan validation succeeded. OLM Bundle Validation succeeded"
diff --git a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
index 4a5c7743..f29f1a7f 100644
--- a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
+++ b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
@@ -3,19 +3,16 @@ package v1alpha1
 import (
 	conditionsv1 "github.com/openshift/custom-resource-status/conditions/v1"
 	corev1 "k8s.io/api/core/v1"
-	v1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
 
 // ServiceBindingRequestSpec defines the desired state of ServiceBindingRequest
-// +k8s:openapi-gen=true
 type ServiceBindingRequestSpec struct {
 	// Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file
 	// Add custom validation using kubebuilder tags:
 	// 	https://book.kubebuilder.io/beyond_basics/generating_crd.html
-
 	// MountPathPrefix is the prefix for volume mount
 	// +optional
 	MountPathPrefix string `json:"mountPathPrefix,omitempty"`
@@ -43,29 +40,29 @@ type ServiceBindingRequestSpec struct {
 
 	// ApplicationSelector is used to identify the application connecting to the
 	// backing service operator.
+	// +optional
 	ApplicationSelector ApplicationSelector `json:"applicationSelector"`
 
 	// DetectBindingResources is flag used to bind all non-bindable variables from
 	// different subresources owned by backing operator CR.
 	// +optional
-	DetectBindingResources bool `json:"detectBindingResources"`
+	DetectBindingResources bool `json:"detectBindingResources,omitempty"`
 }
 
 // ServiceBindingRequestStatus defines the observed state of ServiceBindingRequest
 // +k8s:openapi-gen=true
 type ServiceBindingRequestStatus struct {
 	// BindingStatus is the status of the service binding request.
-	BindingStatus string `json:"bindingStatus,omitempty"`
+	BindingStatus string `json:"bindingStatus"`
 	// Conditions describes the state of the operator's reconciliation functionality.
-	Conditions []conditionsv1.Condition `json:"conditions,omitempty"`
+	Conditions []conditionsv1.Condition `json:"conditions"`
 	// Secret is the name of the intermediate secret
-	Secret string `json:"secret,omitempty"`
+	Secret string `json:"secret"`
 	// ApplicationObjects contains all the application objects filtered by label
-	ApplicationObjects []BoundApplication `json:"applications,omitempty"`
+	Applications []BoundApplication `json:"applications"`
 }
 
 // BackingServiceSelector defines the selector based on resource name, version, and resource kind
-// +k8s:openapi-gen=true
 type BackingServiceSelector struct {
 	metav1.GroupVersionKind `json:",inline"`
 	ResourceRef             string `json:"resourceRef"`
@@ -76,12 +73,11 @@ type BackingServiceSelector struct {
 // BoundApplication defines the application workloads to which the binding secret has
 // injected.
 type BoundApplication struct {
-	metav1.GroupVersionKind `json:",inline"`
-	v1.LocalObjectReference `json:",inline"`
+	metav1.GroupVersionKind     `json:",inline"`
+	corev1.LocalObjectReference `json:",inline"`
 }
 
 // ApplicationSelector defines the selector based on labels and GVR
-// +k8s:openapi-gen=true
 type ApplicationSelector struct {
 	// +optional
 	LabelSelector               *metav1.LabelSelector `json:"labelSelector,omitempty"`
@@ -94,8 +90,8 @@ type ApplicationSelector struct {
 // ServiceBindingRequest expresses intent to bind an operator-backed service with
 // an application workload.
 // +k8s:openapi-gen=true
-// +operator-sdk:gen-csv:customresourcedefinitions.displayName="Service Binding Request"
 // +kubebuilder:subresource:status
+// +operator-sdk:gen-csv:customresourcedefinitions.displayName="Service Binding Request"
 // +kubebuilder:resource:path=servicebindingrequests,shortName=sbr;sbrs
 type ServiceBindingRequest struct {
 	metav1.TypeMeta   `json:",inline"`
diff --git a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
index 17338809..e00095f5 100644
--- a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
@@ -1,5 +1,21 @@
 // +build !ignore_autogenerated
 
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 // Code generated by operator-sdk. DO NOT EDIT.
 
 package v1alpha1
@@ -184,8 +200,8 @@ func (in *ServiceBindingRequestStatus) DeepCopyInto(out *ServiceBindingRequestSt
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
 	}
-	if in.ApplicationObjects != nil {
-		in, out := &in.ApplicationObjects, &out.ApplicationObjects
+	if in.Applications != nil {
+		in, out := &in.Applications, &out.Applications
 		*out = make([]BoundApplication, len(*in))
 		copy(*out, *in)
 	}
diff --git a/pkg/apis/apps/v1alpha1/zz_generated.openapi.go b/pkg/apis/apps/v1alpha1/zz_generated.openapi.go
index b667c6c1..086dbf67 100644
--- a/pkg/apis/apps/v1alpha1/zz_generated.openapi.go
+++ b/pkg/apis/apps/v1alpha1/zz_generated.openapi.go
@@ -1,5 +1,21 @@
 // +build !ignore_autogenerated
 
+/*
+Copyright The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
 // This file was autogenerated by openapi-gen. Do not edit it manually!
 
 package v1alpha1
@@ -11,100 +27,8 @@ import (
 
 func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
 	return map[string]common.OpenAPIDefinition{
-		"./pkg/apis/apps/v1alpha1.ApplicationSelector":         schema_pkg_apis_apps_v1alpha1_ApplicationSelector(ref),
-		"./pkg/apis/apps/v1alpha1.BackingServiceSelector":      schema_pkg_apis_apps_v1alpha1_BackingServiceSelector(ref),
-		"./pkg/apis/apps/v1alpha1.ServiceBindingRequest":       schema_pkg_apis_apps_v1alpha1_ServiceBindingRequest(ref),
-		"./pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec":   schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestSpec(ref),
-		"./pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus": schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestStatus(ref),
-	}
-}
-
-func schema_pkg_apis_apps_v1alpha1_ApplicationSelector(ref common.ReferenceCallback) common.OpenAPIDefinition {
-	return common.OpenAPIDefinition{
-		Schema: spec.Schema{
-			SchemaProps: spec.SchemaProps{
-				Description: "ApplicationSelector defines the selector based on labels and GVR",
-				Type:        []string{"object"},
-				Properties: map[string]spec.Schema{
-					"labelSelector": {
-						SchemaProps: spec.SchemaProps{
-							Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"),
-						},
-					},
-					"group": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-					"version": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-					"resource": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-					"resourceRef": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-				},
-				Required: []string{"group", "version", "resource"},
-			},
-		},
-		Dependencies: []string{
-			"k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
-	}
-}
-
-func schema_pkg_apis_apps_v1alpha1_BackingServiceSelector(ref common.ReferenceCallback) common.OpenAPIDefinition {
-	return common.OpenAPIDefinition{
-		Schema: spec.Schema{
-			SchemaProps: spec.SchemaProps{
-				Description: "BackingServiceSelector defines the selector based on resource name, version, and resource kind",
-				Type:        []string{"object"},
-				Properties: map[string]spec.Schema{
-					"group": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-					"version": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-					"kind": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-					"resourceRef": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-					"namespace": {
-						SchemaProps: spec.SchemaProps{
-							Type:   []string{"string"},
-							Format: "",
-						},
-					},
-				},
-				Required: []string{"group", "version", "kind", "resourceRef"},
-			},
-		},
+		"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequest":       schema_pkg_apis_apps_v1alpha1_ServiceBindingRequest(ref),
+		"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus": schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestStatus(ref),
 	}
 }
 
@@ -136,94 +60,19 @@ func schema_pkg_apis_apps_v1alpha1_ServiceBindingRequest(ref common.ReferenceCal
 					},
 					"spec": {
 						SchemaProps: spec.SchemaProps{
-							Ref: ref("./pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec"),
+							Ref: ref("github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec"),
 						},
 					},
 					"status": {
 						SchemaProps: spec.SchemaProps{
-							Ref: ref("./pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus"),
-						},
-					},
-				},
-			},
-		},
-		Dependencies: []string{
-			"./pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec", "./pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
-	}
-}
-
-func schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
-	return common.OpenAPIDefinition{
-		Schema: spec.Schema{
-			SchemaProps: spec.SchemaProps{
-				Description: "ServiceBindingRequestSpec defines the desired state of ServiceBindingRequest",
-				Type:        []string{"object"},
-				Properties: map[string]spec.Schema{
-					"mountPathPrefix": {
-						SchemaProps: spec.SchemaProps{
-							Description: "MountPathPrefix is the prefix for volume mount",
-							Type:        []string{"string"},
-							Format:      "",
-						},
-					},
-					"envVarPrefix": {
-						SchemaProps: spec.SchemaProps{
-							Description: "EnvVarPrefix is the prefix for environment variables",
-							Type:        []string{"string"},
-							Format:      "",
-						},
-					},
-					"customEnvVar": {
-						SchemaProps: spec.SchemaProps{
-							Description: "Custom env variables",
-							Type:        []string{"array"},
-							Items: &spec.SchemaOrArray{
-								Schema: &spec.Schema{
-									SchemaProps: spec.SchemaProps{
-										Ref: ref("k8s.io/api/core/v1.EnvVar"),
-									},
-								},
-							},
-						},
-					},
-					"backingServiceSelector": {
-						SchemaProps: spec.SchemaProps{
-							Description: "BackingServiceSelector is used to identify the backing service operator. Deprecation Notice: In the upcoming release, this field would be depcreated. It would be mandatory to set \"backingServiceSelectors\".",
-							Ref:         ref("./pkg/apis/apps/v1alpha1.BackingServiceSelector"),
-						},
-					},
-					"backingServiceSelectors": {
-						SchemaProps: spec.SchemaProps{
-							Description: "BackingServiceSelectors is used to identify multiple backing services. This would be made a required field after 'BackingServiceSelector' is removed.",
-							Type:        []string{"array"},
-							Items: &spec.SchemaOrArray{
-								Schema: &spec.Schema{
-									SchemaProps: spec.SchemaProps{
-										Ref: ref("./pkg/apis/apps/v1alpha1.BackingServiceSelector"),
-									},
-								},
-							},
-						},
-					},
-					"applicationSelector": {
-						SchemaProps: spec.SchemaProps{
-							Description: "ApplicationSelector is used to identify the application connecting to the backing service operator.",
-							Ref:         ref("./pkg/apis/apps/v1alpha1.ApplicationSelector"),
-						},
-					},
-					"detectBindingResources": {
-						SchemaProps: spec.SchemaProps{
-							Description: "DetectBindingResources is flag used to bind all non-bindable variables from different subresources owned by backing operator CR.",
-							Type:        []string{"boolean"},
-							Format:      "",
+							Ref: ref("github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus"),
 						},
 					},
 				},
-				Required: []string{"applicationSelector"},
 			},
 		},
 		Dependencies: []string{
-			"./pkg/apis/apps/v1alpha1.ApplicationSelector", "./pkg/apis/apps/v1alpha1.BackingServiceSelector", "k8s.io/api/core/v1.EnvVar"},
+			"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec", "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
 	}
 }
 
@@ -268,16 +117,17 @@ func schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestStatus(ref common.Refere
 							Items: &spec.SchemaOrArray{
 								Schema: &spec.Schema{
 									SchemaProps: spec.SchemaProps{
-										Ref: ref("./pkg/apis/apps/v1alpha1.BoundApplication"),
+										Ref: ref("github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.BoundApplication"),
 									},
 								},
 							},
 						},
 					},
 				},
+				Required: []string{"bindingStatus", "conditions", "secret", "applications"},
 			},
 		},
 		Dependencies: []string{
-			"./pkg/apis/apps/v1alpha1.BoundApplication", "github.com/openshift/custom-resource-status/conditions/v1.Condition"},
+			"github.com/openshift/custom-resource-status/conditions/v1.Condition", "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.BoundApplication"},
 	}
 }
diff --git a/pkg/controller/servicebindingrequest/binding.go b/pkg/controller/servicebindingrequest/binding.go
index 32c8169d..ad024c15 100644
--- a/pkg/controller/servicebindingrequest/binding.go
+++ b/pkg/controller/servicebindingrequest/binding.go
@@ -283,7 +283,7 @@ func (b *ServiceBinder) setApplicationObjects(
 		}
 		boundApps = append(boundApps, boundApp)
 	}
-	sbrStatus.ApplicationObjects = boundApps
+	sbrStatus.Applications = boundApps
 }
 
 // buildPlan creates a new plan.
diff --git a/pkg/controller/servicebindingrequest/olm.go b/pkg/controller/servicebindingrequest/olm.go
index 460ed192..415e960a 100644
--- a/pkg/controller/servicebindingrequest/olm.go
+++ b/pkg/controller/servicebindingrequest/olm.go
@@ -8,6 +8,8 @@ import (
 	olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -42,7 +44,10 @@ func (o *OLM) listCSVs() ([]unstructured.Unstructured, error) {
 	gvr := olmv1alpha1.SchemeGroupVersion.WithResource(csvResource)
 	resourceClient := o.client.Resource(gvr).Namespace(o.ns)
 	csvs, err := resourceClient.List(metav1.ListOptions{})
-	if err != nil {
+	if err != nil && errors.IsNotFound(err) {
+		log.Warning("ClusterServiceVersions CRD is not installed")
+		return nil, nil
+	} else if err != nil {
 		log.Error(err, "during listing CSV objects from cluster")
 		return nil, err
 	}
@@ -171,6 +176,34 @@ func (o *OLM) SelectCRDByGVK(gvk schema.GroupVersionKind, crd *unstructured.Unst
 	return crdDescriptions[0], nil
 }
 
+// buildCRDDescriptionFromCR builds a CRDDescription from annotations present in the CR.
+func buildCRDDescriptionFromCR(cr *unstructured.Unstructured, crdDescription *olmv1alpha1.CRDDescription) error {
+	var (
+		err error
+	)
+
+	gvk := schema.GroupVersionKind{
+		Kind:    cr.GetKind(),
+		Version: cr.GroupVersionKind().Version,
+		Group:   cr.GroupVersionKind().Group,
+	}
+	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+
+	crdDescription.Name = gvr.Resource + "." + gvr.Group
+	crdDescription.Kind = cr.GetKind()
+	crdDescription.Version = cr.GroupVersionKind().Version
+
+	specDescriptors, statusDescriptors, err := buildDescriptorsFromAnnotations(cr.GetAnnotations())
+	if err != nil {
+		return err
+	}
+
+	crdDescription.SpecDescriptors = append(crdDescription.SpecDescriptors, specDescriptors...)
+	crdDescription.StatusDescriptors = append(crdDescription.StatusDescriptors, statusDescriptors...)
+
+	return nil
+}
+
 // buildCRDDescriptionFromCRD builds a CRDDescription from annotations present in the CRD.
 func buildCRDDescriptionFromCRD(crd *unstructured.Unstructured) (*olmv1alpha1.CRDDescription, error) {
 	var (
diff --git a/pkg/controller/servicebindingrequest/planner.go b/pkg/controller/servicebindingrequest/planner.go
index 7428fe5d..16248ce3 100644
--- a/pkg/controller/servicebindingrequest/planner.go
+++ b/pkg/controller/servicebindingrequest/planner.go
@@ -5,11 +5,13 @@ import (
 	"errors"
 
 	"k8s.io/apimachinery/pkg/api/meta"
+
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/client-go/dynamic"
 
+	olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 	v1alpha1 "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
 	"github.com/redhat-developer/service-binding-operator/pkg/log"
 )
@@ -91,31 +93,53 @@ func (p *Planner) Plan() (*Plan, error) {
 	relatedResources := make([]*RelatedResource, 0)
 	for _, s := range selectors {
 
+		var crdDescription *olmv1alpha1.CRDDescription
+
+		if s.Namespace == nil {
+			s.Namespace = &ns
+		}
+
 		bssGVK := schema.GroupVersionKind{Kind: s.Kind, Version: s.Version, Group: s.Group}
 
-		// resolve the CRD using the service's GVK
-		crd, err := p.searchCRD(bssGVK)
+		// Start with looking up if the resource exists
+		// If yes, errors during lookups of the CRD and
+		// the CRD could be ignored.
+		cr, err := p.searchCR(s)
 		if err != nil {
 			return nil, err
 		}
-		p.logger.Debug("Resolved CRD", "CRD", crd)
 
-		// resolve the CRDDescription based on the service's GVK and the resolved CRD
-		olm := NewOLM(p.client, ns)
-		crdDescription, err := olm.SelectCRDByGVK(bssGVK, crd)
+		// resolve the CRD using the service's GVK
+		crd, err := p.searchCRD(bssGVK)
 		if err != nil {
-			return nil, err
+			// expected this to work, but didn't
+			// if k8sError.IsNotFound(err) {...}
+			p.logger.Error(err, "Probably not a CRD")
+
+		} else {
+
+			p.logger.Debug("Resolved CRD", "CRD", crd)
+
+			olm := NewOLM(p.client, ns)
+
+			// Parse annotations from the OLM descriptors or the CRD
+			crdDescription, err = olm.SelectCRDByGVK(bssGVK, crd)
+			if err != nil {
+				p.logger.Error(err, "Probably not an OLM operator")
+			}
+			p.logger.Debug("Tentatively resolved CRDDescription", "CRDDescription", crdDescription)
 		}
-		p.logger.Debug("Resolved CRDDescription", "CRDDescription", crdDescription)
 
-		if s.Namespace == nil {
-			s.Namespace = &ns
+		// Parse ( and override ) annotations from the CR or kubernetes object
+		if crdDescription == nil {
+			crdDescription = &olmv1alpha1.CRDDescription{}
 		}
-		cr, err := p.searchCR(s)
+		err = buildCRDDescriptionFromCR(cr, crdDescription)
 		if err != nil {
 			return nil, err
 		}
 
+		p.logger.Debug("Computed CRDDescription", "CRDDescription", crdDescription)
 		r := &RelatedResource{
 			CRDDescription: crdDescription,
 			CR:             cr,
diff --git a/pkg/controller/servicebindingrequest/sbrcontroller.go b/pkg/controller/servicebindingrequest/sbrcontroller.go
index 3b4efd66..bc61e287 100644
--- a/pkg/controller/servicebindingrequest/sbrcontroller.go
+++ b/pkg/controller/servicebindingrequest/sbrcontroller.go
@@ -5,6 +5,8 @@ import (
 	"strings"
 
 	olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -145,9 +147,18 @@ func (s *SBRController) AddWatchForGVK(gvk schema.GroupVersionKind) error {
 // addCSVWatch creates a watch on ClusterServiceVersion.
 func (s *SBRController) addCSVWatch() error {
 	log := s.logger
+	gvr := olmv1alpha1.SchemeGroupVersion.WithResource(ClusterServiceVersionKind)
+	resourceClient := s.Client.Resource(gvr).Namespace(os.Getenv("WATCH_NAMESPACE"))
+	_, err := resourceClient.List(metav1.ListOptions{})
+	if err != nil && errors.IsNotFound(err) {
+		log.Warning("ClusterServiceVersions CRD is not installed, skip watching")
+		return nil
+	} else if err != nil {
+		return err
+	}
 	csvGVK := olmv1alpha1.SchemeGroupVersion.WithKind(ClusterServiceVersionKind)
 	source := s.createSourceForGVK(csvGVK)
-	err := s.Controller.Watch(source, NewCreateWatchEventHandler(s))
+	err = s.Controller.Watch(source, NewCreateWatchEventHandler(s))
 	if err != nil {
 		return err
 	}
diff --git a/pkg/controller/servicebindingrequest/secret.go b/pkg/controller/servicebindingrequest/secret.go
index 17c181fa..3e7a382c 100644
--- a/pkg/controller/servicebindingrequest/secret.go
+++ b/pkg/controller/servicebindingrequest/secret.go
@@ -18,28 +18,6 @@ type Secret struct {
 	plan   *Plan             // plan instance
 }
 
-// customEnvParser parse informed data in order to interpolate with values provided by custom
-// environment component.
-func (s *Secret) customEnvParser(data map[string][]byte) (map[string][]byte, error) {
-	// transforming input into format expected by custom environment parser
-	cache := make(map[string]interface{})
-	for k, v := range data {
-		cache[k] = v
-	}
-
-	// interpolating custom environment
-	envParser := NewCustomEnvParser(s.plan.SBR.Spec.CustomEnvVar, cache)
-	values, err := envParser.Parse()
-	if err != nil {
-		return nil, err
-	}
-
-	for k, v := range values {
-		data[k] = []byte(v.(string))
-	}
-	return data, nil
-}
-
 // buildResourceClient creates a resource client to handle corev1/secret resource.
 func (s *Secret) buildResourceClient() dynamic.ResourceInterface {
 	gvr := corev1.SchemeGroupVersion.WithResource(SecretResource)


Test patch:
diff --git a/pkg/controller/servicebindingrequest/olm_test.go b/pkg/controller/servicebindingrequest/olm_test.go
index 53c64398..df390c08 100644
--- a/pkg/controller/servicebindingrequest/olm_test.go
+++ b/pkg/controller/servicebindingrequest/olm_test.go
@@ -5,10 +5,15 @@ import (
 	"strings"
 	"testing"
 
+	olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 	"github.com/stretchr/testify/require"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
+	k8stesting "k8s.io/client-go/testing"
 	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
 
 	"github.com/redhat-developer/service-binding-operator/test/mocks"
@@ -27,6 +32,35 @@ func assertGVKs(t *testing.T, gvks []schema.GroupVersionKind) {
 	}
 }
 
+func TestOLMWithoutCSVCRD(t *testing.T) {
+	ns := "controller"
+	f := mocks.NewFake(t, ns)
+	client := f.FakeDynClient()
+	gvr := olmv1alpha1.SchemeGroupVersion.WithResource(csvResource)
+
+	// the original FakeDynClient would not return error for unknown resource
+	// prepend our reactor to mock a not found error like a real API server
+	client.PrependReactor("*", "*", func(action k8stesting.Action) (bool, runtime.Object, error) {
+		if gvr.String() == action.GetResource().String() {
+			return true, nil, errors.NewNotFound(gvr.GroupResource(), "the server could not find the requested resource")
+		}
+		return false, nil, nil
+	})
+	olm := NewOLM(client, ns)
+
+	t.Run("listCSVs without CSV CRD installed", func(t *testing.T) {
+		resourceClient := client.Resource(gvr).Namespace(ns)
+		objs, err := resourceClient.List(metav1.ListOptions{})
+		require.Error(t, err)
+		require.True(t, errors.IsNotFound(err))
+		require.Nil(t, objs)
+
+		csvs, err := olm.listCSVs()
+		require.NoError(t, err)
+		require.Len(t, csvs, 0)
+	})
+}
+
 func TestOLMNew(t *testing.T) {
 	ns := "controller"
 	csvName := "unit-csv"
@@ -76,56 +110,123 @@ func TestOLMNew(t *testing.T) {
 		assertGVKs(t, gvks)
 	})
 }
-
 func TestAnnotationParsing(t *testing.T) {
 	annotations := map[string]interface{}{
 		"servicebindingoperator.redhat.io/status.dbCredentials-db.password": "binding:env:object:secret",
 		"servicebindingoperator.redhat.io/spec.dbName":                      "binding:env:attribute",
-		"servicebindingoperator.redhat.io/spec.image":                       "binding:env:attribute",
 		"servicebindingoperator.redhat.io/status.dbConfigMap-db.host":       "binding:env:object:configmap",
 	}
-	crd := &unstructured.Unstructured{
-		Object: map[string]interface{}{
-			"apiVersion": "apiextensions.k8s.io/v1beta1",
-			"kind":       "CustomResourceDefinition",
-			"metadata":   map[string]interface{}{},
-			"spec": map[string]interface{}{
-				"names": map[string]interface{}{
-					"kind": "Carp",
+
+	t.Run("Build CSV from CRD", func(t *testing.T) {
+		crd := &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": "apiextensions.k8s.io/v1beta1",
+				"kind":       "CustomResourceDefinition",
+				"metadata": map[string]interface{}{
+					"annotations": annotations,
+				},
+				"spec": map[string]interface{}{
+					"names": map[string]interface{}{
+						"kind": "Carp",
+					},
+					"group":   "app.dev",
+					"version": "v1",
 				},
-				"group":   "app.dev",
-				"version": "v1",
+				"status": map[string]interface{}{},
 			},
-			"status": map[string]interface{}{},
-		},
-	}
-	t.Run("Build CSV from CRD - no annotations", func(t *testing.T) {
+		}
 		crdDescription, err := buildCRDDescriptionFromCRD(crd)
 		require.NoError(t, err)
-		require.Len(t, crdDescription.SpecDescriptors, 0)
-		require.Len(t, crdDescription.StatusDescriptors, 0)
+
+		require.Len(t, crdDescription.StatusDescriptors, 2)
+
+		require.Equal(t, "dbName", crdDescription.SpecDescriptors[0].Path)
+		require.Equal(t, "binding:env:attribute:spec.dbName", crdDescription.SpecDescriptors[0].XDescriptors[0])
+
+		expected := map[string]string{
+			"dbName":        "binding:env:attribute:spec.dbName",
+			"dbCredentials": "binding:env:object:secret:db.password",
+			"dbConfigMap":   "binding:env:object:configmap:db.host",
+		}
+
+		for _, value := range crdDescription.StatusDescriptors {
+			require.Equal(t, expected[value.Path], value.XDescriptors[0])
+		}
+
+		// If there are no annotations in the CR,
+		// existing descriptors should not be impacted.
+
+		cr := &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": "app.dev/v1",
+				"kind":       "Carp",
+				"metadata":   map[string]interface{}{},
+				"spec":       map[string]interface{}{},
+				"status":     map[string]interface{}{},
+			},
+		}
+		err = buildCRDDescriptionFromCR(cr, crdDescription)
+
+		require.Nil(t, err)
+		require.Equal(t, "dbName", crdDescription.SpecDescriptors[0].Path)
+		require.Equal(t, "binding:env:attribute:spec.dbName", crdDescription.SpecDescriptors[0].XDescriptors[0])
+
+		for _, value := range crdDescription.StatusDescriptors {
+			require.Equal(t, expected[value.Path], value.XDescriptors[0])
+		}
 	})
 
-	t.Run("Build CSV from CRD", func(t *testing.T) {
-		crd.Object["metadata"] = map[string]interface{}{
-			"annotations": annotations,
+	t.Run("Build CSV descriptors from Annotations", func(t *testing.T) {
+		annotationsString := map[string]string{
+			"servicebindingoperator.redhat.io/status.dbConfigMap-db.host":       "binding:env:object:configmap",
+			"servicebindingoperator.redhat.io/spec.dbName":                      "binding:env:attribute",
+			"servicebindingoperator.redhat.io/status.dbCredentials-db.password": "binding:env:object:secret",
 		}
-		crdDescription, err := buildCRDDescriptionFromCRD(crd)
+
+		specDescriptors, statusDescriptors, err := buildDescriptorsFromAnnotations(annotationsString)
 		require.NoError(t, err)
 
-		require.Len(t, crdDescription.StatusDescriptors, 2)
-		require.Len(t, crdDescription.SpecDescriptors, 2)
+		require.Equal(t, "dbName", specDescriptors[0].Path)
+		require.Equal(t, "binding:env:attribute:spec.dbName", specDescriptors[0].XDescriptors[0])
 
 		expected := map[string]string{
 			"dbName":        "binding:env:attribute:spec.dbName",
-			"image":         "binding:env:attribute:spec.image",
 			"dbCredentials": "binding:env:object:secret:db.password",
 			"dbConfigMap":   "binding:env:object:configmap:db.host",
 		}
-		for _, value := range crdDescription.SpecDescriptors {
+
+		for _, value := range statusDescriptors {
 			require.Equal(t, expected[value.Path], value.XDescriptors[0])
 		}
 
+	})
+
+	t.Run("Build CSV from CR", func(t *testing.T) {
+		cr := &unstructured.Unstructured{
+			Object: map[string]interface{}{
+				"apiVersion": "apiextensions.k8s.io/v1beta1",
+				"kind":       "Random",
+				"metadata": map[string]interface{}{
+					"annotations": annotations,
+				},
+				"spec":   map[string]interface{}{},
+				"status": map[string]interface{}{},
+			},
+		}
+		crdDescription := &olmv1alpha1.CRDDescription{}
+		err := buildCRDDescriptionFromCR(cr, crdDescription)
+		require.NotNil(t, crdDescription)
+		require.NoError(t, err)
+
+		require.Equal(t, "dbName", crdDescription.SpecDescriptors[0].Path)
+		require.Equal(t, "binding:env:attribute:spec.dbName", crdDescription.SpecDescriptors[0].XDescriptors[0])
+
+		expected := map[string]string{
+			"dbName":        "binding:env:attribute:spec.dbName",
+			"dbCredentials": "binding:env:object:secret:db.password",
+			"dbConfigMap":   "binding:env:object:configmap:db.host",
+		}
+
 		for _, value := range crdDescription.StatusDescriptors {
 			require.Equal(t, expected[value.Path], value.XDescriptors[0])
 		}
diff --git a/pkg/controller/servicebindingrequest/planner_test.go b/pkg/controller/servicebindingrequest/planner_test.go
index 67d22daf..be13311c 100644
--- a/pkg/controller/servicebindingrequest/planner_test.go
+++ b/pkg/controller/servicebindingrequest/planner_test.go
@@ -4,7 +4,11 @@ import (
 	"context"
 	"testing"
 
+	routev1 "github.com/openshift/api/route/v1"
 	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
 	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
 
 	"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
@@ -130,3 +134,67 @@ func TestPlannerAnnotation(t *testing.T) {
 		require.NotNil(t, crd)
 	})
 }
+
+func TestPlannerWithCRAnnotations(t *testing.T) {
+
+	ns := "planner"
+	name := "service-binding-request"
+	f := mocks.NewFake(t, "test")
+
+	// create a Route
+	routeCR := mocks.RouteCRMock(ns, "test")
+	annotations := map[string]string{
+		"servicebindingoperator.redhat.io/spec.host": "binding:env:attribute",
+	}
+	routeCR.Annotations = annotations
+	route, err := runtime.DefaultUnstructuredConverter.ToUnstructured(routeCR)
+	require.NoError(t, err)
+	f.S.AddKnownTypes(routev1.SchemeGroupVersion, &routev1.Route{})
+	f.AddMockResource(&unstructured.Unstructured{Object: route})
+
+	sbr := &v1alpha1.ServiceBindingRequest{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: ns,
+			Name:      name,
+		},
+		Spec: v1alpha1.ServiceBindingRequestSpec{
+			ApplicationSelector: v1alpha1.ApplicationSelector{
+				GroupVersionResource: metav1.GroupVersionResource{Group: "g", Version: "v", Resource: "r"},
+				ResourceRef:          "app",
+			},
+			BackingServiceSelectors: &[]v1alpha1.BackingServiceSelector{
+				{
+					GroupVersionKind: metav1.GroupVersionKind{Group: "", Version: "v1", Kind: "Route"},
+					ResourceRef:      routeCR.GetName(),
+					Namespace:        &ns,
+				},
+			},
+		},
+	}
+	f.S.AddKnownTypes(v1alpha1.SchemeGroupVersion, &v1alpha1.ServiceBindingRequest{})
+
+	planner := NewPlanner(context.TODO(), f.FakeDynClient(), sbr)
+
+	t.Run("plan with annotated CR", func(t *testing.T) {
+		plan, err := planner.Plan()
+
+		require.NoError(t, err)
+		require.Len(t, plan.GetRelatedResources().GetCRs(), 1)
+
+		require.Equal(t, "host", plan.RelatedResources[0].CRDDescription.SpecDescriptors[0].Path)
+		require.Equal(t, "binding:env:attribute:spec.host", plan.RelatedResources[0].CRDDescription.SpecDescriptors[0].XDescriptors[0])
+	})
+
+	sbr.Spec.BackingServiceSelectors = &[]v1alpha1.BackingServiceSelector{
+		{
+			GroupVersionKind: metav1.GroupVersionKind{Group: "", Version: "v1", Kind: "Route"},
+			ResourceRef:      "non-existent",
+			Namespace:        &ns,
+		},
+	}
+	t.Run("plan with non existent CR", func(t *testing.T) {
+		plan, err := planner.Plan()
+		require.Error(t, err)
+		require.Nil(t, plan)
+	})
+}
diff --git a/pkg/controller/servicebindingrequest/reconciler_test.go b/pkg/controller/servicebindingrequest/reconciler_test.go
index c9a24bde..0239d605 100644
--- a/pkg/controller/servicebindingrequest/reconciler_test.go
+++ b/pkg/controller/servicebindingrequest/reconciler_test.go
@@ -93,7 +93,7 @@ func TestApplicationSelectorByName(t *testing.T) {
 		require.NoError(t, err)
 
 		require.Equal(t, BindingSuccess, sbrOutput.Status.BindingStatus)
-		require.Equal(t, 1, len(sbrOutput.Status.ApplicationObjects))
+		require.Equal(t, 1, len(sbrOutput.Status.Applications))
 		expectedStatus := v1alpha1.BoundApplication{
 			GroupVersionKind: v1.GroupVersionKind{
 				Group:   deploymentsGVR.Group,
@@ -104,7 +104,7 @@ func TestApplicationSelectorByName(t *testing.T) {
 				Name: namespacedName.Name,
 			},
 		}
-		require.True(t, reflect.DeepEqual(expectedStatus, sbrOutput.Status.ApplicationObjects[0]))
+		require.True(t, reflect.DeepEqual(expectedStatus, sbrOutput.Status.Applications[0]))
 	})
 }
 
@@ -151,7 +151,7 @@ func TestReconcilerReconcileUsingSecret(t *testing.T) {
 		require.Equal(t, "BindingSuccess", sbrOutput.Status.BindingStatus)
 		require.Equal(t, reconcilerName, sbrOutput.Status.Secret)
 
-		require.Equal(t, 1, len(sbrOutput.Status.ApplicationObjects))
+		require.Equal(t, 1, len(sbrOutput.Status.Applications))
 		expectedStatus := v1alpha1.BoundApplication{
 			GroupVersionKind: v1.GroupVersionKind{
 				Group:   deploymentsGVR.Group,
@@ -162,7 +162,7 @@ func TestReconcilerReconcileUsingSecret(t *testing.T) {
 				Name: namespacedName.Name,
 			},
 		}
-		require.True(t, reflect.DeepEqual(expectedStatus, sbrOutput.Status.ApplicationObjects[0]))
+		require.True(t, reflect.DeepEqual(expectedStatus, sbrOutput.Status.Applications[0]))
 	})
 }
 
@@ -205,3 +205,129 @@ func TestReconcilerReconcileUsingVolumes(t *testing.T) {
 		require.Equal(t, reconcilerName, volumes[0].VolumeSource.Secret.SecretName)
 	})
 }
+
+func TestReconcilerGenericBinding(t *testing.T) {
+	ctx := context.TODO()
+	backingServiceResourceRef := "backingService1"
+	matchLabels := map[string]string{
+		"connects-to": "database",
+		"environment": "reconciler",
+	}
+	f := mocks.NewFake(t, reconcilerNs)
+	f.AddMockedUnstructuredServiceBindingRequest(reconcilerName, backingServiceResourceRef, "", deploymentsGVR, matchLabels)
+	f.AddMockedUnstructuredCSV("cluster-service-version-list")
+	f.AddMockedUnstructuredDatabaseCRD()
+	f.AddMockedUnstructuredDatabaseCR(backingServiceResourceRef)
+	f.AddMockedSecret("db-credentials")
+
+	fakeClient := f.FakeClient()
+	reconciler := &Reconciler{client: fakeClient, dynClient: f.FakeDynClient(), scheme: f.S}
+
+	// Reconcile without deployment
+	res, err := reconciler.Reconcile(reconcileRequest())
+	require.NoError(t, err)
+	require.True(t, res.Requeue)
+
+	namespacedName := types.NamespacedName{Namespace: reconcilerNs, Name: reconcilerName}
+	sbrOutput, err := reconciler.getServiceBindingRequest(namespacedName)
+	require.NoError(t, err)
+
+	require.Equal(t, "BindingFail", sbrOutput.Status.BindingStatus)
+	require.Equal(t, corev1.ConditionFalse, sbrOutput.Status.Conditions[0].Status)
+	require.Equal(t, 0, len(sbrOutput.Status.Applications))
+
+	// Reconcile with deployment
+	f.AddMockedUnstructuredDeployment(reconcilerName, matchLabels)
+
+	fakeClient = f.FakeClient()
+	reconciler = &Reconciler{client: fakeClient, dynClient: f.FakeDynClient(), scheme: f.S}
+	res, err = reconciler.Reconcile(reconcileRequest())
+	require.NoError(t, err)
+	require.False(t, res.Requeue)
+
+	d := appsv1.Deployment{}
+	require.NoError(t, fakeClient.Get(ctx, namespacedName, &d))
+
+	sbrOutput2, err := reconciler.getServiceBindingRequest(namespacedName)
+	require.NoError(t, err)
+
+	require.Equal(t, "BindingSuccess", sbrOutput2.Status.BindingStatus)
+	require.Equal(t, reconcilerName, sbrOutput2.Status.Secret)
+	require.Equal(t, corev1.ConditionTrue, sbrOutput2.Status.Conditions[0].Status)
+	require.Equal(t, 1, len(sbrOutput2.Status.Applications))
+
+	// Update Credentials
+	s := corev1.Secret{}
+	secretNamespaced := types.NamespacedName{Namespace: reconcilerNs, Name: "db-credentials"}
+	require.NoError(t, fakeClient.Get(ctx, secretNamespaced, &s))
+	s.Data["password"] = []byte("abc123")
+	require.NoError(t, fakeClient.Update(ctx, &s))
+
+	reconciler = &Reconciler{client: fakeClient, dynClient: f.FakeDynClient(), scheme: f.S}
+	res, err = reconciler.Reconcile(reconcileRequest())
+	require.NoError(t, err)
+	require.False(t, res.Requeue)
+
+	sbrOutput3, err := reconciler.getServiceBindingRequest(namespacedName)
+	require.NoError(t, err)
+
+	d = appsv1.Deployment{}
+	require.NoError(t, fakeClient.Get(ctx, namespacedName, &d))
+
+	require.Equal(t, "BindingSuccess", sbrOutput3.Status.BindingStatus)
+	require.Equal(t, corev1.ConditionTrue, sbrOutput3.Status.Conditions[0].Status)
+	require.Equal(t, reconcilerName, sbrOutput3.Status.Secret)
+	require.Equal(t, s.Data["password"], []byte("abc123"))
+	require.Equal(t, 1, len(sbrOutput3.Status.Applications))
+}
+
+//TestReconcilerReconcileWithConflictingAppSelc tests when sbr has conflicting ApplicationSel such as MatchLabels=App1 and ResourceRef=App2 it should prioritise the ResourceRef
+func TestReconcilerReconcileWithConflictingAppSelc(t *testing.T) {
+	backingServiceResourceRef := "backingServiceRef"
+	applicationResourceRef1 := "applicationResourceRef1"
+	matchLabels1 := map[string]string{
+		"connects-to": "database",
+		"environment": "testing",
+	}
+	applicationResourceRef2 := "applicationResourceRef2"
+
+	f := mocks.NewFake(t, reconcilerNs)
+
+	f.AddMockedUnstructuredDeployment(applicationResourceRef1, matchLabels1)
+	f.AddMockedUnstructuredDeployment(applicationResourceRef2, nil)
+	f.AddMockedUnstructuredServiceBindingRequest(reconcilerName, backingServiceResourceRef, applicationResourceRef2, deploymentsGVR, matchLabels1)
+	f.AddMockedUnstructuredDatabaseCRD()
+	f.AddMockedUnstructuredDatabaseCR(backingServiceResourceRef)
+	f.AddMockedSecret("db-credentials")
+
+	fakeClient := f.FakeClient()
+	fakeDynClient := f.FakeDynClient()
+	reconciler := &Reconciler{client: fakeClient, dynClient: fakeDynClient, scheme: f.S}
+
+	t.Run("test-reconciler-reconcile-with-conflicting-application-selector", func(t *testing.T) {
+
+		res, err := reconciler.Reconcile(reconcileRequest())
+		require.NoError(t, err)
+		require.False(t, res.Requeue)
+
+		namespacedName := types.NamespacedName{Namespace: reconcilerNs, Name: reconcilerName}
+		sbrOutput, err := reconciler.getServiceBindingRequest(namespacedName)
+		require.NoError(t, err)
+
+		expectedStatus := v1alpha1.BoundApplication{
+			GroupVersionKind: v1.GroupVersionKind{
+				Group:   deploymentsGVR.Group,
+				Version: deploymentsGVR.Version,
+				Kind:    "Deployment",
+			},
+			LocalObjectReference: corev1.LocalObjectReference{
+				Name: applicationResourceRef2,
+			},
+		}
+
+		require.Equal(t, BindingSuccess, sbrOutput.Status.BindingStatus)
+		require.Equal(t, reconcilerName, sbrOutput.Status.Secret)
+		require.Equal(t, corev1.ConditionTrue, sbrOutput.Status.Conditions[0].Status)
+		require.True(t, reflect.DeepEqual(expectedStatus, sbrOutput.Status.Applications[0]))
+	})
+}
diff --git a/pkg/controller/servicebindingrequest/secret_test.go b/pkg/controller/servicebindingrequest/secret_test.go
index 6ae996e2..6562520e 100644
--- a/pkg/controller/servicebindingrequest/secret_test.go
+++ b/pkg/controller/servicebindingrequest/secret_test.go
@@ -32,12 +32,6 @@ func TestSecretNew(t *testing.T) {
 
 	s := NewSecret(f.FakeDynClient(), plan)
 
-	t.Run("customEnvParser", func(t *testing.T) {
-		customData, err := s.customEnvParser(data)
-		assert.NoError(t, err)
-		assert.NotNil(t, customData)
-	})
-
 	t.Run("createOrUpdate", func(t *testing.T) {
 		u, err := s.createOrUpdate(data)
 		assert.NoError(t, err)
diff --git a/test/operator-hub/subscription.yaml b/test/operator-hub/subscription.yaml
index efc11063..c01a9aa7 100644
--- a/test/operator-hub/subscription.yaml
+++ b/test/operator-hub/subscription.yaml
@@ -6,7 +6,8 @@ metadata:
   namespace: openshift-operators
 spec:
   channel: alpha
-  installPlanApproval: Automatic
+  installPlanApproval: Manual
   name: service-binding-operator
   source: example-operators
   sourceNamespace: openshift-marketplace
+  startingCSV: REPLACE_CSV_VERSION