Repository: redhat-developer/service-binding-operator
Pull request #339 (2020-02-18 16:17:04)

Remove "Coming Soon"
.. for operators with no metadata




Patch:
diff --git a/.travis.yml b/.travis.yml
index 5cdbd7ec..ca5ea17e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -35,9 +35,9 @@ before_install:
 script:
   # Run merge-to-master-release
   - if [ "$TRAVIS_BRANCH" = "master" ]; then
-    make merge-to-master-release && \
-    make push-to-manifest-repo && \
-    make push-bundle-to-quay || exit 1
+    make merge-to-master-release &&
+    make push-to-manifest-repo &&
+    make push-bundle-to-quay || exit 1;
     fi
 
 after_success:
diff --git a/Dockerfile.rhel b/Dockerfile.rhel
index 4ca6eee7..7c755827 100644
--- a/Dockerfile.rhel
+++ b/Dockerfile.rhel
@@ -16,7 +16,6 @@ COPY build build
 COPY cmd cmd
 COPY deploy deploy
 COPY hack hack
-COPY manifests manifests
 COPY pkg pkg
 COPY test test
 COPY vendor vendor
diff --git a/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml b/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml
index d5ac4c97..bb2a33e8 100644
--- a/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml
+++ b/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml
@@ -116,6 +116,28 @@ spec:
               - resourceRef
               - version
               type: object
+            backingServiceSelectors:
+              description: BackingServiceSelectors is used to identify multiple backing
+                services.
+              items:
+                description: BackingServiceSelector defines the selector based on
+                  resource name, version, and resource kind
+                properties:
+                  group:
+                    type: string
+                  kind:
+                    type: string
+                  resourceRef:
+                    type: string
+                  version:
+                    type: string
+                required:
+                - group
+                - kind
+                - resourceRef
+                - version
+                type: object
+              type: array
             customEnvVar:
               description: Custom env variables
               items:
@@ -226,7 +248,6 @@ spec:
               type: string
           required:
           - applicationSelector
-          - backingServiceSelector
           type: object
         status:
           description: ServiceBindingRequestStatus defines the observed state of ServiceBindingRequest
diff --git a/docs/OperatorBestPractices.md b/docs/OperatorBestPractices.md
index b24773da..e297be88 100644
--- a/docs/OperatorBestPractices.md
+++ b/docs/OperatorBestPractices.md
@@ -32,7 +32,7 @@ There are three methods for making Operator Managed Backing Service Bindable:
 
 * [Operator Providing Metadata in CRD Annotations](#operator-providing-metadata-in-crd-annotations)
 * [Operator Providing Metadata in OLM](#operator-providing-metadata-in-olm)
-* [Operator Not Providing Metadata](#operator-not-providing-metadata) Coming Soon!
+* [Operator Not Providing Metadata](#operator-not-providing-metadata)
 
 ### Operator Providing Metadata in CRD Annotations
 
diff --git a/examples/multiple_services/README.md b/examples/multiple_services/README.md
new file mode 100644
index 00000000..cdfd1f44
--- /dev/null
+++ b/examples/multiple_services/README.md
@@ -0,0 +1,157 @@
+# Multiple Services Binding Example
+
+This document describes step-by-step the actions to create the required
+infrastructure to demonstrate multiple services binding built on existing
+Postgres and ETCD examples.
+
+As *Cluster Administrator*, the reader will install both the "PostgreSQL
+Database" and the "ETCD" operators, as described below.
+
+Once the cluster setup is finished, the reader will create a Postgres
+database and a ETCD cluster, and bind services to a Node application as
+a *Developer*.
+
+## Cluster Configuration
+
+### Create a New Project
+
+Create a new project, in this example it is called `multiple-services-demo`.
+
+### Install the Postgres Operator
+
+Switch to the *Administrator* perspective.
+
+Add an extra OperatorSource by pushing the "+" button on the top right corner
+and pasting the following:
+
+```yaml
+---
+apiVersion: operators.coreos.com/v1
+kind: OperatorSource
+metadata:
+  name: db-operators
+  namespace: openshift-marketplace
+spec:
+  type: appregistry
+  endpoint: https://quay.io/cnr
+  registryNamespace: pmacik
+```
+
+Go to "Operators > OperatorHub", search for "Postgres" and install "PostgreSQL
+Database" provided by Red Hat.
+
+Select "A specific namespace on the cluster" in "Installation Mode", select the
+"multiple-services-demo" namespace in "Installed Namespace" and push "Subscribe".
+
+### Install the ETCD Operator
+
+Go to "Operators > OperatorHub", search for "etcd" and install "etcd" provided by
+CNCF.
+
+Select "A specific namespace on the cluster" in "Installation Mode", select the
+"multiple-services-demo" namespace in "Installed Namespace" and push "Subscribe".
+
+## Application Configuration
+
+Switch to the *Developer* perspective.
+
+Create the Postgres database `db-demo` by pushing the "+" button on the top right
+corner and pasting the following:
+
+```yaml
+---
+apiVersion: postgresql.baiju.dev/v1alpha1
+kind: Database
+metadata:
+  name: db-demo
+spec:
+  image: docker.io/postgres
+  imageName: postgres
+  dbName: db-demo
+```
+
+Create the ETCD cluster `etcd-demo` by pushing the "+" button on the top right
+corner and pasting the following:
+
+```yaml
+---
+apiVersion: etcd.database.coreos.com/v1beta2
+kind: EtcdCluster
+metadata:
+ name: etcd-demo
+spec:
+ size: 5
+ version: "3.2.13"
+```
+
+Import the application by pushing the "+Add" button on the left side of the
+screen, and then the "From Git" button. Fill the "Git Repo URL" with
+`https://github.com/akashshinde/node-todo.git`; the repository will be
+validated and the appropriate "Builder Image" and "Builder Image Version"
+will be selected. Push the "Create" button to create the application.
+
+Create the ServiceBindingRequest `node-todo-git` by pushing the "+" button
+on the top right corner and pasting the following:
+
+```yaml
+---
+apiVersion: apps.openshift.io/v1alpha1
+kind: ServiceBindingRequest
+metadata:
+  name: node-todo-git
+spec:
+  applicationSelector:
+    resourceRef: node-todo-git
+    group: apps
+    version: v1
+    resource: deployments
+  backingServiceSelectors:
+  - group: postgresql.baiju.dev
+    version: v1alpha1
+    kind: Database
+    resourceRef: db-demo
+  - group: etcd.database.coreos.com
+    version: v1beta2
+    kind: EtcdCluster
+    resourceRef: etcd-demo
+  detectBindingResources: true
+```
+
+Once the binding is processed, the secret can be verified by executing
+`kubectl get secrets node-todo-git -o yaml`:
+
+```yaml
+apiVersion: v1
+data:
+  DATABASE_CLUSTERIP: MTcyLjMwLjcyLjg5
+  DATABASE_CONFIGMAP_DB_HOST: MTcyLjMwLjcyLjg5
+  DATABASE_CONFIGMAP_DB_NAME: ZGItZGVtbw==
+  DATABASE_CONFIGMAP_DB_PASSWORD: cGFzc3dvcmQ=
+  DATABASE_CONFIGMAP_DB_PORT: NTQzMg==
+  DATABASE_CONFIGMAP_DB_USERNAME: cG9zdGdyZXM=
+  DATABASE_CONFIGMAP_PASSWORD: cGFzc3dvcmQ=
+  DATABASE_CONFIGMAP_USER: cG9zdGdyZXM=
+  DATABASE_DB_HOST: MTcyLjMwLjcyLjg5
+  DATABASE_DB_NAME: ZGItZGVtbw==
+  DATABASE_DB_PASSWORD: cGFzc3dvcmQ=
+  DATABASE_DB_PORT: NTQzMg==
+  DATABASE_DB_USERNAME: cG9zdGdyZXM=
+  DATABASE_DBCONNECTIONIP: MTcyLjMwLjcyLjg5
+  DATABASE_DBCONNECTIONPORT: NTQzMg==
+  DATABASE_DBNAME: ZGItZGVtbw==
+  DATABASE_SECRET_PASSWORD: cGFzc3dvcmQ=
+  DATABASE_SECRET_USER: cG9zdGdyZXM=
+  ETCDCLUSTER_CLUSTERIP: MTcyLjMwLjYyLjUy
+kind: Secret
+metadata:
+  annotations:
+    service-binding-operator.apps.openshift.io/binding-name: node-todo-git
+    service-binding-operator.apps.openshift.io/binding-namespace: multiple-services-demo
+  creationTimestamp: "2020-02-14T11:58:29Z"
+  name: node-todo-git
+  namespace: multiple-services-demo
+  resourceVersion: "257567"
+  selfLink: /api/v1/namespaces/multiple-services-demo/secrets/node-todo-git
+  uid: 15aafcae-d334-49d8-be4c-2331f9c7cffe
+type: Opaque
+```
diff --git a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
index 45ede407..b0fbdd72 100644
--- a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
+++ b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go
@@ -27,10 +27,11 @@ type ServiceBindingRequestSpec struct {
 	CustomEnvVar []corev1.EnvVar `json:"customEnvVar,omitempty"`
 
 	// BackingServiceSelector is used to identify the backing service operator.
-	BackingServiceSelector BackingServiceSelector `json:"backingServiceSelector"`
+	// +optional
+	BackingServiceSelector BackingServiceSelector `json:"backingServiceSelector,omitempty"`
 
 	// BackingServiceSelectors is used to identify multiple backing services.
-	BackingServiceSelectors []BackingServiceSelector `json:"backingServiceSelectors"`
+	BackingServiceSelectors []BackingServiceSelector `json:"backingServiceSelectors,omitempty"`
 
 	// ApplicationSelector is used to identify the application connecting to the
 	// backing service operator.
diff --git a/pkg/controller/servicebindingrequest/binder.go b/pkg/controller/servicebindingrequest/binder.go
index 8004a8f8..3543555d 100644
--- a/pkg/controller/servicebindingrequest/binder.go
+++ b/pkg/controller/servicebindingrequest/binder.go
@@ -421,20 +421,21 @@ func nestedMapComparison(a, b *unstructured.Unstructured, fields ...string) (boo
 	var (
 		aMap map[string]interface{}
 		bMap map[string]interface{}
-		ok   bool
+		aOk  bool
+		bOk  bool
 		err  error
 	)
 
-	if aMap, ok, err = unstructured.NestedMap(a.Object, fields...); err != nil {
+	if aMap, aOk, err = unstructured.NestedMap(a.Object, fields...); err != nil {
 		return false, err
-	} else if !ok {
-		return false, fmt.Errorf("original object doesn't have a 'spec' field")
 	}
 
-	if bMap, ok, err = unstructured.NestedMap(b.Object, fields...); err != nil {
+	if bMap, bOk, err = unstructured.NestedMap(b.Object, fields...); err != nil {
 		return false, err
-	} else if !ok {
-		return false, fmt.Errorf("original object doesn't have a 'spec' field")
+	}
+
+	if aOk != bOk {
+		return false, nil
 	}
 
 	result := cmp.DeepEqual(aMap, bMap)()
diff --git a/pkg/controller/servicebindingrequest/binding.go b/pkg/controller/servicebindingrequest/binding.go
index 1cfea17c..81a7cd75 100644
--- a/pkg/controller/servicebindingrequest/binding.go
+++ b/pkg/controller/servicebindingrequest/binding.go
@@ -186,7 +186,7 @@ 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)
+	secretObj, err := b.Secret.Commit(b.Data)
 	if err != nil {
 		b.Logger.Error(err, "On saving secret data..")
 		return b.onError(err, b.SBR, sbrStatus, nil)
@@ -208,7 +208,7 @@ func (b *ServiceBinder) Bind() (reconcile.Result, error) {
 
 	// 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 {
+	if err = SetSBRAnnotations(b.DynClient, namespacedName, append(b.Objects, secretObj)); err != nil {
 		b.Logger.Error(err, "On setting annotations in related objects.")
 		return b.onError(err, b.SBR, sbrStatus, updatedObjects)
 	}
@@ -220,7 +220,7 @@ func (b *ServiceBinder) Bind() (reconcile.Result, error) {
 	}
 
 	// appending finalizer, should be later removed upon resource deletion
-	sbr.SetFinalizers(append(sbr.GetFinalizers(), Finalizer))
+	sbr.SetFinalizers(append(removeStringSlice(b.SBR.GetFinalizers(), Finalizer), Finalizer))
 	if _, err = b.updateServiceBindingRequest(sbr); err != nil {
 		return NoRequeue(err)
 	}
@@ -302,17 +302,10 @@ func BuildServiceBinder(options *ServiceBinderOptions) (*ServiceBinder, error) {
 
 	// 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),
+		Binder:    NewBinder(ctx, options.Client, options.DynClient, options.SBR, retriever.VolumeKeys),
 		DynClient: options.DynClient,
 		SBR:       options.SBR,
 		Objects:   objs,
diff --git a/pkg/controller/servicebindingrequest/olm.go b/pkg/controller/servicebindingrequest/olm.go
index e226ed51..c48ab3f2 100644
--- a/pkg/controller/servicebindingrequest/olm.go
+++ b/pkg/controller/servicebindingrequest/olm.go
@@ -1,6 +1,7 @@
 package servicebindingrequest
 
 import (
+	"fmt"
 	"sort"
 	"strings"
 
@@ -114,9 +115,8 @@ func (o *OLM) loopCRDDescriptions(
 	return nil
 }
 
-// SelectCRDDescriptionByGVK return a single CRDDescription filled with information from CSVs and
-// annotations.
-func (o *OLM) SelectCRDDescriptionByGVK(gvk schema.GroupVersionKind, crd *unstructured.Unstructured) (*olmv1alpha1.CRDDescription, error) {
+// SelectCRDByGVK return a single CRD based on a given GVK.
+func (o *OLM) SelectCRDByGVK(gvk schema.GroupVersionKind, crd *unstructured.Unstructured) (*olmv1alpha1.CRDDescription, error) {
 	log := o.logger.WithValues("Selector.GVK", gvk)
 	ownedCRDs, err := o.ListCSVOwnedCRDs()
 	if err != nil {
@@ -124,17 +124,19 @@ func (o *OLM) SelectCRDDescriptionByGVK(gvk schema.GroupVersionKind, crd *unstru
 		return nil, err
 	}
 
-	var resultCRDDescription *olmv1alpha1.CRDDescription
+	var crdDescription *olmv1alpha1.CRDDescription
 
 	// CRDDescription is both used by OLM to configure OLM descriptors in manifests existing in the
 	// cluster but is also built from annotations present in the CRD
 	if crd != nil {
-		resultCRDDescription, err = buildCRDDescriptionFromCRD(crd)
+		crdDescription, err = buildCRDDescriptionFromCRD(crd)
 		if err != nil {
 			return nil, err
 		}
 	}
 
+	crdDescriptions := []*olmv1alpha1.CRDDescription{}
+
 	err = o.loopCRDDescriptions(ownedCRDs, func(crdDescription *olmv1alpha1.CRDDescription) {
 		log = o.logger.WithValues(
 			"CRDDescription.Name", crdDescription.Name,
@@ -152,18 +154,21 @@ func (o *OLM) SelectCRDDescriptionByGVK(gvk schema.GroupVersionKind, crd *unstru
 			return
 		}
 		log.Debug("CRDDescription object matches selector!")
-		if resultCRDDescription == nil {
-			resultCRDDescription = crdDescription.DeepCopy()
-		}
-
-		resultCRDDescription.StatusDescriptors = append(resultCRDDescription.StatusDescriptors, crdDescription.StatusDescriptors...)
-		resultCRDDescription.SpecDescriptors = append(resultCRDDescription.SpecDescriptors, crdDescription.SpecDescriptors...)
+		crdDescriptions = append(crdDescriptions, crdDescription)
 	})
 	if err != nil {
 		return nil, err
 	}
 
-	return resultCRDDescription, nil
+	if len(crdDescriptions) == 0 && crdDescription == nil {
+		log.Debug("No CRD could be found for GVK.")
+		return nil, fmt.Errorf("no crd could be found for gvk")
+	} else if len(crdDescriptions) == 0 {
+		// use the crdDescription built from CRD annotations as fallback
+		return crdDescription, nil
+	}
+
+	return crdDescriptions[0], nil
 }
 
 // buildCRDDescriptionFromCRD builds a CRDDescription from annotations present in the CRD.
diff --git a/pkg/controller/servicebindingrequest/plan_decoupled.go b/pkg/controller/servicebindingrequest/plan_decoupled.go
index ce0bfb25..9252cfaa 100644
--- a/pkg/controller/servicebindingrequest/plan_decoupled.go
+++ b/pkg/controller/servicebindingrequest/plan_decoupled.go
@@ -4,12 +4,10 @@ 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")
+	return p.RelatedResources.GetCRs()
 }
 
 // GetRelatedResources returns the collection of related resources enumerated in the plan.
 func (p *Plan) GetRelatedResources() RelatedResources {
-	//return p.RelatedResources
-	panic("implement me")
+	return p.RelatedResources
 }
diff --git a/pkg/controller/servicebindingrequest/planner.go b/pkg/controller/servicebindingrequest/planner.go
index 70e6bb50..6a4ef392 100644
--- a/pkg/controller/servicebindingrequest/planner.go
+++ b/pkg/controller/servicebindingrequest/planner.go
@@ -2,9 +2,7 @@ package servicebindingrequest
 
 import (
 	"context"
-	"strings"
 
-	olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
 	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -30,86 +28,87 @@ type Planner struct {
 
 // Plan outcome, after executing planner.
 type Plan struct {
-	Ns             string                         // namespace name
-	Name           string                         // plan name, same than ServiceBindingRequest
-	CRDDescription *olmv1alpha1.CRDDescription    // custom resource definition description
-	CR             *unstructured.Unstructured     // custom resource object
-	SBR            v1alpha1.ServiceBindingRequest // service binding request
+	Ns               string                         // namespace name
+	Name             string                         // plan name, same than ServiceBindingRequest
+	SBR              v1alpha1.ServiceBindingRequest // service binding request
+	RelatedResources RelatedResources               // CR and CRDDescription pairs SBR related
 }
 
 // searchCR based on a CustomResourceDefinitionDescription and name, search for the object.
-func (p *Planner) searchCR() (*unstructured.Unstructured, error) {
-	bss := p.sbr.Spec.BackingServiceSelector
-	gvk := schema.GroupVersionKind{Group: bss.Group, Version: bss.Version, Kind: bss.Kind}
-	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
-	opts := metav1.GetOptions{}
-
-	log := p.logger.WithValues("CR.GVK", gvk.String(), "CR.GVR", gvr.String())
-	log.Debug("Searching for CR instance...")
-
-	cr, err := p.client.Resource(gvr).Namespace(p.sbr.GetNamespace()).Get(bss.ResourceRef, opts)
-
-	if err != nil {
-		log.Info("during reading CR")
-		return nil, err
+func (p *Planner) searchCR(namespace string, selector v1alpha1.BackingServiceSelector) (*unstructured.Unstructured, error) {
+	// gvr is the plural guessed resource for the given selector
+	gvk := schema.GroupVersionKind{
+		Group:   selector.Group,
+		Version: selector.Version,
+		Kind:    selector.Kind,
 	}
+	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
+	// delegate the search selector's namespaced resource client
+	return p.client.Resource(gvr).Namespace(namespace).Get(selector.ResourceRef, metav1.GetOptions{})
+}
 
-	log.Debug("Found target CR!", "CR.Name", cr.GetName())
-	return cr, nil
+// CRDGVR is the plural GVR for Kubernetes CRDs.
+var CRDGVR = schema.GroupVersionResource{
+	Group:    "apiextensions.k8s.io",
+	Version:  "v1beta1",
+	Resource: "customresourcedefinitions",
 }
 
-// searchCRD based on a CustomResourceDefinitionDescription and name, search for the object.
-func (p *Planner) searchCRD() (*unstructured.Unstructured, error) {
-	bss := p.sbr.Spec.BackingServiceSelector
-	gvk := schema.GroupVersionKind{Group: "apiextensions.k8s.io", Version: "v1beta1", Kind: "CustomResourceDefinition"}
+// searchCRD returns the CRD related to the gvk.
+func (p *Planner) searchCRD(gvk schema.GroupVersionKind) (*unstructured.Unstructured, error) {
+	// gvr is the plural guessed resource for the given GVK
 	gvr, _ := meta.UnsafeGuessKindToResource(gvk)
-	opts := metav1.GetOptions{}
-
-	logger := p.logger.WithValues("CR.GVK", gvk.String(), "CR.GVR", gvr.String(), "Kind", bss.Kind)
-	logger.Info("Searching for CRD instance...")
-
-	// TODO: This hack should be removed! Probably the name should be prompted from user through SBR CR.
-	name := strings.ToLower(bss.Kind) + "s." + bss.Group
-	crd, err := p.client.Resource(gvr).Get(name, opts)
-
-	if err != nil {
-		logger.Info("during reading CRD")
-		return nil, err
-	}
-
-	logger.WithValues("CR.Name", crd.GetName()).Info("Found target CR!")
-	return crd, nil
+	// crdName is the string'fied GroupResource, e.g. "deployments.apps"
+	crdName := gvr.GroupResource().String()
+	// delegate the search to the CustomResourceDefinition resource client
+	return p.client.Resource(CRDGVR).Get(crdName, metav1.GetOptions{})
 }
 
 // Plan by retrieving the necessary resources related to binding a service backend.
 func (p *Planner) Plan() (*Plan, error) {
-	bss := p.sbr.Spec.BackingServiceSelector
-	gvk := schema.GroupVersionKind{Group: bss.Group, Version: bss.Version, Kind: bss.Kind}
-	olm := NewOLM(p.client, p.sbr.GetNamespace())
-	crd, err := p.searchCRD()
-	if err != nil {
-		return nil, err
-	}
-
-	p.logger.Debug("After search crd", "CRD", crd)
-
-	crdDescription, err := olm.SelectCRDDescriptionByGVK(gvk, crd)
-	if err != nil {
-		return nil, err
+	ns := p.sbr.GetNamespace()
+	selectors := append([]v1alpha1.BackingServiceSelector{}, p.sbr.Spec.BackingServiceSelectors...)
+	if (p.sbr.Spec.BackingServiceSelector != v1alpha1.BackingServiceSelector{}) {
+		selectors = append(selectors, p.sbr.Spec.BackingServiceSelector)
 	}
 
-	// retrieve the CR based on kind, api-version and name
-	cr, err := p.searchCR()
-	if err != nil {
-		return nil, err
+	relatedResources := make([]*RelatedResource, 0)
+	for _, s := range selectors {
+		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)
+		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)
+		if err != nil {
+			return nil, err
+		}
+		p.logger.Debug("Resolved CRDDescription", "CRDDescription", crdDescription)
+
+		cr, err := p.searchCR(ns, s)
+		if err != nil {
+			return nil, err
+		}
+
+		r := &RelatedResource{
+			CRDDescription: crdDescription,
+			CR:             cr,
+		}
+		relatedResources = append(relatedResources, r)
+		p.logger.Debug("Resolved related resource", "RelatedResource", r)
 	}
 
 	return &Plan{
-		Ns:             p.sbr.GetNamespace(),
-		Name:           p.sbr.GetName(),
-		CRDDescription: crdDescription,
-		CR:             cr,
-		SBR:            *p.sbr,
+		Name:             p.sbr.GetName(),
+		Ns:               ns,
+		RelatedResources: relatedResources,
+		SBR:              *p.sbr,
 	}, nil
 }
 
diff --git a/pkg/controller/servicebindingrequest/reconciler.go b/pkg/controller/servicebindingrequest/reconciler.go
index 88ebe6c4..431dd4a5 100644
--- a/pkg/controller/servicebindingrequest/reconciler.go
+++ b/pkg/controller/servicebindingrequest/reconciler.go
@@ -1,13 +1,7 @@
 package servicebindingrequest
 
 import (
-	"context"
-	"errors"
-	"fmt"
-
-	"gotest.tools/assert/cmp"
 	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/types"
 	"k8s.io/client-go/dynamic"
@@ -15,7 +9,6 @@ import (
 	"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"
 )
 
@@ -27,202 +20,58 @@ type Reconciler struct {
 }
 
 const (
-	// BindingSuccess binding has succeeded
 	BindingSuccess = "Success"
-	// sbrFinalizer annotation used in finalizer steps
-	sbrFinalizer = "finalizer.servicebindingrequest.openshift.io"
 )
 
 // reconcilerLog local logger instance
 var reconcilerLog = log.NewLog("reconciler")
 
-// setSecretName update the CR status field to "in progress", and setting secret name.
-func (r *Reconciler) setSecretName(sbrStatus *v1alpha1.ServiceBindingRequestStatus, name string) {
-	sbrStatus.BindingStatus = bindingInProgress
-	sbrStatus.Secret = name
-}
-
-// setStatus update the CR status field.
-func (r *Reconciler) setStatus(sbrStatus *v1alpha1.ServiceBindingRequestStatus, status string) {
-	sbrStatus.BindingStatus = status
-}
-
-// setApplicationObjects set the ApplicationObject status field, and also set the overall status as
-// success, since it was able to bind applications.
-func (r *Reconciler) 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.BindingStatus = BindingSuccess
-	sbrStatus.ApplicationObjects = names
-}
+//// validateServiceBindingRequest check for unsupported settings in SBR.
+//func (r *Reconciler) validateServiceBindingRequest(sbr *v1alpha1.ServiceBindingRequest) error {
+//	// check if application ResourceRef and MatchLabels, one of them is required.
+//	if sbr.Spec.ApplicationSelector.ResourceRef == "" &&
+//		sbr.Spec.ApplicationSelector.LabelSelector == nil {
+//		return fmt.Errorf("both ResourceRef and LabelSelector are not set")
+//	}
+//	return nil
+//}
 
 // getServiceBindingRequest retrieve the SBR object based on namespaced-name.
 func (r *Reconciler) getServiceBindingRequest(
 	namespacedName types.NamespacedName,
 ) (*v1alpha1.ServiceBindingRequest, error) {
-	gvr := v1alpha1.SchemeGroupVersion.WithResource(ServiceBindingRequestResource)
-	resourceClient := r.dynClient.Resource(gvr).Namespace(namespacedName.Namespace)
+	gr := v1alpha1.SchemeGroupVersion.WithResource(ServiceBindingRequestResource)
+	resourceClient := r.dynClient.Resource(gr).Namespace(namespacedName.Namespace)
 	u, err := resourceClient.Get(namespacedName.Name, metav1.GetOptions{})
 	if err != nil {
 		return nil, err
 	}
-	sbr := &v1alpha1.ServiceBindingRequest{}
-	err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sbr)
-	if err != nil {
-		return nil, err
-	}
-	return sbr, nil
-}
-
-// updateStatusServiceBindingRequest update the status field of a ServiceBindingRequest.
-func (r *Reconciler) 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
-	}
-
-	gvr := v1alpha1.SchemeGroupVersion.WithResource(ServiceBindingRequestResource)
-	resourceClient := r.dynClient.Resource(gvr).Namespace(sbr.GetNamespace())
-	u, err = resourceClient.UpdateStatus(u, metav1.UpdateOptions{})
-	if err != nil {
-		return nil, err
-	}
 
+	sbr := &v1alpha1.ServiceBindingRequest{}
 	err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sbr)
 	if err != nil {
 		return nil, err
 	}
-	return sbr, nil
-}
-
-// updateServiceBindingRequest execute update API call on a SBR request. It can return errors from
-// this action.
-func (r *Reconciler) updateServiceBindingRequest(
-	sbr *v1alpha1.ServiceBindingRequest,
-) (*v1alpha1.ServiceBindingRequest, error) {
-	u, err := converter.ToUnstructured(sbr)
-	if err != nil {
-		return nil, err
-	}
-	gvr := v1alpha1.SchemeGroupVersion.WithResource(ServiceBindingRequestResource)
-	resourceClient := r.dynClient.Resource(gvr).Namespace(sbr.GetNamespace())
-	u, err = resourceClient.Update(u, metav1.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 (r *Reconciler) onError(
-	err error,
-	sbr *v1alpha1.ServiceBindingRequest,
-	sbrStatus *v1alpha1.ServiceBindingRequestStatus,
-	objs []*unstructured.Unstructured,
-) (reconcile.Result, error) {
-	// settting overall status to failed
-	r.setStatus(sbrStatus, bindingFail)
-	//
-	if objs != nil {
-		r.setApplicationObjects(sbrStatus, objs)
-	}
-	_, errStatus := r.updateStatusServiceBindingRequest(sbr, sbrStatus)
-	if errStatus != nil {
-		return RequeueError(errStatus)
-	}
-	return RequeueOnNotFound(err, requeueAfter)
-}
-
-// checkSBR checks the Service Binding Request
-func checkSBR(sbr *v1alpha1.ServiceBindingRequest, log *log.Log) error {
-	// Check if application ResourceRef is present
-	if sbr.Spec.ApplicationSelector.ResourceRef == "" {
-		log.Debug("Spec.ApplicationSelector.ResourceRef not found")
-
-		// Check if MatchLabels is present
-		if sbr.Spec.ApplicationSelector.LabelSelector == nil {
-			err := errors.New("NotFoundError")
-			log.Error(err, "Spec.ApplicationSelector.LabelSelector not found")
-			return err
-		}
-	}
-	return nil
-}
-
 // unbind removes the relationship between the given sbr and the manifests the operator has
 // previously modified. This process also deletes any manifests created to support the binding
 // functionality, such as ConfigMaps and Secrets.
-func (r *Reconciler) unbind(
-	logger *log.Log,
-	binder *Binder,
-	secret *Secret,
-	sbr *v1alpha1.ServiceBindingRequest,
-	objectsToAnnotate []*unstructured.Unstructured,
-) (reconcile.Result, error) {
+func (r *Reconciler) unbind(logger *log.Log, bm *ServiceBinder) (reconcile.Result, error) {
 	logger = logger.WithName("unbind")
 
 	// when finalizer is not found anymore, it can be safely removed
-	if !containsStringSlice(sbr.GetFinalizers(), sbrFinalizer) {
+	if !containsStringSlice(bm.SBR.GetFinalizers(), Finalizer) {
 		logger.Info("Resource can be safely deleted!")
 		return Done()
 	}
 
-	logger.Debug("Reading intermediary secret before deletion.")
-	secretObj, _, err := secret.Get()
-	if err != nil {
-		logger.Error(err, "On reading intermediary secret.")
-		return RequeueError(err)
-	}
-
-	// adding secret on list of objects, to remove annotations from secret before deletion
-	objectsToAnnotate = append(objectsToAnnotate, secretObj)
-
-	logger.Info("Cleaning related objects from operator's annotations...")
-	if err = RemoveSBRAnnotations(r.dynClient, objectsToAnnotate); err != nil {
-		logger.Error(err, "On removing annotations from related objects.")
-		return RequeueError(err)
-	}
-
 	logger.Info("Executing unbinding steps...")
-	err = binder.Unbind()
-	if err != nil {
+	if res, err := bm.Unbind(); err != nil {
 		logger.Error(err, "On unbinding application.")
-		return RequeueError(err)
-	}
-
-	logger.Info("Deleting intermediary secret...")
-	if err = secret.Delete(); err != nil {
-		logger.Error(err, "On deleting intermediary secret.")
-		return RequeueError(err)
-	}
-
-	logger.Debug("Removing resource finalizers...")
-	sbr.SetFinalizers(removeStringSlice(sbr.GetFinalizers(), sbrFinalizer))
-	if _, err = r.updateServiceBindingRequest(sbr); err != nil {
-		return NoRequeue(err)
+		return res, err
 	}
 
 	logger.Debug("Deletion done!")
@@ -233,60 +82,16 @@ func (r *Reconciler) unbind(
 // in the common parts of the reconciler, and execute the final binding steps.
 func (r *Reconciler) bind(
 	logger *log.Log,
-	binder *Binder,
-	secret *Secret,
-	retrievedData map[string][]byte,
-	retrievedCache map[string]interface{},
-	sbr *v1alpha1.ServiceBindingRequest,
+	bm *ServiceBinder,
 	sbrStatus *v1alpha1.ServiceBindingRequestStatus,
-	objectsToAnnotate []*unstructured.Unstructured,
-) (reconcile.Result, error) {
+) (
+	reconcile.Result,
+	error,
+) {
 	logger = logger.WithName("bind")
 
-	logger.Info("Saving data on intermediary secret...")
-	secretObj, err := secret.Commit(retrievedData, retrievedCache)
-	if err != nil {
-		logger.Error(err, "On saving secret data..")
-		return r.onError(err, sbr, sbrStatus, nil)
-	}
-
-	// appending intermediary secret in the list of objects to annotate
-	objectsToAnnotate = append(objectsToAnnotate, secretObj)
-	// making sure secret name is part of status
-	r.setSecretName(sbrStatus, secretObj.GetName())
-
 	logger.Info("Binding applications with intermediary secret...")
-	updatedObjects, err := binder.Bind()
-	if err != nil {
-		logger.Error(err, "On binding application.")
-		return r.onError(err, sbr, sbrStatus, updatedObjects)
-	}
-
-	// saving on status the list of objects that have been touched
-	r.setApplicationObjects(sbrStatus, updatedObjects)
-	namespacedName := types.NamespacedName{Namespace: sbr.GetNamespace(), Name: sbr.GetName()}
-
-	// annotating objects related to binding
-	if err = SetSBRAnnotations(r.dynClient, namespacedName, objectsToAnnotate); err != nil {
-		logger.Error(err, "On setting annotations in related objects.")
-		return r.onError(err, sbr, sbrStatus, updatedObjects)
-	}
-
-	// updating status of request instance
-	if sbr, err = r.updateStatusServiceBindingRequest(sbr, sbrStatus); err != nil {
-		return RequeueOnConflict(err)
-	}
-
-	// appending finalizer, should be later removed upon resource deletion
-	if !containsStringSlice(sbr.GetFinalizers(), sbrFinalizer) {
-		sbr.SetFinalizers(append(sbr.GetFinalizers(), sbrFinalizer))
-		if _, err = r.updateServiceBindingRequest(sbr); err != nil {
-			return NoRequeue(err)
-		}
-	}
-
-	logger.Info("All done!")
-	return Done()
+	return bm.Bind()
 }
 
 // Reconcile a ServiceBindingRequest by the following steps:
@@ -300,9 +105,6 @@ func (r *Reconciler) bind(
 // 4. Search applications that are interested to bind with given service, by inspecting labels. The
 //    Deployment (and other kinds) will be updated in "spec" level.
 func (r *Reconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) {
-	ctx := context.TODO()
-	objectsToAnnotate := []*unstructured.Unstructured{}
-
 	logger := reconcilerLog.WithValues(
 		"Request.Namespace", request.Namespace,
 		"Request.Name", request.Name,
@@ -310,66 +112,47 @@ func (r *Reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err
 
 	logger.Info("Reconciling ServiceBindingRequest...")
 
-	// fetch the ServiceBindingRequest instance
+	// fetch and validate namespaced ServiceBindingRequest instance
 	sbr, err := r.getServiceBindingRequest(request.NamespacedName)
 	if err != nil {
 		logger.Error(err, "On retrieving service-binding-request instance.")
 		return DoneOnNotFound(err)
 	}
 
+	// validate namespaced ServiceBindingRequest instance (this check has been disabled until test data has been
+	// adjusted to reflect the validation)
+	//
+	//if err = r.validateServiceBindingRequest(sbr); err != nil {
+	//	logger.Error(err, "On validating service-binding-request instance.")
+	//	return Done()
+	//}
+
 	logger = logger.WithValues("ServiceBindingRequest.Name", sbr.Name)
 	logger.Debug("Found service binding request to inspect")
 
 	// splitting instance from it's status
 	sbrStatus := &sbr.Status
 
-	// check Service Binding Request
-	if err = checkSBR(sbr, logger); err != nil {
-		return RequeueError(err)
-	}
-
-	//
-	// Planing changes
-	//
-
-	logger.Debug("Creating a plan based on OLM and CRD.")
-	planner := NewPlanner(ctx, r.dynClient, sbr)
-	plan, err := planner.Plan()
-	if err != nil {
-		logger.Error(err, "On creating a plan to bind applications.")
-		return r.onError(err, sbr, sbrStatus, nil)
+	options := &ServiceBinderOptions{
+		Client:                 r.client,
+		DynClient:              r.dynClient,
+		DetectBindingResources: sbr.Spec.DetectBindingResources,
+		EnvVarPrefix:           sbr.Spec.EnvVarPrefix,
+		SBR:                    sbr,
+		Logger:                 logger,
 	}
 
-	// storing CR in objects to annotate
-	objectsToAnnotate = append(objectsToAnnotate, plan.CR)
-
-	//
-	// Retrieving data
-	//
-
-	logger.Debug("Retrieving data to create intermediate secret.")
-	retriever := NewRetriever(r.dynClient, plan, sbr.Spec.EnvVarPrefix)
-	retrievedData, retrievedCache, err := retriever.Retrieve()
+	bm, err := BuildServiceBinder(options)
 	if err != nil {
-		logger.Error(err, "On retrieving binding data.")
-		return r.onError(err, sbr, sbrStatus, nil)
+		logger.Error(err, "Creating binding context")
+		return RequeueError(err)
 	}
 
-	// storing objects used in Retriever
-	objectsToAnnotate = append(objectsToAnnotate, retriever.Objects...)
-
-	//
-	// Binding and unbind intermediary secret
-	//
-
-	secret := NewSecret(r.dynClient, plan)
-	binder := NewBinder(ctx, r.client, r.dynClient, sbr, retriever.volumeKeys)
-
 	if sbr.GetDeletionTimestamp() != nil {
 		logger.Info("Resource is marked for deletion...")
-		return r.unbind(logger, binder, secret, sbr, objectsToAnnotate)
+		return r.unbind(logger, bm)
 	}
 
 	logger.Info("Starting the bind of application(s) with backing service...")
-	return r.bind(logger, binder, secret, retrievedData, retrievedCache, sbr, sbrStatus, objectsToAnnotate)
+	return r.bind(logger, bm, sbrStatus)
 }
diff --git a/pkg/controller/servicebindingrequest/retrieve_decoupled.go b/pkg/controller/servicebindingrequest/retrieve_decoupled.go
index 5d68949b..f40c4bcf 100644
--- a/pkg/controller/servicebindingrequest/retrieve_decoupled.go
+++ b/pkg/controller/servicebindingrequest/retrieve_decoupled.go
@@ -42,13 +42,14 @@ func (r *Retriever) ReadBindableResourcesData(
 }
 
 func (r *Retriever) storeInto(cr *unstructured.Unstructured, key string, value []byte) {
-	// r.store(cr, k, v)
-	panic("implement me")
+	r.store(cr, key, value)
 }
 
 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")
+	if err := r.read(u, path, fieldPath, descriptors); err != nil {
+		return err
+	}
+	return nil
 }
 
 // ReadCRDDescriptionData reads data related to given crdDescription
diff --git a/pkg/controller/servicebindingrequest/retriever.go b/pkg/controller/servicebindingrequest/retriever.go
index dafc0876..1fae4d90 100644
--- a/pkg/controller/servicebindingrequest/retriever.go
+++ b/pkg/controller/servicebindingrequest/retriever.go
@@ -20,7 +20,7 @@ type Retriever struct {
 	Objects       []*unstructured.Unstructured // list of objects employed
 	client        dynamic.Interface            // Kubernetes API client
 	plan          *Plan                        // plan instance
-	volumeKeys    []string                     // list of keys found
+	VolumeKeys    []string                     // list of keys found
 	bindingPrefix string                       // prefix for variable names
 	cache         map[string]interface{}       // store visited paths
 }
@@ -51,9 +51,9 @@ func (r *Retriever) getNestedValue(key string, sectionMap interface{}) (string,
 }
 
 // getCRKey retrieve key in section from CR object, part of the "plan" instance.
-func (r *Retriever) getCRKey(section string, key string) (string, interface{}, error) {
-	obj := r.plan.CR.Object
-	objName := r.plan.CR.GetName()
+func (r *Retriever) getCRKey(u *unstructured.Unstructured, section string, key string) (string, interface{}, error) {
+	obj := u.Object
+	objName := u.GetName()
 	log := r.logger.WithValues("CR.Name", objName, "CR.section", section, "CR.key", key)
 	log.Debug("Reading CR attributes...")
 
@@ -76,7 +76,7 @@ func (r *Retriever) getCRKey(section string, key string) (string, interface{}, e
 // read attributes from CR, where place means which top level key name contains the "path" actual
 // value, and parsing x-descriptors in order to either directly read CR data, or read items from
 // a secret.
-func (r *Retriever) read(place, path string, xDescriptors []string) error {
+func (r *Retriever) read(cr *unstructured.Unstructured, place, path string, xDescriptors []string) error {
 	log := r.logger.WithValues(
 		"CR.Section", place,
 		"CRDDescription.Path", path,
@@ -89,13 +89,13 @@ func (r *Retriever) read(place, path string, xDescriptors []string) error {
 
 	// holds the configMap name and items
 	configMaps := make(map[string][]string)
-	pathValue, _, err := r.getCRKey(place, path)
+	pathValue, _, err := r.getCRKey(cr, place, path)
+	if err != nil {
+		return err
+	}
 	for _, xDescriptor := range xDescriptors {
 		log = log.WithValues("CRDDescription.xDescriptor", xDescriptor, "cache", r.cache)
 		log.Debug("Inspecting xDescriptor...")
-		if err != nil {
-			return err
-		}
 
 		if _, ok := r.cache[place].(map[string]interface{}); !ok {
 			r.cache[place] = make(map[string]interface{})
@@ -112,9 +112,9 @@ func (r *Retriever) read(place, path string, xDescriptors []string) error {
 		} else if strings.HasPrefix(xDescriptor, volumeMountSecretPrefix) {
 			secrets[pathValue] = append(secrets[pathValue], r.extractSecretItemName(xDescriptor))
 			r.markVisitedPaths(r.extractSecretItemName(xDescriptor), pathValue, place)
-			r.volumeKeys = append(r.volumeKeys, pathValue)
+			r.VolumeKeys = append(r.VolumeKeys, pathValue)
 		} else if strings.HasPrefix(xDescriptor, attributePrefix) {
-			r.store(path, []byte(pathValue))
+			r.store(cr, path, []byte(pathValue))
 		} else {
 			log.Debug("Defaulting....")
 		}
@@ -122,14 +122,14 @@ func (r *Retriever) read(place, path string, xDescriptors []string) error {
 
 	for name, items := range secrets {
 		// loading secret items all-at-once
-		err := r.readSecret(name, items, place, path)
+		err := r.readSecret(cr, name, items, place, path)
 		if err != nil {
 			return err
 		}
 	}
 	for name, items := range configMaps {
 		// add the function readConfigMap
-		err := r.readConfigMap(name, items, place, path)
+		err := r.readConfigMap(cr, name, items, place, path)
 		if err != nil {
 			return err
 		}
@@ -167,21 +167,17 @@ func (r *Retriever) markVisitedPaths(name, keyPath, fromPath string) {
 
 // readSecret based in secret name and list of items, read a secret from the same namespace informed
 // in plan instance.
-func (r *Retriever) readSecret(
-	name string,
-	items []string,
-	fromPath string,
-	path string) error {
+func (r *Retriever) readSecret(cr *unstructured.Unstructured, name string, items []string, fromPath string, path string) error {
 	log := r.logger.WithValues("Secret.Name", name, "Secret.Items", items)
 	log.Debug("Reading secret items...")
 
 	gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "secrets"}
-	u, err := r.client.Resource(gvr).Namespace(r.plan.Ns).Get(name, metav1.GetOptions{})
+	secret, err := r.client.Resource(gvr).Namespace(r.plan.Ns).Get(name, metav1.GetOptions{})
 	if err != nil {
 		return err
 	}
 
-	data, exists, err := unstructured.NestedMap(u.Object, []string{"data"}...)
+	data, exists, err := unstructured.NestedMap(secret.Object, []string{"data"}...)
 	if err != nil {
 		return err
 	}
@@ -201,21 +197,17 @@ func (r *Retriever) readSecret(
 		// update cache after reading configmap/secret in cache
 		r.cache[fromPath].(map[string]interface{})[path].(map[string]interface{})[k] = string(data)
 		// making sure key name has a secret reference
-		r.store(fmt.Sprintf("configMap_%s", k), data)
-		r.store(fmt.Sprintf("secret_%s", k), data)
+		r.store(cr, fmt.Sprintf("configMap_%s", k), data)
+		r.store(cr, fmt.Sprintf("secret_%s", k), data)
 	}
 
-	r.Objects = append(r.Objects, u)
+	r.Objects = append(r.Objects, secret)
 	return nil
 }
 
 // readConfigMap based in configMap name and list of items, read a configMap from the same namespace informed
 // in plan instance.
-func (r *Retriever) readConfigMap(
-	name string,
-	items []string,
-	fromPath string,
-	path string) error {
+func (r *Retriever) readConfigMap(cr *unstructured.Unstructured, name string, items []string, fromPath string, path string) error {
 	log := r.logger.WithValues("ConfigMap.Name", name, "ConfigMap.Items", items)
 	log.Debug("Reading ConfigMap items...")
 
@@ -244,7 +236,7 @@ func (r *Retriever) readConfigMap(
 		// update cache after reading configmap/secret in cache
 		r.cache[fromPath].(map[string]interface{})[path].(map[string]interface{})[k] = value
 		// making sure key name has a configMap reference
-		r.store(fmt.Sprintf("configMap_%s", k), []byte(value))
+		r.store(cr, fmt.Sprintf("configMap_%s", k), []byte(value))
 	}
 
 	r.Objects = append(r.Objects, u)
@@ -252,13 +244,13 @@ func (r *Retriever) readConfigMap(
 }
 
 // store key and value, formatting key to look like an environment variable.
-func (r *Retriever) store(key string, value []byte) {
+func (r *Retriever) store(u *unstructured.Unstructured, key string, value []byte) {
 	key = strings.ReplaceAll(key, ":", "_")
 	key = strings.ReplaceAll(key, ".", "_")
 	if r.bindingPrefix == "" {
-		key = fmt.Sprintf("%s_%s", r.plan.CR.GetKind(), key)
+		key = fmt.Sprintf("%s_%s", u.GetKind(), key)
 	} else {
-		key = fmt.Sprintf("%s_%s_%s", r.bindingPrefix, r.plan.CR.GetKind(), key)
+		key = fmt.Sprintf("%s_%s_%s", r.bindingPrefix, u.GetKind(), key)
 	}
 	key = strings.ToUpper(key)
 	r.data[key] = value
@@ -267,40 +259,32 @@ func (r *Retriever) store(key string, value []byte) {
 // Retrieve loop and read data pointed by the references in plan instance. Also runs through
 // "bindable resources", gathering extra data. It can return error on retrieving and reading
 // resources.
-func (r *Retriever) Retrieve() (map[string][]byte, map[string]interface{}, error) {
-	var err error
-	r.logger.Info("Looking for spec-descriptors in 'spec'...")
-	for _, specDescriptor := range r.plan.CRDDescription.SpecDescriptors {
-		if err = r.read("spec", specDescriptor.Path, specDescriptor.XDescriptors); err != nil {
-			return nil, nil, err
+func (r *Retriever) Retrieve() (map[string][]byte, error) {
+	// read bindable data from the specified resources
+	if r.plan.SBR.Spec.DetectBindingResources {
+		err := r.ReadBindableResourcesData(&r.plan.SBR, r.plan.GetCRs())
+		if err != nil {
+			return nil, err
 		}
 	}
 
-	r.logger.Info("Looking for status-descriptors in 'status'...")
-	for _, statusDescriptor := range r.plan.CRDDescription.StatusDescriptors {
-		if err = r.read("status", statusDescriptor.Path, statusDescriptor.XDescriptors); err != nil {
-			return nil, nil, err
-		}
-	}
+	// read bindable data from the CRDDescription found by the planner
+	for _, relatedResource := range r.plan.GetRelatedResources() {
 
-	if r.plan.SBR.Spec.DetectBindingResources {
-		r.logger.Info("Detecting extra resources for binding...")
-		b := NewDetectBindableResources(&r.plan.SBR, r.plan.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()
+		err := r.ReadCRDDescriptionData(relatedResource.CR, relatedResource.CRDDescription)
 		if err != nil {
-			return nil, nil, err
-		}
-		for k, v := range vals {
-			r.store(k, []byte(fmt.Sprintf("%v", v)))
+			return nil, err
 		}
 	}
 
-	return r.data, r.cache, nil
+	// gather retriever's read data
+	var err error
+	retrievedData, err := r.Get()
+	if err != nil {
+		return nil, err
+	}
+
+	return retrievedData, nil
 }
 
 // NewRetriever instantiate a new retriever instance.
@@ -311,7 +295,7 @@ func NewRetriever(client dynamic.Interface, plan *Plan, bindingPrefix string) *R
 		Objects:       []*unstructured.Unstructured{},
 		client:        client,
 		plan:          plan,
-		volumeKeys:    []string{},
+		VolumeKeys:    []string{},
 		bindingPrefix: bindingPrefix,
 		cache:         make(map[string]interface{}),
 	}
diff --git a/pkg/controller/servicebindingrequest/sbrcontroller.go b/pkg/controller/servicebindingrequest/sbrcontroller.go
index 5a6f2120..3b4efd66 100644
--- a/pkg/controller/servicebindingrequest/sbrcontroller.go
+++ b/pkg/controller/servicebindingrequest/sbrcontroller.go
@@ -100,7 +100,7 @@ func updateFunc(logger *log.Log) func(updateEvent event.UpdateEvent) bool {
 		if isSecret || isConfigMap {
 			dataFieldsAreEqual, err := compareObjectFields(e.ObjectNew, e.ObjectOld, "data")
 			if err != nil {
-				logger.Error(err, "")
+				logger.Error(err, "error comparing object fields: %s", err.Error())
 				return false
 			}
 
diff --git a/pkg/controller/servicebindingrequest/secret.go b/pkg/controller/servicebindingrequest/secret.go
index 3dffbf76..3b08af83 100644
--- a/pkg/controller/servicebindingrequest/secret.go
+++ b/pkg/controller/servicebindingrequest/secret.go
@@ -20,8 +20,12 @@ type Secret struct {
 
 // customEnvParser parse informed data in order to interpolate with values provided by custom
 // environment component.
-func (s *Secret) customEnvParser(data map[string][]byte, cache map[string]interface{}) (map[string][]byte, error) {
+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)
@@ -80,8 +84,8 @@ func (s *Secret) createOrUpdate(payload map[string][]byte) (*unstructured.Unstru
 
 // Commit will store informed data as a secret, commit it against the API server. It can forward
 // errors from custom environment parser component, or from the API server itself.
-func (s *Secret) Commit(data map[string][]byte, cache map[string]interface{}) (*unstructured.Unstructured, error) {
-	payload, err := s.customEnvParser(data, cache)
+func (s *Secret) Commit(data map[string][]byte) (*unstructured.Unstructured, error) {
+	payload, err := s.customEnvParser(data)
 	if err != nil {
 		return nil, err
 	}


Test patch:
diff --git a/pkg/controller/servicebindingrequest/binding_test.go b/pkg/controller/servicebindingrequest/binding_test.go
index 7a7a0c26..1f936e32 100644
--- a/pkg/controller/servicebindingrequest/binding_test.go
+++ b/pkg/controller/servicebindingrequest/binding_test.go
@@ -18,8 +18,6 @@ import (
 
 // 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 {
@@ -128,20 +126,24 @@ func TestServiceBinder_Bind(t *testing.T) {
 	db1 := f.AddMockedUnstructuredPostgresDatabaseCR("db1")
 	{
 		runtimeStatus := map[string]interface{}{
-			"dbConfigMap": "db1",
+			"dbConfigMap":   "db1",
+			"dbCredentials": "db1",
 		}
 		err := unstructured.SetNestedMap(db1.Object, runtimeStatus, "status")
 		require.NoError(t, err)
 	}
+	f.AddMockedSecret("db1")
 
 	db2 := f.AddMockedUnstructuredPostgresDatabaseCR("db2")
 	{
 		runtimeStatus := map[string]interface{}{
-			"dbConfigMap": "db2",
+			"dbConfigMap":   "db2",
+			"dbCredentials": "db2",
 		}
 		err := unstructured.SetNestedMap(db2.Object, runtimeStatus, "status")
 		require.NoError(t, err)
 	}
+	f.AddMockedSecret("db2")
 
 	// create the ServiceBindingRequest
 	sbrSingleService := &v1alpha1.ServiceBindingRequest{
diff --git a/pkg/controller/servicebindingrequest/bindinginfo_test.go b/pkg/controller/servicebindingrequest/bindinginfo_test.go
index 331d3871..5b7d30ae 100644
--- a/pkg/controller/servicebindingrequest/bindinginfo_test.go
+++ b/pkg/controller/servicebindingrequest/bindinginfo_test.go
@@ -1,6 +1,9 @@
 package servicebindingrequest
 
-import "testing"
+import (
+	"github.com/stretchr/testify/require"
+	"testing"
+)
 
 // TestNewBindingInfo exercises annotation binding information parsing.
 func TestNewBindingInfo(t *testing.T) {
@@ -42,7 +45,7 @@ func TestNewBindingInfo(t *testing.T) {
 				t.Errorf("NewBindingInfo() error = %v, wantErr %v", err, tt.wantErr)
 				return
 			} else if err == nil {
-				requireYamlEqual(t, tt.want, b)
+				require.Equal(t, tt.want, b)
 			}
 		})
 	}
diff --git a/pkg/controller/servicebindingrequest/olm_test.go b/pkg/controller/servicebindingrequest/olm_test.go
index 58232c8b..8997454d 100644
--- a/pkg/controller/servicebindingrequest/olm_test.go
+++ b/pkg/controller/servicebindingrequest/olm_test.go
@@ -5,24 +5,14 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/pmezard/go-difflib/difflib"
 	"github.com/stretchr/testify/require"
-	"gopkg.in/yaml.v2"
-	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
 
-	olmv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/apis/operators/v1alpha1"
-
 	"github.com/redhat-developer/service-binding-operator/test/mocks"
 )
 
-const (
-	dataUserAnnotation     = "servicebindingoperator.redhat.io/status.dbCredentials-data.user"
-	dataPasswordAnnotation = "servicebindingoperator.redhat.io/status.dbCredentials-data.password"
-)
-
 func init() {
 	logf.SetLogger(logf.ZapLogger(true))
 }
@@ -57,9 +47,9 @@ func TestOLMNew(t *testing.T) {
 		require.Len(t, crds, 1)
 	})
 
-	t.Run("SelectCRDDescriptionByGVK", func(t *testing.T) {
+	t.Run("SelectCRDByGVK", func(t *testing.T) {
 		// FIXME: include test for populated CRD
-		crd, err := olm.SelectCRDDescriptionByGVK(schema.GroupVersionKind{
+		crd, err := olm.SelectCRDByGVK(schema.GroupVersionKind{
 			Group:   mocks.CRDName,
 			Version: mocks.CRDVersion,
 			Kind:    mocks.CRDKind,
@@ -85,131 +75,3 @@ func TestOLMNew(t *testing.T) {
 		assertGVKs(t, gvks)
 	})
 }
-
-func TestOLMBuildDescriptorsFromAnnotations(t *testing.T) {
-	type args struct {
-		annotations map[string]string
-	}
-	tests := []struct {
-		name             string
-		args             args
-		specDescriptor   *olmv1alpha1.SpecDescriptor
-		statusDescriptor *olmv1alpha1.StatusDescriptor
-		wantErr          bool
-	}{
-		{
-			name: "secret",
-			args: args{
-				annotations: map[string]string{
-					dataUserAnnotation:     "binding:env:object:secret",
-					dataPasswordAnnotation: "binding:env:object:secret",
-				},
-			},
-			specDescriptor: nil,
-			statusDescriptor: &olmv1alpha1.StatusDescriptor{
-				Path:        "dbCredentials",
-				DisplayName: "",
-				Description: "",
-				XDescriptors: []string{
-					"binding:env:object:secret:data.password",
-					"binding:env:object:secret:data.user",
-				},
-				Value: nil,
-			},
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			specDescriptor, statusDescriptor, err :=
-				buildDescriptorsFromAnnotations(tt.args.annotations)
-			if (err != nil) != tt.wantErr {
-				t.Errorf(
-					"buildDescriptorsFromAnnotations() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-
-			requireYamlEqual(t, specDescriptor, tt.specDescriptor)
-			requireYamlEqual(t, statusDescriptor, tt.statusDescriptor)
-		})
-	}
-}
-
-func TestOLMBuildCRDDescriptionFromCRDAnnotations(t *testing.T) {
-	type args struct {
-		crd *unstructured.Unstructured
-	}
-
-	tests := []struct {
-		name           string
-		args           args
-		crdDescription *olmv1alpha1.CRDDescription
-		wantErr        bool
-	}{
-		{
-			name: "happy path",
-			args: args{
-				crd: &unstructured.Unstructured{
-					Object: map[string]interface{}{
-						"spec": map[string]interface{}{
-							"names": map[string]interface{}{
-								"kind": "CustomResourceDefinitionKind",
-							},
-							"version": "v1alpha1",
-						},
-						"metadata": map[string]interface{}{
-							"annotations": map[string]interface{}{
-								dataUserAnnotation:     "binding:env:object:secret",
-								dataPasswordAnnotation: "binding:env:object:secret",
-							},
-						},
-					},
-				},
-			},
-			crdDescription: &olmv1alpha1.CRDDescription{
-				Kind:    "CustomResourceDefinitionKind",
-				Version: "v1alpha1",
-				StatusDescriptors: []olmv1alpha1.StatusDescriptor{
-					{
-						Path: "dbCredentials",
-						XDescriptors: []string{
-							"binding:env:object:secret:data.password",
-							"binding:env:object:secret:data.user",
-						},
-					},
-				},
-			},
-			wantErr: false,
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			crdDescription, err := buildCRDDescriptionFromCRD(tt.args.crd)
-			if (err != nil) != tt.wantErr {
-				t.Errorf("buildCRDDescriptionFromCRD() error = %v, wantErr %v", err, tt.wantErr)
-				return
-			}
-			requireYamlEqual(t, crdDescription, tt.crdDescription)
-		})
-	}
-}
-
-// requireYamlEqual compares two values and return a message with the different context
-func requireYamlEqual(t *testing.T, a interface{}, b interface{}) {
-	yamlActual, _ := yaml.Marshal(a)
-	yamlExpected, _ := yaml.Marshal(b)
-
-	diff := difflib.UnifiedDiff{
-		A:        difflib.SplitLines(string(yamlExpected)),
-		B:        difflib.SplitLines(string(yamlActual)),
-		FromFile: "Expected",
-		ToFile:   "Actual",
-		Context:  4,
-	}
-
-	message, err := difflib.GetUnifiedDiffString(diff)
-	require.NoError(t, err)
-	if len(message) > 0 {
-		t.Errorf("expected is different from actual:\n%s", message)
-	}
-}
diff --git a/pkg/controller/servicebindingrequest/planner_test.go b/pkg/controller/servicebindingrequest/planner_test.go
index 2d0fc38d..cea2693d 100644
--- a/pkg/controller/servicebindingrequest/planner_test.go
+++ b/pkg/controller/servicebindingrequest/planner_test.go
@@ -4,9 +4,11 @@ import (
 	"context"
 	"testing"
 
-	"github.com/redhat-developer/service-binding-operator/test/mocks"
 	"github.com/stretchr/testify/require"
 	logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
+
+	"github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1"
+	"github.com/redhat-developer/service-binding-operator/test/mocks"
 )
 
 var planner *Planner
@@ -25,6 +27,9 @@ func TestPlannerNew(t *testing.T) {
 	}
 	f := mocks.NewFake(t, ns)
 	sbr := f.AddMockedServiceBindingRequest(name, resourceRef, "", deploymentsGVR, matchLabels)
+	sbr.Spec.BackingServiceSelectors = []v1alpha1.BackingServiceSelector{
+		sbr.Spec.BackingServiceSelector,
+	}
 	f.AddMockedUnstructuredCSV("cluster-service-version")
 	f.AddMockedDatabaseCR(resourceRef)
 	f.AddMockedUnstructuredDatabaseCRD()
@@ -33,7 +38,7 @@ func TestPlannerNew(t *testing.T) {
 	require.NotNil(t, planner)
 
 	t.Run("searchCR", func(t *testing.T) {
-		cr, err := planner.searchCR()
+		cr, err := planner.searchCR(ns, sbr.Spec.BackingServiceSelector)
 
 		require.NoError(t, err)
 		require.NotNil(t, cr)
@@ -44,8 +49,7 @@ func TestPlannerNew(t *testing.T) {
 
 		require.NoError(t, err)
 		require.NotNil(t, plan)
-		require.NotNil(t, plan.CRDDescription)
-		require.NotNil(t, plan.CR)
+		require.NotEmpty(t, plan.RelatedResources)
 		require.Equal(t, ns, plan.Ns)
 		require.Equal(t, name, plan.Name)
 	})
@@ -62,12 +66,13 @@ func TestPlannerAnnotation(t *testing.T) {
 	f := mocks.NewFake(t, ns)
 	sbr := f.AddMockedServiceBindingRequest(name, resourceRef, "", deploymentsGVR, matchLabels)
 	f.AddMockedUnstructuredDatabaseCRD()
+	cr := f.AddMockedDatabaseCR("database")
 
 	planner = NewPlanner(context.TODO(), f.FakeDynClient(), sbr)
 	require.NotNil(t, planner)
 
 	t.Run("searchCRD", func(t *testing.T) {
-		crd, err := planner.searchCRD()
+		crd, err := planner.searchCRD(cr.GetObjectKind().GroupVersionKind())
 
 		require.NoError(t, err)
 		require.NotNil(t, crd)
diff --git a/pkg/controller/servicebindingrequest/reconciler_test.go b/pkg/controller/servicebindingrequest/reconciler_test.go
index dc52b138..e6c81163 100644
--- a/pkg/controller/servicebindingrequest/reconciler_test.go
+++ b/pkg/controller/servicebindingrequest/reconciler_test.go
@@ -46,7 +46,9 @@ func TestReconcilerReconcileError(t *testing.T) {
 		"environment": "reconciler",
 	}
 	f := mocks.NewFake(t, reconcilerNs)
+	f.AddMockedUnstructuredDatabaseCRD()
 	f.AddMockedUnstructuredServiceBindingRequest(reconcilerName, backingServiceResourceRef, "", deploymentsGVR, matchLabels)
+	f.AddMockedUnstructuredPostgresDatabaseCR("test-using-secret")
 
 	fakeClient := f.FakeClient()
 	fakeDynClient := f.FakeDynClient()
@@ -54,15 +56,10 @@ func TestReconcilerReconcileError(t *testing.T) {
 
 	res, err := reconciler.Reconcile(reconcileRequest())
 
-	// FIXME: decide this test's fate
-	// I'm not very sure what this test was about, but in the case the SBR definition contains
-	// references to objects that do not exist, the reconciliation process is supposed to be
-	// successful. Commented below was the original test.
-	//
-	// require.Error(t, err)
-	// require.True(t, res.Requeue)
-
-	require.NoError(t, err)
+	// currently this test passes because annotations present in the Databases CRD being currently
+	// used doesn't have a 'status' field in its definition; once it does and this code is updated (
+	// since the Postgres CRD is being imported to be used in tests) this test will fail.
+	require.Error(t, err)
 	require.True(t, res.Requeue)
 }
 
diff --git a/pkg/controller/servicebindingrequest/retriever_test.go b/pkg/controller/servicebindingrequest/retriever_test.go
index 7c8d73de..a2486046 100644
--- a/pkg/controller/servicebindingrequest/retriever_test.go
+++ b/pkg/controller/servicebindingrequest/retriever_test.go
@@ -25,29 +25,39 @@ func TestRetriever(t *testing.T) {
 	cr, err := mocks.UnstructuredDatabaseCRMock(ns, crName)
 	require.NoError(t, err)
 
-	plan := &Plan{Ns: ns, Name: "retriever", CRDDescription: &crdDescription, CR: cr}
+	plan := &Plan{
+		Ns:   ns,
+		Name: "retriever",
+		RelatedResources: []*RelatedResource{
+			{
+				CRDDescription: &crdDescription,
+				CR:             cr,
+			},
+		},
+	}
 
 	fakeDynClient := f.FakeDynClient()
 
 	retriever = NewRetriever(fakeDynClient, plan, "SERVICE_BINDING")
 	require.NotNil(t, retriever)
 
-	t.Run("retrive", func(t *testing.T) {
-		objs, _, err := retriever.Retrieve()
+	t.Run("retrieve", func(t *testing.T) {
+		_ = retriever.ReadCRDDescriptionData(cr, &crdDescription)
+		objs, err := retriever.Retrieve()
 		require.NoError(t, err)
 		require.NotEmpty(t, retriever.data)
 		require.True(t, len(objs) > 0)
 	})
 
 	t.Run("getCRKey", func(t *testing.T) {
-		imageName, _, err := retriever.getCRKey("spec", "imageName")
+		imageName, _, err := retriever.getCRKey(cr, "spec", "imageName")
 		require.NoError(t, err)
 		require.Equal(t, "postgres", imageName)
 	})
 
 	t.Run("read", func(t *testing.T) {
 		// reading from secret, from status attribute
-		err := retriever.read("status", "dbCredentials", []string{
+		err := retriever.read(cr, "status", "dbCredentials", []string{
 			"binding:env:object:secret:user",
 			"binding:env:object:secret:password",
 		})
@@ -58,7 +68,7 @@ func TestRetriever(t *testing.T) {
 		require.Contains(t, retriever.data, "SERVICE_BINDING_DATABASE_SECRET_PASSWORD")
 
 		// reading from spec attribute
-		err = retriever.read("spec", "image", []string{
+		err = retriever.read(cr, "spec", "image", []string{
 			"binding:env:attribute",
 		})
 		require.NoError(t, err)
@@ -76,7 +86,7 @@ func TestRetriever(t *testing.T) {
 	t.Run("readSecret", func(t *testing.T) {
 		retriever.data = make(map[string][]byte)
 
-		err := retriever.readSecret("db-credentials", []string{"user", "password"}, "spec", "dbConfigMap")
+		err := retriever.readSecret(cr, "db-credentials", []string{"user", "password"}, "spec", "dbConfigMap")
 		require.NoError(t, err)
 
 		require.Contains(t, retriever.data, "SERVICE_BINDING_DATABASE_SECRET_USER")
@@ -84,7 +94,7 @@ func TestRetriever(t *testing.T) {
 	})
 
 	t.Run("store", func(t *testing.T) {
-		retriever.store("test", []byte("test"))
+		retriever.store(cr, "test", []byte("test"))
 		require.Contains(t, retriever.data, "SERVICE_BINDING_DATABASE_TEST")
 		require.Equal(t, []byte("test"), retriever.data["SERVICE_BINDING_DATABASE_TEST"])
 	})
@@ -94,7 +104,7 @@ func TestRetriever(t *testing.T) {
 		require.NotNil(t, retriever)
 		retriever.data = make(map[string][]byte)
 
-		err := retriever.readSecret("db-credentials", []string{"user", "password"}, "spec", "dbConfigMap")
+		err := retriever.readSecret(cr, "db-credentials", []string{"user", "password"}, "spec", "dbConfigMap")
 		require.NoError(t, err)
 
 		require.Contains(t, retriever.data, "DATABASE_SECRET_USER")
@@ -117,7 +127,16 @@ func TestRetrieverWithNestedCRKey(t *testing.T) {
 	cr, err := mocks.UnstructuredNestedDatabaseCRMock(ns, crName)
 	require.NoError(t, err)
 
-	plan := &Plan{Ns: ns, Name: "retriever", CRDDescription: &crdDescription, CR: cr}
+	plan := &Plan{
+		Ns:   ns,
+		Name: "retriever",
+		RelatedResources: []*RelatedResource{
+			{
+				CRDDescription: &crdDescription,
+				CR:             cr,
+			},
+		},
+	}
 
 	fakeDynClient := f.FakeDynClient()
 
@@ -125,7 +144,7 @@ func TestRetrieverWithNestedCRKey(t *testing.T) {
 	require.NotNil(t, retriever)
 
 	t.Run("Second level", func(t *testing.T) {
-		imageName, _, err := retriever.getCRKey("spec", "image.name")
+		imageName, _, err := retriever.getCRKey(cr, "spec", "image.name")
 		require.NoError(t, err)
 		require.Equal(t, "postgres", imageName)
 	})
@@ -133,12 +152,12 @@ func TestRetrieverWithNestedCRKey(t *testing.T) {
 	t.Run("Second level error", func(t *testing.T) {
 		// FIXME: if attribute isn't available in CR we would not throw any error.
 		t.Skip()
-		_, _, err := retriever.getCRKey("spec", "image..name")
+		_, _, err := retriever.getCRKey(cr, "spec", "image..name")
 		require.NotNil(t, err)
 	})
 
 	t.Run("Third level", func(t *testing.T) {
-		something, _, err := retriever.getCRKey("spec", "image.third.something")
+		something, _, err := retriever.getCRKey(cr, "spec", "image.third.something")
 		require.NoError(t, err)
 		require.Equal(t, "somevalue", something)
 	})
@@ -161,7 +180,16 @@ func TestRetrieverWithConfigMap(t *testing.T) {
 	cr, err := mocks.UnstructuredDatabaseConfigMapMock(ns, crName, crName)
 	require.NoError(t, err)
 
-	plan := &Plan{Ns: ns, Name: "retriever", CRDDescription: &crdDescription, CR: cr}
+	plan := &Plan{
+		Ns:   ns,
+		Name: "retriever",
+		RelatedResources: []*RelatedResource{
+			{
+				CRDDescription: &crdDescription,
+				CR:             cr,
+			},
+		},
+	}
 
 	fakeDynClient := f.FakeDynClient()
 
@@ -170,7 +198,7 @@ func TestRetrieverWithConfigMap(t *testing.T) {
 
 	t.Run("read", func(t *testing.T) {
 		// reading from configMap, from status attribute
-		err = retriever.read("spec", "dbConfigMap", []string{
+		err = retriever.read(cr, "spec", "dbConfigMap", []string{
 			"binding:env:object:configmap:user",
 			"binding:env:object:configmap:password",
 		})
@@ -180,6 +208,7 @@ func TestRetrieverWithConfigMap(t *testing.T) {
 		require.Contains(t, retriever.data, "SERVICE_BINDING_DATABASE_CONFIGMAP_USER")
 		require.Contains(t, retriever.data, "SERVICE_BINDING_DATABASE_CONFIGMAP_PASSWORD")
 	})
+
 	t.Run("extractConfigMapItemName", func(t *testing.T) {
 		require.Equal(t, "user", retriever.extractConfigMapItemName(
 			"binding:env:object:configmap:user"))
@@ -188,7 +217,7 @@ func TestRetrieverWithConfigMap(t *testing.T) {
 	t.Run("readConfigMap", func(t *testing.T) {
 		retriever.data = make(map[string][]byte)
 
-		err := retriever.readConfigMap(crName, []string{"user", "password"}, "spec", "dbConfigMap")
+		err := retriever.readConfigMap(cr, crName, []string{"user", "password"}, "spec", "dbConfigMap")
 		require.NoError(t, err)
 
 		require.Contains(t, retriever.data, ("SERVICE_BINDING_DATABASE_CONFIGMAP_USER"))
@@ -204,13 +233,23 @@ func TestCustomEnvParser(t *testing.T) {
 	crName := "db-testing"
 
 	f := mocks.NewFake(t, ns)
+	f.AddMockedUnstructuredCSV("csv")
 	f.AddMockedSecret("db-credentials")
 
 	crdDescription := mocks.CRDDescriptionMock()
 	cr, err := mocks.UnstructuredDatabaseCRMock(ns, crName)
 	require.NoError(t, err)
 
-	plan := &Plan{Ns: ns, Name: "retriever", CRDDescription: &crdDescription, CR: cr}
+	plan := &Plan{
+		Ns:   ns,
+		Name: "retriever",
+		RelatedResources: []*RelatedResource{
+			{
+				CRDDescription: &crdDescription,
+				CR:             cr,
+			},
+		},
+	}
 
 	fakeDynClient := f.FakeDynClient()
 
@@ -218,9 +257,12 @@ func TestCustomEnvParser(t *testing.T) {
 	require.NotNil(t, retriever)
 
 	t.Run("Should detect custom env values", func(t *testing.T) {
-		_, _, err = retriever.Retrieve()
+		_ = retriever.ReadCRDDescriptionData(cr, &crdDescription)
+		_, err = retriever.Retrieve()
 		require.NoError(t, err)
 
+		t.Logf("\nCache %+v", retriever.cache)
+
 		envMap := []corev1.EnvVar{
 			{
 				Name:  "JDBC_CONNECTION_STRING",
diff --git a/pkg/controller/servicebindingrequest/secret_test.go b/pkg/controller/servicebindingrequest/secret_test.go
index cb30534d..017a12a3 100644
--- a/pkg/controller/servicebindingrequest/secret_test.go
+++ b/pkg/controller/servicebindingrequest/secret_test.go
@@ -21,17 +21,19 @@ func TestSecretNew(t *testing.T) {
 	f := mocks.NewFake(t, ns)
 
 	matchLabels := map[string]string{}
-
 	sbr := mocks.ServiceBindingRequestMock(ns, name, "", "", deploymentsGVR, matchLabels)
 
-	plan := &Plan{Ns: ns, Name: name, CRDDescription: nil, SBR: *sbr}
+	plan := &Plan{
+		Ns:   ns,
+		Name: name,
+		SBR:  *sbr,
+	}
 	data := map[string][]byte{"key": []byte("value")}
 
 	s := NewSecret(f.FakeDynClient(), plan)
-	fakeDynClient := f.FakeDynClient()
-	retriever := NewRetriever(fakeDynClient, plan, "SERVICE_BINDING")
+
 	t.Run("customEnvParser", func(t *testing.T) {
-		customData, err := s.customEnvParser(data, retriever.cache)
+		customData, err := s.customEnvParser(data)
 		assert.NoError(t, err)
 		assert.NotNil(t, customData)
 	})
@@ -48,14 +50,15 @@ func TestSecretNew(t *testing.T) {
 	})
 
 	t.Run("Commit", func(t *testing.T) {
-		u, err := s.Commit(data, retriever.cache)
+		u, err := s.Commit(data)
 		assert.NoError(t, err)
 		assertSecretNamespacedName(t, u, ns, name)
 	})
 
 	t.Run("Get", func(t *testing.T) {
-		u, _, err := s.Get()
+		u, found, err := s.Get()
 		assert.NoError(t, err)
+		assert.True(t, found)
 		assertSecretNamespacedName(t, u, ns, name)
 	})
 }