Repository: redhat-developer/service-binding-operator
Pull request #311 (2020-02-03 10:22:27)

validation of olm bundles part1
### Motivation

Refers - https://issues.redhat.com/browse/APPSVC-392

### Changes

[WIP]

### Testing

Pending

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 **avni-sharma**
You can assign the PR to them by writing `/assign @avni-sharma` 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":["avni-sharma"]} -->
openshift-ci-robot: @Avni-Sharma: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | da050dda0b49d37072498bfabebf718d9670e7a8 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/311/pull-ci-redhat-developer-service-binding-operator-master-unit/995) | `/test unit`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | da050dda0b49d37072498bfabebf718d9670e7a8 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/311/pull-ci-redhat-developer-service-binding-operator-master-unit/995) | `/test unit`
ci/prow/lint | da050dda0b49d37072498bfabebf718d9670e7a8 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/311/pull-ci-redhat-developer-service-binding-operator-master-lint/1027) | `/test lint`

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

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | da050dda0b49d37072498bfabebf718d9670e7a8 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/311/pull-ci-redhat-developer-service-binding-operator-master-unit/995) | `/test unit`
ci/prow/lint | da050dda0b49d37072498bfabebf718d9670e7a8 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/311/pull-ci-redhat-developer-service-binding-operator-master-lint/1027) | `/test lint`
ci/prow/e2e | da050dda0b49d37072498bfabebf718d9670e7a8 | [link](https://prow.svc.ci.openshift.org/view/gcs/origin-ci-test/pr-logs/pull/redhat-developer_service-binding-operator/311/pull-ci-redhat-developer-service-binding-operator-master-e2e/1081) | `/test e2e`

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=311). [Your PR dashboard](https://prow.svc.ci.openshift.org/pr?query=is:pr+state:open+author:Avni-Sharma). 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: @Avni-Sharma: 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>
codecov[bot]: # [Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/311?src=pr&el=h1) Report
> Merging [#311](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/311?src=pr&el=desc) into [master](https://codecov.io/gh/redhat-developer/service-binding-operator/commit/da050dda0b49d37072498bfabebf718d9670e7a8?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

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

```diff
@@           Coverage Diff           @@
##           master     #311   +/-   ##
=======================================
  Coverage   59.81%   59.81%           
=======================================
  Files          17       17           
  Lines        1294     1294           
=======================================
  Hits          774      774           
  Misses        388      388           
  Partials      132      132
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/311?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/311?src=pr&el=footer). Last update [da050dd...1893735](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/311?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/Makefile b/Makefile
index d2107b86..0e7ac222 100644
--- a/Makefile
+++ b/Makefile
@@ -455,5 +455,21 @@ push-bundle-to-quay:
 .PHONY: dev-release
 ## validating the operator by installing new quay releases
 dev-release:
-	echo  "HELLO BUNDLE VERSION -${BUNDLE_VERSION}"
-	sleep 10m
+	$(Q)oc apply -f ./test/operator-hub/operator_source.yaml
+	# Subscribing to the operator
+	$(Q)oc apply -f ./test/operator-hub/subscription.yaml
+	while [ "$(oc get pods --field-selector=status.phase=Running -n openshift-marketplace" == "No resources found." ]
+	do
+		sleep 1s
+	done
+	$(eval VERSION_NUMBER := $(shell oc get csvs  -n=default -o jsonpath='{.items[*].spec.version}'))
+	@if [ ${VERSION_NUMBER} == ${BUNDLE_VERSION} ]; \
+	then \
+    	echo -e "OLM Bundle Version validation succeeded \n " \
+		exit 0; \
+    else \
+		echo -e "OLM Bundle Version validation failed \n" \
+		exit 1; \
+    fi
+
+
diff --git a/go.mod b/go.mod
index e267ba05..a21d2eee 100644
--- a/go.mod
+++ b/go.mod
@@ -42,6 +42,7 @@ require (
 
 // Pinned to kubernetes-1.15.4
 replace (
+	github.com/openshift/api => github.com/openshift/api v0.0.0-20190424152011-77b8897ec79a
 	k8s.io/api => k8s.io/api v0.0.0-20190918195907-bd6ac527cfd2
 	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20190918201827-3de75813f604
 	k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d
diff --git a/go.sum b/go.sum
index ef837674..0a7cb360 100644
--- a/go.sum
+++ b/go.sum
@@ -475,6 +475,8 @@ github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM
 github.com/opencontainers/runc v0.0.0-20181113202123-f000fe11ece1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/selinux v0.0.0-20170621221121-4a2974bf1ee9/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs=
+github.com/openshift/api v0.0.0-20190424152011-77b8897ec79a h1:zJauc4Mzrbn2C+G6cMwvvMCGWQZoyHaDlhoP6AjQDs8=
+github.com/openshift/api v0.0.0-20190424152011-77b8897ec79a/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY=
 github.com/openshift/api v3.9.1-0.20190424152011-77b8897ec79a+incompatible h1:q2JBuObKafI7B4Eli6eLd+2T5JsU9ioWZ82zQwyjJPg=
 github.com/openshift/api v3.9.1-0.20190424152011-77b8897ec79a+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY=
 github.com/openshift/client-go v0.0.0-20190401163519-84c2b942258a/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk=
diff --git a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
index 5a36659a..b75c4fab 100644
--- a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
+++ b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
@@ -29,6 +29,9 @@ type ServiceBindingRequestSpec struct {
 	// BackingServiceSelector is used to identify the backing service operator.
 	BackingServiceSelector BackingServiceSelector `json:"backingServiceSelector"`
 
+	// BackingServiceSelectors is used to identify multiple backing services.
+	BackingServiceSelectors []BackingServiceSelector `json:"backingServiceSelectors"`
+
 	// ApplicationSelector is used to identify the application connecting to the
 	// backing service operator.
 	ApplicationSelector ApplicationSelector `json:"applicationSelector"`
diff --git a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
index 5b64c5b7..41676c36 100644
--- a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go
@@ -121,6 +121,11 @@ func (in *ServiceBindingRequestSpec) DeepCopyInto(out *ServiceBindingRequestSpec
 		}
 	}
 	out.BackingServiceSelector = in.BackingServiceSelector
+	if in.BackingServiceSelectors != nil {
+		in, out := &in.BackingServiceSelectors, &out.BackingServiceSelectors
+		*out = make([]BackingServiceSelector, len(*in))
+		copy(*out, *in)
+	}
 	in.ApplicationSelector.DeepCopyInto(&out.ApplicationSelector)
 	return
 }
diff --git a/pkg/controller/servicebindingrequest/binding.go b/pkg/controller/servicebindingrequest/binding.go
new file mode 100644
index 00000000..1cfea17c
--- /dev/null
+++ b/pkg/controller/servicebindingrequest/binding.go
@@ -0,0 +1,322 @@
+package servicebindingrequest
+
+import (
+	"context"
+	"errors"
+	"fmt"
+
+	"gotest.tools/assert/cmp"
+	"k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/client-go/dynamic"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+	"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
+	"github.com/redhat-developer/service-binding-operator/pkg/converter"
+	"github.com/redhat-developer/service-binding-operator/pkg/log"
+)
+
+const (
+	// bindingInProgress binding is in progress
+	bindingInProgress = "InProgress"
+	// bindingFail binding has failed
+	bindingFail = "Fail"
+	// time in seconds to wait before requeuing requests
+	requeueAfter int64 = 45
+	// Finalizer annotation used in finalizer steps
+	Finalizer = "finalizer.servicebindingrequest.openshift.io"
+)
+
+// GroupVersion represents the service binding request resource's group version.
+var GroupVersion = v1alpha1.SchemeGroupVersion.WithResource(ServiceBindingRequestResource)
+
+// ServiceBinderOptions is BuildServiceBinder arguments.
+type ServiceBinderOptions struct {
+	Logger                 *log.Log
+	DynClient              dynamic.Interface
+	DetectBindingResources bool
+	EnvVarPrefix           string
+	SBR                    *v1alpha1.ServiceBindingRequest
+	Client                 client.Client
+}
+
+// Valid returns whether the options are valid.
+func (o *ServiceBinderOptions) Valid() bool {
+	return o.SBR != nil && o.DynClient != nil && o.Client != nil
+}
+
+// ServiceBinder manages binding for a Service Binding Request and associated objects.
+type ServiceBinder struct {
+	// Binder is responsible for interacting with the cluster and apply binding related changes.
+	Binder *Binder
+	// Data is the collection of all data read by the manager.
+	Data map[string][]byte
+	// DynClient is the Kubernetes dynamic client used to interact with the cluster.
+	DynClient dynamic.Interface
+	// Logger provides logging facilities for internal components.
+	Logger *log.Log
+	// Objects is a list of additional unstructured objects related to the Service Binding Request.
+	Objects []*unstructured.Unstructured
+	// SBR is the ServiceBindingRequest associated with binding..
+	SBR *v1alpha1.ServiceBindingRequest
+	// Secret is the Secret associated with the Service Binding Request.
+	Secret *Secret
+}
+
+// updateServiceBindingRequest execute update API call on a SBR request. It can return errors from
+// this action.
+func (b *ServiceBinder) updateServiceBindingRequest(
+	sbr *v1alpha1.ServiceBindingRequest,
+) (*v1alpha1.ServiceBindingRequest, error) {
+	u, err := converter.ToUnstructured(sbr)
+	if err != nil {
+		return nil, err
+	}
+
+	u, err = b.DynClient.
+		Resource(GroupVersion).
+		Namespace(sbr.GetNamespace()).
+		Update(u, v1.UpdateOptions{})
+
+	if err != nil {
+		return nil, err
+	}
+
+	err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sbr)
+	if err != nil {
+		return nil, err
+	}
+	return sbr, nil
+}
+
+// Unbind removes the relationship between a Service Binding Request and its related objects.
+func (b *ServiceBinder) Unbind() (reconcile.Result, error) {
+	logger := b.Logger.WithName("Unbind")
+
+	logger.Info("Cleaning related objects from operator's annotations...")
+	if err := RemoveSBRAnnotations(b.DynClient, b.Objects); err != nil {
+		logger.Error(err, "On removing annotations from related objects.")
+		return RequeueError(err)
+	}
+
+	if err := b.Binder.Unbind(); err != nil {
+		logger.Error(err, "On unbinding related objects")
+		return RequeueError(err)
+	}
+
+	logger.Info("Deleting intermediary secret")
+	if err := b.Secret.Delete(); err != nil {
+		logger.Error(err, "On deleting intermediary secret.")
+		return RequeueError(err)
+	}
+
+	logger.Debug("Removing resource finalizers...")
+	b.SBR.SetFinalizers(removeStringSlice(b.SBR.GetFinalizers(), Finalizer))
+	if _, err := b.updateServiceBindingRequest(b.SBR); err != nil {
+		return NoRequeue(err)
+	}
+
+	return Done()
+}
+
+// updateStatusServiceBindingRequest updates the Service Binding Request's status field.
+func (b *ServiceBinder) updateStatusServiceBindingRequest(
+	sbr *v1alpha1.ServiceBindingRequest,
+	sbrStatus *v1alpha1.ServiceBindingRequestStatus,
+) (
+	*v1alpha1.ServiceBindingRequest,
+	error,
+) {
+	// do not update if both statuses are equal
+	if result := cmp.DeepEqual(sbr.Status, sbrStatus)(); result.Success() {
+		return sbr, nil
+	}
+
+	// coping status over informed object
+	sbr.Status = *sbrStatus
+
+	// converting object into unstructured
+	u, err := converter.ToUnstructured(sbr)
+	if err != nil {
+		return nil, err
+	}
+
+	gr := v1alpha1.SchemeGroupVersion.WithResource(ServiceBindingRequestResource)
+	resourceClient := b.DynClient.Resource(gr).Namespace(sbr.GetNamespace())
+	u, err = resourceClient.UpdateStatus(u, v1.UpdateOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sbr)
+	if err != nil {
+		return nil, err
+	}
+	return sbr, nil
+}
+
+// onError comprise the update of ServiceBindingRequest status to set error flag, and inspect
+// informed error to apply a different behavior for not-founds.
+func (b *ServiceBinder) onError(
+	err error,
+	sbr *v1alpha1.ServiceBindingRequest,
+	sbrStatus *v1alpha1.ServiceBindingRequestStatus,
+	objs []*unstructured.Unstructured,
+) (reconcile.Result, error) {
+	sbrStatus.BindingStatus = bindingFail
+
+	if objs != nil {
+		sbrStatus.BindingStatus = BindingSuccess
+		b.setApplicationObjects(sbrStatus, objs)
+	}
+
+	_, errStatus := b.updateStatusServiceBindingRequest(sbr, sbrStatus)
+	if errStatus != nil {
+		return RequeueError(errStatus)
+	}
+
+	return RequeueOnNotFound(err, requeueAfter)
+}
+
+// Bind configures binding between the Service Binding Request and its related objects.
+func (b *ServiceBinder) Bind() (reconcile.Result, error) {
+	sbrStatus := b.SBR.Status.DeepCopy()
+
+	b.Logger.Info("Saving data on intermediary secret...")
+	secretObj, err := b.Secret.Commit(b.Data, nil)
+	if err != nil {
+		b.Logger.Error(err, "On saving secret data..")
+		return b.onError(err, b.SBR, sbrStatus, nil)
+	}
+
+	// update status information
+	sbrStatus.BindingStatus = bindingInProgress
+	sbrStatus.Secret = secretObj.GetName()
+
+	updatedObjects, err := b.Binder.Bind()
+	if err != nil {
+		b.Logger.Error(err, "On binding application.")
+		return b.onError(err, b.SBR, sbrStatus, updatedObjects)
+	}
+
+	// saving on status the list of objects that have been touched
+	sbrStatus.BindingStatus = BindingSuccess
+	b.setApplicationObjects(sbrStatus, updatedObjects)
+
+	// annotating objects related to binding
+	namespacedName := types.NamespacedName{Namespace: b.SBR.GetNamespace(), Name: b.SBR.GetName()}
+	if err = SetSBRAnnotations(b.DynClient, namespacedName, b.Objects); err != nil {
+		b.Logger.Error(err, "On setting annotations in related objects.")
+		return b.onError(err, b.SBR, sbrStatus, updatedObjects)
+	}
+
+	// updating status of request instance
+	sbr, err := b.updateStatusServiceBindingRequest(b.SBR, sbrStatus)
+	if err != nil {
+		return RequeueOnConflict(err)
+	}
+
+	// appending finalizer, should be later removed upon resource deletion
+	sbr.SetFinalizers(append(sbr.GetFinalizers(), Finalizer))
+	if _, err = b.updateServiceBindingRequest(sbr); err != nil {
+		return NoRequeue(err)
+	}
+
+	b.Logger.Info("All done!")
+	return Done()
+}
+
+// setApplicationObjects replaces the Status's equivalent field.
+func (b *ServiceBinder) setApplicationObjects(
+	sbrStatus *v1alpha1.ServiceBindingRequestStatus,
+	objs []*unstructured.Unstructured,
+) {
+	names := []string{}
+	for _, obj := range objs {
+		names = append(names, fmt.Sprintf("%s/%s", obj.GetNamespace(), obj.GetName()))
+	}
+	sbrStatus.ApplicationObjects = names
+}
+
+// buildPlan creates a new plan.
+func buildPlan(
+	ctx context.Context,
+	dynClient dynamic.Interface,
+	sbr *v1alpha1.ServiceBindingRequest,
+) (*Plan, error) {
+	planner := NewPlanner(ctx, dynClient, sbr)
+	return planner.Plan()
+}
+
+// InvalidOptionsErr is returned when ServiceBinderOptions are not valid.
+var InvalidOptionsErr = errors.New("invalid options")
+
+// BuildServiceBinder creates a new binding manager according to options.
+func BuildServiceBinder(options *ServiceBinderOptions) (*ServiceBinder, error) {
+	if !options.Valid() {
+		return nil, InvalidOptionsErr
+	}
+
+	// objs groups all extra objects related to the informed SBR
+	objs := make([]*unstructured.Unstructured, 0)
+
+	// plan is a source of information regarding the binding process
+	ctx := context.Background()
+	plan, err := buildPlan(ctx, options.DynClient, options.SBR)
+	if err != nil {
+		return nil, err
+	}
+
+	rs := plan.GetCRs()
+	// append all SBR related CRs
+	objs = append(objs, rs...)
+
+	// retriever is responsible for gathering data related to the given plan.
+	retriever := NewRetriever(options.DynClient, plan, options.EnvVarPrefix)
+
+	// read bindable data from the specified resources
+	if options.DetectBindingResources {
+		err := retriever.ReadBindableResourcesData(&plan.SBR, rs)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// read bindable data from the CRDDescription found by the planner
+	for _, r := range plan.GetRelatedResources() {
+		err = retriever.ReadCRDDescriptionData(r.CR, r.CRDDescription)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// gather retriever's read data
+	// TODO: do not return error
+	retrievedData, err := retriever.Get()
+	if err != nil {
+		return nil, err
+	}
+
+	// gather related secret, again only appending it if there's a value.
+	secret := NewSecret(options.DynClient, plan)
+	secretObj, found, err := secret.Get()
+	if err != nil {
+		return nil, err
+	}
+	if found {
+		objs = append(objs, secretObj)
+	}
+
+	return &ServiceBinder{
+		Logger:    options.Logger,
+		Binder:    NewBinder(ctx, options.Client, options.DynClient, options.SBR, retriever.volumeKeys),
+		DynClient: options.DynClient,
+		SBR:       options.SBR,
+		Objects:   objs,
+		Data:      retrievedData,
+		Secret:    secret,
+	}, nil
+}
diff --git a/pkg/controller/servicebindingrequest/plan_decoupled.go b/pkg/controller/servicebindingrequest/plan_decoupled.go
new file mode 100644
index 00000000..ce0bfb25
--- /dev/null
+++ b/pkg/controller/servicebindingrequest/plan_decoupled.go
@@ -0,0 +1,15 @@
+package servicebindingrequest
+
+import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
+// GetCRs returns a slice of unstructured CRs contained in the internal related resources collection.
+func (p *Plan) GetCRs() []*unstructured.Unstructured {
+	// return p.RelatedResources.GetCRs()
+	panic("implement me")
+}
+
+// GetRelatedResources returns the collection of related resources enumerated in the plan.
+func (p *Plan) GetRelatedResources() RelatedResources {
+	//return p.RelatedResources
+	panic("implement me")
+}
diff --git a/pkg/controller/servicebindingrequest/reconciler.go b/pkg/controller/servicebindingrequest/reconciler.go
index 22648e97..88ebe6c4 100644
--- a/pkg/controller/servicebindingrequest/reconciler.go
+++ b/pkg/controller/servicebindingrequest/reconciler.go
@@ -27,14 +27,8 @@ type Reconciler struct {
 }
 
 const (
-	// bindingInProgress binding is in progress
-	bindingInProgress = "InProgress"
 	// BindingSuccess binding has succeeded
 	BindingSuccess = "Success"
-	// bindingFail binding has failed
-	bindingFail = "Fail"
-	// time in seconds to wait before requeuing requests
-	requeueAfter int64 = 45
 	// sbrFinalizer annotation used in finalizer steps
 	sbrFinalizer = "finalizer.servicebindingrequest.openshift.io"
 )
@@ -197,7 +191,7 @@ func (r *Reconciler) unbind(
 	}
 
 	logger.Debug("Reading intermediary secret before deletion.")
-	secretObj, err := secret.Get()
+	secretObj, _, err := secret.Get()
 	if err != nil {
 		logger.Error(err, "On reading intermediary secret.")
 		return RequeueError(err)
diff --git a/pkg/controller/servicebindingrequest/related_resources.go b/pkg/controller/servicebindingrequest/related_resources.go
new file mode 100644
index 00000000..3f0dfb72
--- /dev/null
+++ b/pkg/controller/servicebindingrequest/related_resources.go
@@ -0,0 +1,24 @@
+package servicebindingrequest
+
+import (
+	"github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+)
+
+// RelatedResource represents a SBR related resource, composed by its CR and CRDDescription.
+type RelatedResource struct {
+	CRDDescription *v1alpha1.CRDDescription
+	CR             *unstructured.Unstructured
+}
+
+// RelatedResources contains a collection of SBR related resources.
+type RelatedResources []*RelatedResource
+
+// GetCRs returns a slice of unstructured CRs contained in the collection.
+func (rr RelatedResources) GetCRs() []*unstructured.Unstructured {
+	var crs []*unstructured.Unstructured
+	for _, r := range rr {
+		crs = append(crs, r.CR)
+	}
+	return crs
+}
diff --git a/pkg/controller/servicebindingrequest/retrieve_decoupled.go b/pkg/controller/servicebindingrequest/retrieve_decoupled.go
new file mode 100644
index 00000000..5d68949b
--- /dev/null
+++ b/pkg/controller/servicebindingrequest/retrieve_decoupled.go
@@ -0,0 +1,71 @@
+package servicebindingrequest
+
+import (
+	"fmt"
+
+	olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
+	"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
+)
+
+// Get returns the data read from related resources (see ReadBindableResourcesData and
+// ReadCRDDescriptionData).
+func (r *Retriever) Get() (map[string][]byte, error) {
+	return r.data, nil
+}
+
+// ReadBindableResourcesData reads all related resources of a given sbr
+func (r *Retriever) ReadBindableResourcesData(
+	sbr *v1alpha1.ServiceBindingRequest,
+	crs []*unstructured.Unstructured,
+) error {
+	r.logger.Info("Detecting extra resources for binding...")
+	for _, cr := range crs {
+		b := NewDetectBindableResources(sbr, cr, []schema.GroupVersionResource{
+			{Group: "", Version: "v1", Resource: "configmaps"},
+			{Group: "", Version: "v1", Resource: "services"},
+			{Group: "route.openshift.io", Version: "v1", Resource: "routes"},
+		}, r.client)
+
+		vals, err := b.GetBindableVariables()
+		if err != nil {
+			return err
+		}
+		for k, v := range vals {
+			r.storeInto(cr, k, []byte(fmt.Sprintf("%v", v)))
+		}
+	}
+
+	return nil
+}
+
+func (r *Retriever) storeInto(cr *unstructured.Unstructured, key string, value []byte) {
+	// r.store(cr, k, v)
+	panic("implement me")
+}
+
+func (r *Retriever) copyFrom(u *unstructured.Unstructured, path string, fieldPath string, descriptors []string) error {
+	// r.read(u, "spec", specDescriptor.Path, specDescriptor.XDescriptors); err != nil {
+	panic("implement me")
+}
+
+// ReadCRDDescriptionData reads data related to given crdDescription
+func (r *Retriever) ReadCRDDescriptionData(u *unstructured.Unstructured, crdDescription *olmv1alpha1.CRDDescription) error {
+	r.logger.Info("Looking for spec-descriptors in 'spec'...")
+	for _, specDescriptor := range crdDescription.SpecDescriptors {
+		if err := r.copyFrom(u, "spec", specDescriptor.Path, specDescriptor.XDescriptors); err != nil {
+			return err
+		}
+	}
+
+	r.logger.Info("Looking for status-descriptors in 'status'...")
+	for _, statusDescriptor := range crdDescription.StatusDescriptors {
+		if err := r.copyFrom(u, "status", statusDescriptor.Path, statusDescriptor.XDescriptors); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/pkg/controller/servicebindingrequest/secret.go b/pkg/controller/servicebindingrequest/secret.go
index 551696bc..3dffbf76 100644
--- a/pkg/controller/servicebindingrequest/secret.go
+++ b/pkg/controller/servicebindingrequest/secret.go
@@ -90,9 +90,13 @@ func (s *Secret) Commit(data map[string][]byte, cache map[string]interface{}) (*
 
 // Get an unstructured object from the secret handled by this component. It can return errors in case
 // the API server does.
-func (s *Secret) Get() (*unstructured.Unstructured, error) {
+func (s *Secret) Get() (*unstructured.Unstructured, bool, error) {
 	resourceClient := s.buildResourceClient()
-	return resourceClient.Get(s.plan.Name, metav1.GetOptions{})
+	u, err := resourceClient.Get(s.plan.Name, metav1.GetOptions{})
+	if err != nil && !errors.IsNotFound(err) {
+		return nil, false, err
+	}
+	return u, u != nil, nil
 }
 
 // Delete the secret represented by this component. It can return error when the API server does.
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f35d8531..6596bf15 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -135,7 +135,7 @@ github.com/mitchellh/go-homedir
 github.com/modern-go/concurrent
 # github.com/modern-go/reflect2 v1.0.1
 github.com/modern-go/reflect2
-# github.com/openshift/api v3.9.1-0.20190424152011-77b8897ec79a+incompatible
+# github.com/openshift/api v3.9.1-0.20190424152011-77b8897ec79a+incompatible => github.com/openshift/api v0.0.0-20190424152011-77b8897ec79a
 github.com/openshift/api/apps/v1
 github.com/openshift/api/route/v1
 # github.com/operator-backing-service-samples/postgresql-operator v0.0.0-20191023140509-5c3697ed3069


Test patch:
diff --git a/pkg/controller/servicebindingrequest/binding_test.go b/pkg/controller/servicebindingrequest/binding_test.go
new file mode 100644
index 00000000..7a7a0c26
--- /dev/null
+++ b/pkg/controller/servicebindingrequest/binding_test.go
@@ -0,0 +1,311 @@
+package servicebindingrequest
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/dynamic/fake"
+	k8stesting "k8s.io/client-go/testing"
+
+	"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
+	"github.com/redhat-developer/service-binding-operator/pkg/log"
+	"github.com/redhat-developer/service-binding-operator/test/mocks"
+)
+
+// TestServiceBinder_Bind exercises scenarios regarding binding SBR and its related resources.
+func TestServiceBinder_Bind(t *testing.T) {
+	t.Skip("functionality is not ready yet")
+
+	// wantedAction represents an action issued by the component that is required to exist after it
+	// finished the operation
+	type wantedAction struct {
+		verb     string
+		resource string
+		name     string
+	}
+
+	// args are the test arguments
+	type args struct {
+		// options inform the test how to build the ServiceBinder.
+		options *ServiceBinderOptions
+		// wantBuildErr informs the test an error is wanted at build phase.
+		wantBuildErr error
+		// wantErr informs the test an error is wanted at ServiceBinder's bind phase.
+		wantErr error
+		// wantedActions informs the test all the actions that should have been issued by
+		// ServiceBinder.
+		wantedActions []wantedAction
+	}
+
+	// assertBind exercises the bind functionality
+	assertBind := func(args args) func(*testing.T) {
+		return func(t *testing.T) {
+			sb, err := BuildServiceBinder(args.options)
+			if args.wantBuildErr != nil {
+				require.Error(t, err)
+				return
+			} else {
+				require.NoError(t, err)
+			}
+
+			res, err := sb.Bind()
+
+			if args.wantErr != nil {
+				require.Error(t, err)
+				require.Equal(t, args.wantErr, err)
+				require.Nil(t, res)
+			} else {
+				require.NoError(t, err)
+				require.NotNil(t, res)
+			}
+
+			// extract actions from the dynamic client, regardless of the bind status; it is expected
+			// that failures also issue updates for ServiceBindingRequest objects
+			dynClient, ok := sb.DynClient.(*fake.FakeDynamicClient)
+			require.True(t, ok)
+			actions := dynClient.Actions()
+			require.NotNil(t, actions)
+
+			// regardless of the result, verify the actions expected by the reconciliation
+			// process have been issued if user has specified wanted actions
+			if len(args.wantedActions) > 0 {
+				// proceed to find whether actions match wanted actions
+				for _, w := range args.wantedActions {
+					var match bool
+					// search for each wanted action in the slice of actions issued by ServiceBinder
+					for _, a := range actions {
+						// match will be updated in the switch branches below
+						if match {
+							break
+						}
+
+						if a.Matches(w.verb, w.resource) {
+							// there are several action types; here it is required to 'type
+							// switch' it and perform the right check.
+							switch v := a.(type) {
+							case k8stesting.GetAction:
+								match = v.GetName() == w.name
+							case k8stesting.UpdateAction:
+								obj := v.GetObject()
+								uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
+								require.NoError(t, err)
+								u := &unstructured.Unstructured{Object: uObj}
+								match = w.name == u.GetName()
+							}
+						}
+
+						// short circuit to the end of collected actions if the action has matched.
+						if match {
+							break
+						}
+					}
+					require.True(t, match, "expected action %+v not found", w)
+				}
+			}
+		}
+	}
+
+	matchLabels := map[string]string{
+		"connects-to": "database",
+	}
+
+	f := mocks.NewFake(t, reconcilerName)
+	f.S.AddKnownTypes(v1alpha1.SchemeGroupVersion, &v1alpha1.ServiceBindingRequest{})
+	f.S.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.ConfigMap{})
+
+	d := f.AddMockedUnstructuredDeployment(reconcilerName, matchLabels)
+	f.AddMockedUnstructuredDatabaseCRD()
+	f.AddMockedUnstructuredConfigMap("db1")
+	f.AddMockedUnstructuredConfigMap("db2")
+
+	// create and munge a Database CR since there's no "Status" field in
+	// databases.postgres.baiju.dev, requiring us to add the field directly in the unstructured
+	// object
+	db1 := f.AddMockedUnstructuredPostgresDatabaseCR("db1")
+	{
+		runtimeStatus := map[string]interface{}{
+			"dbConfigMap": "db1",
+		}
+		err := unstructured.SetNestedMap(db1.Object, runtimeStatus, "status")
+		require.NoError(t, err)
+	}
+
+	db2 := f.AddMockedUnstructuredPostgresDatabaseCR("db2")
+	{
+		runtimeStatus := map[string]interface{}{
+			"dbConfigMap": "db2",
+		}
+		err := unstructured.SetNestedMap(db2.Object, runtimeStatus, "status")
+		require.NoError(t, err)
+	}
+
+	// create the ServiceBindingRequest
+	sbrSingleService := &v1alpha1.ServiceBindingRequest{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "apps.openshift.io/v1alpha1",
+			Kind:       "ServiceBindingRequest",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "single-sbr",
+		},
+		Spec: v1alpha1.ServiceBindingRequestSpec{
+			ApplicationSelector: v1alpha1.ApplicationSelector{
+				LabelSelector: &metav1.LabelSelector{
+					MatchLabels: matchLabels,
+				},
+				GroupVersionResource: metav1.GroupVersionResource{
+					Group:    d.GetObjectKind().GroupVersionKind().Group,
+					Version:  d.GetObjectKind().GroupVersionKind().Version,
+					Resource: "deployments",
+				},
+				ResourceRef: d.GetName(),
+			},
+			BackingServiceSelectors: []v1alpha1.BackingServiceSelector{
+				{
+					GroupVersionKind: metav1.GroupVersionKind{
+						Group:   db1.GetObjectKind().GroupVersionKind().Group,
+						Version: db1.GetObjectKind().GroupVersionKind().Version,
+						Kind:    db1.GetObjectKind().GroupVersionKind().Kind,
+					},
+					ResourceRef: db1.GetName(),
+				},
+			},
+		},
+		Status: v1alpha1.ServiceBindingRequestStatus{},
+	}
+	f.AddMockResource(sbrSingleService)
+
+	// create the ServiceBindingRequest
+	sbrMultipleServices := &v1alpha1.ServiceBindingRequest{
+		TypeMeta: metav1.TypeMeta{
+			APIVersion: "apps.openshift.io/v1alpha1",
+			Kind:       "ServiceBindingRequest",
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "multiple-sbr",
+		},
+		Spec: v1alpha1.ServiceBindingRequestSpec{
+			ApplicationSelector: v1alpha1.ApplicationSelector{
+				LabelSelector: &metav1.LabelSelector{
+					MatchLabels: matchLabels,
+				},
+				GroupVersionResource: metav1.GroupVersionResource{
+					Group:    d.GetObjectKind().GroupVersionKind().Group,
+					Version:  d.GetObjectKind().GroupVersionKind().Version,
+					Resource: "deployments",
+				},
+				ResourceRef: d.GetName(),
+			},
+			BackingServiceSelectors: []v1alpha1.BackingServiceSelector{
+				{
+					GroupVersionKind: metav1.GroupVersionKind{
+						Group:   db1.GetObjectKind().GroupVersionKind().Group,
+						Version: db1.GetObjectKind().GroupVersionKind().Version,
+						Kind:    db1.GetObjectKind().GroupVersionKind().Kind,
+					},
+					ResourceRef: db1.GetName(),
+				},
+				{
+					GroupVersionKind: metav1.GroupVersionKind{
+						Group:   db2.GetObjectKind().GroupVersionKind().Group,
+						Version: db2.GetObjectKind().GroupVersionKind().Version,
+						Kind:    db2.GetObjectKind().GroupVersionKind().Kind,
+					},
+					ResourceRef: db2.GetName(),
+				},
+			},
+		},
+		Status: v1alpha1.ServiceBindingRequestStatus{},
+	}
+	f.AddMockResource(sbrMultipleServices)
+
+	logger := log.NewLog("service-binder")
+	t.Run("single bind golden path", assertBind(args{
+		options: &ServiceBinderOptions{
+			Logger:                 logger,
+			DynClient:              f.FakeDynClient(),
+			DetectBindingResources: false,
+			EnvVarPrefix:           "",
+			SBR:                    sbrSingleService,
+			Client:                 f.FakeClient(),
+		},
+		wantedActions: []wantedAction{
+			{
+				resource: "servicebindingrequests",
+				verb:     "update",
+				name:     sbrSingleService.GetName(),
+			},
+			{
+				resource: "secrets",
+				verb:     "update",
+				name:     sbrSingleService.GetName(),
+			},
+			{
+				resource: "databases",
+				verb:     "update",
+				name:     db1.GetName(),
+			},
+		},
+	}))
+
+	t.Run("bind with binding resource detection", assertBind(args{
+		options: &ServiceBinderOptions{
+			Logger:                 logger,
+			DynClient:              f.FakeDynClient(),
+			DetectBindingResources: true,
+			EnvVarPrefix:           "",
+			SBR:                    sbrSingleService,
+			Client:                 f.FakeClient(),
+		},
+	}))
+
+	// Missing SBR returns an InvalidOptionsErr
+	t.Run("bind missing SBR", assertBind(args{
+		options: &ServiceBinderOptions{
+			Logger:                 logger,
+			DynClient:              f.FakeDynClient(),
+			DetectBindingResources: false,
+			EnvVarPrefix:           "",
+			SBR:                    nil,
+			Client:                 f.FakeClient(),
+		},
+		wantBuildErr: InvalidOptionsErr,
+	}))
+
+	t.Run("multiple services bind golden path", assertBind(args{
+		options: &ServiceBinderOptions{
+			Logger:                 logger,
+			DynClient:              f.FakeDynClient(),
+			DetectBindingResources: false,
+			EnvVarPrefix:           "",
+			SBR:                    sbrMultipleServices,
+			Client:                 f.FakeClient(),
+		},
+		wantedActions: []wantedAction{
+			{
+				resource: "servicebindingrequests",
+				verb:     "update",
+				name:     sbrMultipleServices.GetName(),
+			},
+			{
+				resource: "secrets",
+				verb:     "update",
+				name:     sbrMultipleServices.GetName(),
+			},
+			{
+				resource: "databases",
+				verb:     "update",
+				name:     db1.GetName(),
+			},
+			{
+				resource: "databases",
+				verb:     "update",
+				name:     db2.GetName(),
+			},
+		},
+	}))
+}
diff --git a/pkg/controller/servicebindingrequest/retriever_test.go b/pkg/controller/servicebindingrequest/retriever_test.go
index 109e8299..7c8d73de 100644
--- a/pkg/controller/servicebindingrequest/retriever_test.go
+++ b/pkg/controller/servicebindingrequest/retriever_test.go
@@ -153,7 +153,7 @@ func TestRetrieverWithConfigMap(t *testing.T) {
 
 	f := mocks.NewFake(t, ns)
 	f.AddMockedUnstructuredCSV("csv")
-	f.AddMockedConfigMap(crName)
+	f.AddMockedUnstructuredConfigMap(crName)
 	f.AddMockedDatabaseCR(crName)
 
 	crdDescription := mocks.CRDDescriptionConfigMapMock()
diff --git a/pkg/controller/servicebindingrequest/secret_test.go b/pkg/controller/servicebindingrequest/secret_test.go
index 75bb1cc8..cb30534d 100644
--- a/pkg/controller/servicebindingrequest/secret_test.go
+++ b/pkg/controller/servicebindingrequest/secret_test.go
@@ -54,7 +54,7 @@ func TestSecretNew(t *testing.T) {
 	})
 
 	t.Run("Get", func(t *testing.T) {
-		u, err := s.Get()
+		u, _, err := s.Get()
 		assert.NoError(t, err)
 		assertSecretNamespacedName(t, u, ns, name)
 	})
diff --git a/test/mocks/fake.go b/test/mocks/fake.go
index 68e1c05e..3e3dd612 100644
--- a/test/mocks/fake.go
+++ b/test/mocks/fake.go
@@ -12,15 +12,15 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/runtime/schema"
-	"k8s.io/client-go/dynamic"
 	fakedynamic "k8s.io/client-go/dynamic/fake"
 	"k8s.io/client-go/kubernetes/scheme"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	ocav1 "github.com/openshift/api/apps/v1"
-	v1alpha1 "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
 	knativev1 "knative.dev/serving/pkg/apis/serving/v1"
+
+	v1alpha1 "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
 )
 
 // Fake defines all the elements to fake a kubernetes api client.
@@ -107,10 +107,12 @@ func (f *Fake) AddMockedUnstructuredCSVWithVolumeMount(name string) {
 }
 
 // AddMockedDatabaseCR add mocked object from DatabaseCRMock.
-func (f *Fake) AddMockedDatabaseCR(ref string) {
+func (f *Fake) AddMockedDatabaseCR(ref string) runtime.Object {
 	require.NoError(f.t, pgapis.AddToScheme(f.S))
 	f.S.AddKnownTypes(pgv1alpha1.SchemeGroupVersion, &pgv1alpha1.Database{})
-	f.objs = append(f.objs, DatabaseCRMock(f.ns, ref))
+	mock := DatabaseCRMock(f.ns, ref)
+	f.objs = append(f.objs, mock)
+	return mock
 }
 
 func (f *Fake) AddMockedUnstructuredDatabaseCR(ref string) {
@@ -130,12 +132,13 @@ func (f *Fake) AddMockedUnstructuredDeploymentConfig(name string, matchLabels ma
 }
 
 // AddMockedUnstructuredDeployment add mocked object from UnstructuredDeploymentMock.
-func (f *Fake) AddMockedUnstructuredDeployment(name string, matchLabels map[string]string) {
+func (f *Fake) AddMockedUnstructuredDeployment(name string, matchLabels map[string]string) *unstructured.Unstructured {
 	require.NoError(f.t, appsv1.AddToScheme(f.S))
 	d, err := UnstructuredDeploymentMock(f.ns, name, matchLabels)
 	require.NoError(f.t, err)
 	f.S.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.Deployment{})
 	f.objs = append(f.objs, d)
+	return d
 }
 
 // AddMockedUnstructuredKnativeService add mocked object from UnstructuredKnativeService.
@@ -147,18 +150,20 @@ func (f *Fake) AddMockedUnstructuredKnativeService(name string, matchLabels map[
 	f.objs = append(f.objs, d)
 }
 
-func (f *Fake) AddMockedUnstructuredDatabaseCRD() {
+func (f *Fake) AddMockedUnstructuredDatabaseCRD() *unstructured.Unstructured {
 	require.NoError(f.t, apiextensionv1beta1.AddToScheme(f.S))
 	c, err := UnstructuredDatabaseCRDMock(f.ns)
 	require.NoError(f.t, err)
 	f.S.AddKnownTypes(apiextensionv1beta1.SchemeGroupVersion, &apiextensionv1beta1.CustomResourceDefinition{})
 	f.objs = append(f.objs, c)
+	return c
 }
 
-func (f *Fake) AddMockedUnstructuredPostgresDatabaseCR(ref string) {
+func (f *Fake) AddMockedUnstructuredPostgresDatabaseCR(ref string) *unstructured.Unstructured {
 	d, err := UnstructuredPostgresDatabaseCRMock(f.ns, ref)
 	require.NoError(f.t, err)
 	f.objs = append(f.objs, d)
+	return d
 }
 
 // AddMockedSecret add mocked object from SecretMock.
@@ -166,9 +171,12 @@ func (f *Fake) AddMockedSecret(name string) {
 	f.objs = append(f.objs, SecretMock(f.ns, name))
 }
 
-// AddMockedConfigMap add mocked object from ConfigMapMock.
-func (f *Fake) AddMockedConfigMap(name string) {
-	f.objs = append(f.objs, ConfigMapMock(f.ns, name))
+// AddMockedUnstructuredConfigMap add mocked object from ConfigMapMock.
+func (f *Fake) AddMockedUnstructuredConfigMap(name string) {
+	mock := ConfigMapMock(f.ns, name)
+	uObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(mock)
+	require.NoError(f.t, err)
+	f.objs = append(f.objs, &unstructured.Unstructured{Object: uObj})
 }
 
 func (f *Fake) AddMockResource(resource runtime.Object) {
@@ -181,7 +189,7 @@ func (f *Fake) FakeClient() client.Client {
 }
 
 // FakeDynClient returns fake dynamic api client.
-func (f *Fake) FakeDynClient() dynamic.Interface {
+func (f *Fake) FakeDynClient() *fakedynamic.FakeDynamicClient {
 	return fakedynamic.NewSimpleDynamicClient(f.S, f.objs...)
 }
 
diff --git a/test/operator-hub/operator_source.yaml b/test/operator-hub/operator_source.yaml
new file mode 100644
index 00000000..4e10c4c3
--- /dev/null
+++ b/test/operator-hub/operator_source.yaml
@@ -0,0 +1,9 @@
+apiVersion: operators.coreos.com/v1
+kind: OperatorSource
+metadata:
+  name: example-operators
+  namespace: openshift-marketplace
+spec:
+  type: appregistry
+  endpoint: https://quay.io/cnr
+  registryNamespace: redhat-developer
diff --git a/test/operator-hub/subscription.yaml b/test/operator-hub/subscription.yaml
index fad05470..efc11063 100644
--- a/test/operator-hub/subscription.yaml
+++ b/test/operator-hub/subscription.yaml
@@ -2,10 +2,11 @@
 apiVersion: operators.coreos.com/v1alpha1
 kind: Subscription
 metadata:
-  name: my-service-binding
+  name: service-binding-operator
   namespace: openshift-operators
 spec:
-  channel: dev
-  name: service-binding
-  source: my-catalog
-  sourceNamespace: openshift-operator-lifecycle-manager
+  channel: alpha
+  installPlanApproval: Automatic
+  name: service-binding-operator
+  source: example-operators
+  sourceNamespace: openshift-marketplace