Repository: redhat-developer/service-binding-operator
Pull request #80 (2019-07-19 10:44:02)

Adding ConfigMap support for the controller
Refer  - https://jira.coreos.com/browse/ODC-1242
Adding support for configMap through the following way
```
x-descriptors:
    - urn:alm:descriptor:servicebinding:env:object:configmap:username
    - urn:alm:descriptor:servicebinding:env:object:configmap:password
```

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 them all:

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

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=80). [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 them all:

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

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=80). [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 them all:

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

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=80). [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 them all:

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

[Full PR test history](https://prow.svc.ci.openshift.org/pr-history?org=redhat-developer&repo=service-binding-operator&pr=80). [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 -->
Avni-Sharma: /assign @baijum 
Avni-Sharma: /assign @otaviof 
otaviof: /LGTM
otaviof: /approve
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-developer/service-binding-operator/pull/80#issuecomment-513692087" title="Approved">otaviof</a>*

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).

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

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

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

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":[]} -->

Patch:
diff --git a/pkg/controller/servicebindingrequest/reconciler.go b/pkg/controller/servicebindingrequest/reconciler.go
index 36f8dbae..1434f64d 100644
--- a/pkg/controller/servicebindingrequest/reconciler.go
+++ b/pkg/controller/servicebindingrequest/reconciler.go
@@ -47,11 +47,11 @@ func (r *Reconciler) appendEnvFrom(envList []corev1.EnvFromSource, secret string
 //    kind, and by inspecting "connects-to" label identify the name of service instance;
 // 2. Using OperatorLifecycleManager standards, identifying which items are intersting for binding
 //    by parsing CustomResourceDefinitionDescripton object;
-// 3. Search and read contents identified in previous step, creating a intermediary secret to hold
+// 3. Search and read contents identified in previous step, creating an intermediary secret to hold
 //    data formatted as environment variables key/value.
 // 4. Search applications that are interested to bind with given service, by inspecting labels. The
 //    Deployment (and other kinds) will be updated in PodTeamplate level updating `envFrom` entry
-// 	  to load interdiary secret;
+// 	  to load intermediary secret;
 func (r *Reconciler) Reconcile(request reconcile.Request) (reconcile.Result, error) {
 	ctx := context.TODO()
 	logger := logf.Log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
diff --git a/pkg/controller/servicebindingrequest/retriever.go b/pkg/controller/servicebindingrequest/retriever.go
index 2653dcfd..36780dfc 100644
--- a/pkg/controller/servicebindingrequest/retriever.go
+++ b/pkg/controller/servicebindingrequest/retriever.go
@@ -25,8 +25,10 @@ type Retriever struct {
 }
 
 const (
-	bindingPrefix = "SERVICE_BINDING"
-	secretPrefix  = "urn:alm:descriptor:servicebindingrequest:env:object:secret"
+	bindingPrefix   = "SERVICE_BINDING"
+	basePrefix      = "urn:alm:descriptor:servicebindingrequest:env:object"
+	secretPrefix    = basePrefix + ":secret"
+	configMapPrefix = basePrefix + ":configmap"
 )
 
 // getNestedValue retrieve value from dotted key path
@@ -74,7 +76,8 @@ func (r *Retriever) read(place, path string, xDescriptors []string) error {
 
 	// holds the secret name and items
 	secrets := make(map[string][]string)
-
+	// holds the configMap name and items
+	configMaps := make(map[string][]string)
 	for _, xDescriptor := range xDescriptors {
 		logger = logger.WithValues("CRDDescription.xDescriptor", xDescriptor)
 		logger.Info("Inspecting xDescriptor...")
@@ -85,6 +88,8 @@ func (r *Retriever) read(place, path string, xDescriptors []string) error {
 
 		if strings.HasPrefix(xDescriptor, secretPrefix) {
 			secrets[pathValue] = append(secrets[pathValue], r.extractSecretItemName(xDescriptor))
+		} else if strings.HasPrefix(xDescriptor, configMapPrefix) {
+			configMaps[pathValue] = append(configMaps[pathValue], r.extractConfigMapItemName(xDescriptor))
 		} else {
 			r.store(path, []byte(pathValue))
 		}
@@ -97,7 +102,13 @@ func (r *Retriever) read(place, path string, xDescriptors []string) error {
 			return err
 		}
 	}
-
+	for name, items := range configMaps {
+		// add the function readConfigMap
+		err := r.readConfigMap(name, items)
+		if err != nil {
+			return err
+		}
+	}
 	return nil
 }
 
@@ -107,18 +118,22 @@ func (r *Retriever) extractSecretItemName(xDescriptor string) string {
 	return strings.ReplaceAll(xDescriptor, fmt.Sprintf("%s:", secretPrefix), "")
 }
 
+// extractConfigMapItemName based in x-descriptor entry, removing prefix in order to keep only the
+// configMap item name.
+func (r *Retriever) extractConfigMapItemName(xDescriptor string) string {
+	return strings.ReplaceAll(xDescriptor, fmt.Sprintf("%s:", configMapPrefix), "")
+}
+
 // 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) error {
 	logger := r.logger.WithValues("Secret.Name", name, "Secret.Items", items)
 	logger.Info("Reading secret items...")
-
 	secretObj := corev1.Secret{}
 	err := r.client.Get(r.ctx, types.NamespacedName{Namespace: r.plan.Ns, Name: name}, &secretObj)
 	if err != nil {
 		return err
 	}
-
 	logger.Info("Inspecting secret data...")
 	for key, value := range secretObj.Data {
 		logger.WithValues("Secret.Key.Name", key, "Secret.Key.Length", len(value)).
@@ -126,6 +141,27 @@ func (r *Retriever) readSecret(name string, items []string) error {
 		// making sure key name has a secret reference
 		r.store(fmt.Sprintf("secret_%s", key), value)
 	}
+	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) error {
+	logger := r.logger.WithValues("ConfigMap.Name", name, "ConfigMap.Items", items)
+	logger.Info("Reading ConfigMap items...")
+	configMapObj := corev1.ConfigMap{}
+	err := r.client.Get(r.ctx, types.NamespacedName{Namespace: r.plan.Ns, Name: name}, &configMapObj)
+	if err != nil {
+		return err
+	}
+	logger.Info("Inspecting configMap data...")
+	for key, value := range configMapObj.Data {
+		logger.WithValues("configMap.Key.Name", key, "configMap.Key.Length", len(value)).
+			Info("Inspecting configMap key...")
+		// making sure key name has a configMap reference
+		// string to byte
+		r.store(fmt.Sprintf("configMap_%s", key), []byte(value))
+	}
 
 	return nil
 }
@@ -172,7 +208,7 @@ func (r *Retriever) Retrieve() error {
 		}
 	}
 
-	r.logger.Info("Looking for spec-descriptors in 'status'...")
+	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 err


Test patch:
diff --git a/pkg/controller/servicebindingrequest/retriever_test.go b/pkg/controller/servicebindingrequest/retriever_test.go
index 7643d66d..8fd02bb3 100644
--- a/pkg/controller/servicebindingrequest/retriever_test.go
+++ b/pkg/controller/servicebindingrequest/retriever_test.go
@@ -149,3 +149,62 @@ func TestRetrieverNestedCRDKey(t *testing.T) {
 	})
 
 }
+
+func TestConfigMapRetriever(t *testing.T) {
+	logf.SetLogger(logf.ZapLogger(true))
+	var retriever *Retriever
+
+	ns := "testing"
+	crName := "db-testing"
+
+	crdDescription := mocks.CRDDescriptionConfigMapMock()
+
+	cr := mocks.DatabaseConfigMapMock(ns, crName)
+
+	genericCR, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&cr)
+	require.Nil(t, err)
+
+	plan := &planner.Plan{
+		Ns:             ns,
+		Name:           "retriever",
+		CRDDescription: &crdDescription,
+		CR:             &ustrv1.Unstructured{Object: genericCR},
+	}
+
+	dbConfigMap := mocks.ConfigMapMock(ns, "db-configmap")
+	objs := []runtime.Object{&dbConfigMap}
+	fakeClient := fake.NewFakeClient(objs...)
+
+	retriever = NewRetriever(context.TODO(), fakeClient, plan)
+	require.NotNil(t, retriever)
+
+	t.Run("read", func(t *testing.T) {
+
+		// reading from configMap, from status attribute
+		err = retriever.read("spec", "dbConfigMap", []string{
+			"urn:alm:descriptor:servicebindingrequest:env:object:configmap:user",
+			"urn:alm:descriptor:servicebindingrequest:env:object:configmap:password",
+		})
+		assert.Nil(t, err)
+
+		t.Logf("retriever.data '%#v'", retriever.data)
+		assert.Contains(t, retriever.data, "SERVICE_BINDING_DATABASE_CONFIGMAP_USER")
+		assert.Contains(t, retriever.data, "SERVICE_BINDING_DATABASE_CONFIGMAP_PASSWORD")
+	})
+
+	t.Run("extractConfigMapItemName", func(t *testing.T) {
+		assert.Equal(t, "user", retriever.extractConfigMapItemName(
+			"urn:alm:descriptor:servicebindingrequest:env:object:configmap:user"))
+	})
+
+	t.Run("readConfigMap", func(t *testing.T) {
+		retriever.data = make(map[string][]byte)
+
+		err := retriever.readConfigMap("db-configmap", []string{"user", "password"})
+		assert.Nil(t, err)
+
+		assert.Contains(t, retriever.data, ("SERVICE_BINDING_DATABASE_CONFIGMAP_USER"))
+		assert.Contains(t, retriever.data, ("SERVICE_BINDING_DATABASE_CONFIGMAP_PASSWORD"))
+	})
+
+}
diff --git a/test/mocks/mocks.go b/test/mocks/mocks.go
index f7c8a025..aea5bcc9 100644
--- a/test/mocks/mocks.go
+++ b/test/mocks/mocks.go
@@ -88,6 +88,27 @@ func CRDDescriptionMock() olmv1alpha1.CRDDescription {
 	}
 }
 
+// CRDDescriptionConfigMapMock ...
+func CRDDescriptionConfigMapMock() olmv1alpha1.CRDDescription {
+	return olmv1alpha1.CRDDescription{
+		Name:        fmt.Sprintf("%s.%s", CRDKind, CRDName),
+		DisplayName: CRDKind,
+		Description: "mock-crd-description",
+		Kind:        CRDKind,
+		Version:     CRDVersion,
+		SpecDescriptors: []olmv1alpha1.SpecDescriptor{{
+			DisplayName: "DB ConfigMap",
+			Description: "Database ConfigMap",
+			Path:        "dbConfigMap",
+			XDescriptors: []string{
+				"urn:alm:descriptor:io.kubernetes:ConfigMap",
+				"urn:alm:descriptor:servicebindingrequest:env:object:configmap:user",
+				"urn:alm:descriptor:servicebindingrequest:env:object:configmap:password",
+			},
+		}},
+	}
+}
+
 // DatabaseCRMock based on PostgreSQL operator, returning a instantiated object.
 func DatabaseCRMock(ns, name string) pgv1alpha1.Database {
 	return pgv1alpha1.Database{
@@ -132,6 +153,20 @@ func SecretMock(ns, name string) corev1.Secret {
 	}
 }
 
+// ConfigMapMock ...
+func ConfigMapMock(ns, name string) corev1.ConfigMap {
+	return corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: ns,
+			Name:      name,
+		},
+		Data: map[string]string{
+			"user":     "user",
+			"password": "password",
+		},
+	}
+}
+
 // ServiceBindingRequestMock return a binding-request mock of informed name and match labels.
 func ServiceBindingRequestMock(
 	ns, name, resourceRef string, matchLabels map[string]string,
@@ -232,3 +267,33 @@ func NestedDatabaseCRMock(ns, name string) NestedDatabase {
 		},
 	}
 }
+
+// ConfigMapDatabaseSpec ...
+type ConfigMapDatabaseSpec struct {
+	DBConfigMap string `json:"dbConfigMap"`
+}
+
+// ConfigMapDatabase ...
+type ConfigMapDatabase struct {
+	metav1.TypeMeta   `json:",inline"`
+	metav1.ObjectMeta `json:"metadata,omitempty"`
+
+	Spec ConfigMapDatabaseSpec `json:"spec,omitempty"`
+}
+
+// DatabaseConfigMapMock ...
+func DatabaseConfigMapMock(ns, name string) ConfigMapDatabase {
+	return ConfigMapDatabase{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       CRDKind,
+			APIVersion: fmt.Sprintf("%s/%s", CRDName, CRDVersion),
+		},
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: ns,
+			Name:      name,
+		},
+		Spec: ConfigMapDatabaseSpec{
+			DBConfigMap: "db-configmap",
+		},
+	}
+}