Repository: redhat-developer/service-binding-operator
Pull request #521 (2020-06-26 10:23:13)

make custom_env_var can reference the whole secret/configmap without using a slash
This PR is to fix the issue we reported in https://github.com/redhat-developer/service-binding-operator/issues/475#issuecomment-646114247.
@isutton 
Could you take a look at this PR? It need to be rebased after your PR https://github.com/redhat-developer/service-binding-operator/pull/515 is merged.

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 **baijum**
You can assign the PR to them by writing `/assign @baijum` in a comment when ready.

The full list of commands accepted by this bot can be found [here](?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":["baijum"]} -->
openshift-ci-robot: @qibobo: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
openshift-ci-robot: Hi @qibobo. Thanks for your PR.

I'm waiting for a [redhat-developer](https://github.com/orgs/redhat-developer/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/redhat-developer/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=redhat-developer%2Fservice-binding-operator).

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

isutton: @qibobo I've rebased https://github.com/redhat-developer/service-binding-operator/pull/515 on upstream's master.
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 **baijum**
You can assign the PR to them by writing `/assign @baijum` 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":["baijum"]} -->
qibobo: @isutton I have rebase the code with master and verified the feature works.
Test steps:
1 Add annotations:
```
metadata:
  annotations:
    servicebindingoperator.redhat.io/status.secretName: binding:env:object:secret
    servicebindingoperator.redhat.io/status.secretName-url: binding:env:object:secret

```

2 create knative service as application
```
cat <<EOF | kubectl apply -f -
apiVersion: serving.knative.dev/v1
kind: Service 
metadata:
  name: knative-vcap
spec:
  template:
    metadata:
      annotations:
        autoscaling.knative.dev/minScale: "1"
    spec:
      containers:
        - image: docker.io/qibobo/go4autoscaler:latest
          ports:
          - containerPort: 8080
EOF

```

3 Create SBR
```
cat <<EOF | kubectl apply -f -
apiVersion: apps.openshift.io/v1alpha1
kind: ServiceBindingRequest
metadata:
  name: binding-request-vcap
spec:
  applicationSelector:
    resourceRef: knative-vcap
    group: serving.knative.dev 
    version: v1 
    resource: services 
  backingServiceSelectors:
    - group: ibmcloud.ibm.com
      version: v1alpha1
      kind: Binding
      resourceRef: thetranslator5
      envVarPrefix: serviceprefix
  customEnvVar:
    - name: VCAP_SERVICES
      value: '{"language-translator":[{"credentials":{{json .v1alpha1.ibmcloud_ibm_com.Binding.thetranslator5.status.secretName}},"name":"thetranslator5","plan":"standard"}]}'
    - name: TRANSLATOR_NAME
      value: "thetranslator5"
    - name: URL
      value: '{{.v1alpha1.ibmcloud_ibm_com.Binding.thetranslator5.status.secretName.url}}'
EOF
```
4 Check env injection:

```
SERVICEPREFIX_SECRET_IAM_ROLE_CRN=crn:v1:bluemix:public:iam::::serviceRole:Manager
SERVICEPREFIX_SECRET_APIKEY=XXXXXX
SERVICEPREFIX_SECRET_IAM_SERVICEID_CRN=crn:v1:bluemix:public:iam-identity::a/ef6a34810cbcd892507d3ebe01e3d95a::serviceid:ServiceId-471aa3de-daac-483a-9364-42fe0854a353
SERVICEPREFIX_SECRET_IAM_APIKEY_DESCRIPTION=Auto-generated for key 6142fe4b-5a7d-4951-8c64-b70433b1ccd3
SERVICEPREFIX_SECRET_URL=https://api.us-south.language-translator.watson.cloud.ibm.com/instances/b6cff0d7-dcfb-467d-9f75-7a044b6b15fd
SERVICEPREFIX_SECRET_IAM_APIKEY_NAME=thetranslator5

VCAP_SERVICES={"language-translator":[{"credentials":{"apikey":"XXXXXX","iam_apikey_description":"Auto-generated for key 6142fe4b-5a7d-4951-8c64-b70433b1ccd3","iam_apikey_name":"thetranslator5","iam_role_crn":"crn:v1:bluemix:public:iam::::serviceRole:Manager","iam_serviceid_crn":"crn:v1:bluemix:public:iam-identity::a/ef6a34810cbcd892507d3ebe01e3d95a::serviceid:ServiceId-471aa3de-daac-483a-9364-42fe0854a353","url":"https://api.us-south.language-translator.watson.cloud.ibm.com/instances/b6cff0d7-dcfb-467d-9f75-7a044b6b15fd"},"name":"thetranslator5","plan":"standard"}]}
URL=https://api.us-south.language-translator.watson.cloud.ibm.com/instances/b6cff0d7-dcfb-467d-9f75-7a044b6b15fd

```


Patch:
diff --git a/.gitignore b/.gitignore
index 7bdf68ac..d36a3508 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,9 @@ tmp/
 # gocache
 out/
 
+# Acceptance test cache
+.kube/
+
 # Created by https://www.gitignore.io/api/go,vim,emacs,visualstudiocode
 ### Emacs ###
 # -*- mode: gitignore; -*-
@@ -90,5 +93,4 @@ tags
 .idea
 ### Logs
 **/*.log
-venv
 **/*/__pycache__
diff --git a/Makefile b/Makefile
index 84be7a76..6cc6edaa 100644
--- a/Makefile
+++ b/Makefile
@@ -146,6 +146,7 @@ OLM_CATALOG_DIR ?= $(shell echo ${PWD})/deploy/olm-catalog
 CRDS_DIR ?= $(shell echo ${PWD})/deploy/crds
 LOGS_DIR ?= $(OUTPUT_DIR)/logs
 GOLANGCI_LINT_BIN=$(OUTPUT_DIR)/golangci-lint
+PYTHON_VENV_DIR=$(OUTPUT_DIR)/venv3
 
 # -- Variables for uploading code coverage reports to Codecov.io --
 # This default path is set by the OpenShift CI
@@ -158,6 +159,8 @@ PULL_NUMBER := $(shell echo $$CLONEREFS_OPTIONS | jq '.refs[0].pulls[0].number')
 
 # -- Variables for acceptance tests
 TEST_ACCEPTANCE_START_SBO ?= local
+TEST_ACCEPTANCE_OUTPUT_DIR ?= $(OUTPUT_DIR)/acceptance-tests
+TEST_ACCEPTANCE_ARTIFACTS ?= /tmp/artifacts
 
 ## -- Static code analysis (lint) targets --
 
@@ -169,8 +172,8 @@ YAML_FILES := $(shell find . -path ./vendor -prune -o -type f -regex ".*y[a]ml"
 .PHONY: lint-yaml
 ## runs yamllint on all yaml files
 lint-yaml: ${YAML_FILES}
-	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install yamllint==1.23.0
-	$(Q)$(OUTPUT_DIR)/venv3/bin/yamllint -c .yamllint $(YAML_FILES)
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install yamllint==1.23.0
+	$(Q)$(PYTHON_VENV_DIR)/bin/yamllint -c .yamllint $(YAML_FILES)
 
 .PHONY: lint-go-code
 ## Checks the code with golangci-lint
@@ -184,15 +187,15 @@ $(GOLANGCI_LINT_BIN):
 
 ## -- Check the python code
 .PHONY: lint-python-code
-lint-python-code:
-	$(Q)./hack/check-python/lint-python-code.sh
+lint-python-code: setup-venv
+	$(Q)PYTHON_VENV_DIR=$(PYTHON_VENV_DIR) ./hack/check-python/lint-python-code.sh
 
 .PHONY: setup-venv
 ## Setup virtual environment
 setup-venv:
-	$(Q)python3 -m venv $(OUTPUT_DIR)/venv3
-	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install --upgrade setuptools
-	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install --upgrade pip
+	$(Q)python3 -m venv $(PYTHON_VENV_DIR)
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install --upgrade setuptools
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install --upgrade pip
 
 ## -- Test targets --
 
@@ -277,14 +280,16 @@ test-unit-with-coverage:
 
 .PHONY: test-acceptance-setup
 ## Setup the environment for the acceptance tests
+test-acceptance-setup: setup-venv e2e-setup set-test-namespace deploy-rbac deploy-crds
 ifeq ($(TEST_ACCEPTANCE_START_SBO), local)
 test-acceptance-setup:
 	$(Q)echo "Starting local SBO instance"
-	$(eval TEST_ACCEPTANCE_SBO_STARTED := $(shell OPERATOR_NAMESPACE="$(TEST_NAMESPACE)" ZAP_FLAGS="$(ZAP_FLAGS)" ./hack/deploy-sbo-local.sh))
+	$(eval TEST_ACCEPTANCE_SBO_STARTED := $(shell OPERATOR_NAMESPACE="$(TEST_NAMESPACE)" ZAP_FLAGS="$(ZAP_FLAGS)" OUTPUT="$(TEST_ACCEPTANCE_OUTPUT_DIR)" ./hack/deploy-sbo-local.sh))
 else ifeq ($(TEST_ACCEPTANCE_START_SBO), operator-hub)
 test-acceptance-setup:
 	$(eval TEST_ACCEPTANCE_SBO_STARTED := $(shell ./hack/deploy-sbo-operator-hub.sh))
 endif
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install -q -r test/acceptance/features/requirements.txt
 
 .PHONY: set-test-namespace
 set-test-namespace: get-test-namespace
@@ -292,14 +297,21 @@ set-test-namespace: get-test-namespace
 
 .PHONY: test-acceptance
 ## Runs acceptance tests
-test-acceptance: e2e-setup set-test-namespace deploy-clean deploy-rbac deploy-crds test-acceptance-setup
+test-acceptance: test-acceptance-setup
 	$(Q)echo "Running acceptance tests"
 	$(Q)TEST_ACCEPTANCE_START_SBO=$(TEST_ACCEPTANCE_START_SBO) \
 		TEST_ACCEPTANCE_SBO_STARTED=$(TEST_ACCEPTANCE_SBO_STARTED) \
 		TEST_NAMESPACE=$(TEST_NAMESPACE) \
-		behave -v --no-capture --no-capture-stderr --tags="~@disabled" test/acceptance/features
+		$(PYTHON_VENV_DIR)/bin/behave --junit --junit-directory $(TEST_ACCEPTANCE_OUTPUT_DIR) $(V_FLAG) --no-capture --no-capture-stderr --tags="~@disabled" test/acceptance/features
 	$(Q)kill $(TEST_ACCEPTANCE_SBO_STARTED)
 
+.PHONY: test-acceptance-artifacts
+## Collect artifacts from acceptance tests to be archived in CI
+test-acceptance-artifacts:
+	$(Q)echo "Gathering acceptance tests artifacts"
+	$(Q)mkdir -p $(TEST_ACCEPTANCE_ARTIFACTS) \
+	    && cp -rvf $(TEST_ACCEPTANCE_OUTPUT_DIR) $(TEST_ACCEPTANCE_ARTIFACTS)/
+
 .PHONY: test
 ## Test: Runs unit and integration (e2e) tests
 test: test-unit test-e2e
@@ -336,11 +348,11 @@ build-image:
 generate-k8s:
 	$(Q)GOCACHE=$(GOCACHE) operator-sdk generate k8s
 
-build-openapi-gen-cli: $(OUTPUT_DIR)/openapi-gen
+$(OUTPUT_DIR)/openapi-gen:
 	$(Q)GOCACHE=$(GOCACHE) go build -o $(OUTPUT_DIR)/openapi-gen k8s.io/kube-openapi/cmd/openapi-gen
 
 ## Generate-OpenAPI: after modifying _types, generate OpenAPI scaffolding.
-generate-openapi: build-openapi-gen-cli
+generate-openapi: $(OUTPUT_DIR)/openapi-gen
 	# Build the latest openapi-gen from source
 	$(Q)GOCACHE=$(GOCACHE) $(OUTPUT_DIR)/openapi-gen --logtostderr=true -o "" -i $(GO_PACKAGE_PATH)/pkg/apis/apps/v1alpha1 -O zz_generated.openapi -p ./pkg/apis/apps/v1alpha1 -h ./hack/boilerplate.go.txt -r "-"
 
@@ -447,15 +459,9 @@ else
 		-Z > codecov-upload.log
 endif
 
-## -- Target for maintaining consistent crd copies --
 
-.PHONY: consistent-crds-manifests-upstream
-## Copy crd from deploy/crds to manifests-upstream/
-consistent-crds-manifests-upstream:
-	$(Q)cd ./manifests-upstream/${OPERATOR_VERSION}/ && ln -srf ../../deploy/crds/apps_v1alpha1_servicebindingrequest_crd.yaml \
-	servicebindingrequests.apps.openshift.io.crd.yaml
+## -- Bundle validation, push and release targets
 
-## -- Target for merge to master dev release --
 .PHONY: merge-to-master-release
 ## Make a dev release on every merge to master
 merge-to-master-release:
@@ -465,7 +471,6 @@ merge-to-master-release:
 	docker push "$(OPERATOR_IMAGE_REF)"
 
 
-## -- Target for pushing manifest bundle to service-binding-operator-manifest repo --
 .PHONY: push-to-manifest-repo
 ## Push manifest bundle to service-binding-operator-manifest repo
 push-to-manifest-repo:
@@ -483,35 +488,32 @@ push-to-manifest-repo:
 	sed -i -e 's,ICON_BASE64_DATA,$(shell base64 --wrap=0 ./assets/icon/red-hat-logo.svg),g' $(MANIFESTS_TMP)/${BUNDLE_VERSION}/*.clusterserviceversion.yaml
 	sed -i -e 's,ICON_MEDIA_TYPE,image/svg+xml,g' $(MANIFESTS_TMP)/${BUNDLE_VERSION}/*.clusterserviceversion.yaml
 
-## -- Target for preparing manifest bundle to quay application --
 .PHONY: prepare-bundle-to-quay
 ## Prepare manifest bundle to quay application
 prepare-bundle-to-quay:
-	$(Q)python3.7 -m venv $(OUTPUT_DIR)/venv3
-	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install --upgrade setuptools
-	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install --upgrade pip
-	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install operator-courier==2.1.2
-	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier --version
-	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier verify $(MANIFESTS_TMP)
+	$(Q)python3.7 -m venv $(PYTHON_VENV_DIR)
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install --upgrade setuptools
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install --upgrade pip
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install operator-courier==2.1.2
+	$(Q)$(PYTHON_VENV_DIR)/bin/operator-courier --version
+	$(Q)$(PYTHON_VENV_DIR)/bin/operator-courier verify $(MANIFESTS_TMP)
 	rm -rf deploy/olm-catalog/$(GO_PACKAGE_REPO_NAME)/$(BUNDLE_VERSION)
 
-## -- Target to push bundle to quay
+
 .PHONY: push-bundle-to-quay
 ## Push manifest bundle to quay application
 push-bundle-to-quay:
-	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier verify $(SBR_MANIFESTS)
-	$(Q)$(OUTPUT_DIR)/venv3/bin/operator-courier push $(SBR_MANIFESTS) redhat-developer service-binding-operator $(BUNDLE_VERSION) "$(QUAY_BUNDLE_TOKEN)"
+	$(Q)$(PYTHON_VENV_DIR)/bin/operator-courier verify $(SBR_MANIFESTS)
+	$(Q)$(PYTHON_VENV_DIR)/bin/operator-courier push $(SBR_MANIFESTS) redhat-developer service-binding-operator $(BUNDLE_VERSION) "$(QUAY_BUNDLE_TOKEN)"
+
 
-## -- Target for validating the operator --
 .PHONY: dev-release
 ## validating the operator by installing new quay releases
 dev-release:
 	BUNDLE_VERSION=$(BUNDLE_VERSION) ./hack/dev-release.sh
 
-## -- Target to validate release --
 .PHONY: validate-release
 ## validate the operator by installing the releases
 validate-release: setup-venv
-	$(Q)$(OUTPUT_DIR)/venv3/bin/pip install yq==2.10.0
+	$(Q)$(PYTHON_VENV_DIR)/bin/pip install yq==2.10.0
 	BUNDLE_VERSION=$(BASE_BUNDLE_VERSION) CHANNEL="alpha" ./hack/validate-release.sh
-
diff --git a/examples/java_postgresql_customvar/README.md b/examples/java_postgresql_customvar/README.md
index 2284c5cd..43f3d4b6 100644
--- a/examples/java_postgresql_customvar/README.md
+++ b/examples/java_postgresql_customvar/README.md
@@ -199,13 +199,14 @@ spec:
     version: v1alpha1
     kind: Database
     resourceRef: db-demo
+    id: postgresDB
   customEnvVar:
     - name: JDBC_URL
-      value: 'jdbc:postgresql://{{ .status.dbConnectionIP }}:{{ .status.dbConnectionPort }}/{{ .status.dbName }}'
+      value: 'jdbc:postgresql://{{ postgresDB.status.dbConnectionIP }}:{{ .postgresDB.status.dbConnectionPort }}/{{ postgresDB.status.dbName }}'
     - name: DB_USER
-      value: '{{ index .status.dbConfigMap "db.username" }}'
+      value: '{{ postgresDB.status.dbConfigMap.username }}'
     - name: DB_PASSWORD
-      value: '{{ index .status.dbConfigMap "db.password" }}'
+      value: '{{ postgresDB.status.dbConfigMap.password }}'
 EOS
 ```
 
diff --git a/examples/java_postgresql_customvar/service-binding-request.yaml b/examples/java_postgresql_customvar/service-binding-request.yaml
index 0a434e5b..27457bd3 100644
--- a/examples/java_postgresql_customvar/service-binding-request.yaml
+++ b/examples/java_postgresql_customvar/service-binding-request.yaml
@@ -15,10 +15,11 @@ spec:
     version: v1alpha1
     kind: Database
     resourceRef: db-demo
+    id: postgresDB
   customEnvVar:
     - name: JDBC_URL
-      value: 'jdbc:postgresql://{{ .status.dbConnectionIP }}:{{ .status.dbConnectionPort }}/{{ .status.dbName }}'   # yamllint disable-line rule:line-length
+      value: 'jdbc:postgresql://{{ postgresDB.status.dbConnectionIP }}:{{ .postgresDB.status.dbConnectionPort }}/{{ postgresDB.status.dbName }}'
     - name: DB_USER
-      value: '{{ index .status.dbConfigMap "db.username" }}'
+      value: '{{ postgresDB.status.dbConfigMap.username }}'
     - name: DB_PASSWORD
-      value: '{{ index .status.dbConfigMap "db.password" }}'
+      value: '{{ postgresDB.status.dbConfigMap.password }}'
diff --git a/examples/knative_postgresql_customvar/README.md b/examples/knative_postgresql_customvar/README.md
index d88e4d02..7e0253a4 100644
--- a/examples/knative_postgresql_customvar/README.md
+++ b/examples/knative_postgresql_customvar/README.md
@@ -205,11 +205,11 @@ spec:
     resourceRef: db-demo
   customEnvVar:
     - name: JDBC_URL
-      value: 'jdbc:postgresql://{{ .status.dbConnectionIP }}:{{ .status.dbConnectionPort }}/{{ .status.dbName }}'
+      value: 'jdbc:postgresql://{{ postgresDB.status.dbConnectionIP }}:{{ .postgresDB.status.dbConnectionPort }}/{{ postgresDB.status.dbName }}'
     - name: DB_USER
-      value: '{{ index .status.dbConfigMap "db.username" }}'
+      value: '{{ postgresDB.status.dbConfigMap.username }}'
     - name: DB_PASSWORD
-      value: '{{ index .status.dbConfigMap "db.password" }}'
+      value: '{{ postgresDB.status.dbConfigMap.password }}'
 EOS
 ```
 
diff --git a/examples/knative_postgresql_customvar/service-binding-request.yaml b/examples/knative_postgresql_customvar/service-binding-request.yaml
index d2806931..6b27e6f6 100644
--- a/examples/knative_postgresql_customvar/service-binding-request.yaml
+++ b/examples/knative_postgresql_customvar/service-binding-request.yaml
@@ -15,10 +15,11 @@ spec:
     version: v1alpha1
     kind: Database
     resourceRef: db-demo
+    id: postgresDB
   customEnvVar:
     - name: JDBC_URL
-      value: 'jdbc:postgresql://{{ .status.dbConnectionIP }}:{{ .status.dbConnectionPort }}/{{ .status.dbName }}'   # yamllint disable-line rule:line-length
+      value: 'jdbc:postgresql://{{ postgresDB.status.dbConnectionIP }}:{{ .postgresDB.status.dbConnectionPort }}/{{ postgresDB.status.dbName }}'
     - name: DB_USER
-      value: '{{ index .status.dbConfigMap "db.username" }}'
+      value: '{{ postgresDB.status.dbConfigMap.username }}'
     - name: DB_PASSWORD
-      value: '{{ index .status.dbConfigMap "db.password" }}'
+      value: '{{ postgresDB.status.dbConfigMap.password }}'
diff --git a/hack/check-python/check-PEP8-style.sh b/hack/check-python/check-PEP8-style.sh
index 359ceb9d..3f01e04e 100755
--- a/hack/check-python/check-PEP8-style.sh
+++ b/hack/check-python/check-PEP8-style.sh
@@ -13,12 +13,12 @@ echo
 # checks for the whole directories
 for directory in $directories
 do
-    files=$(find "$directory" -path "$directory/venv" -prune -o -name '*.py' -print)
+    files=$(find "$directory" -path "$PYTHON_VENV_DIR" -prune -o -name '*.py' -print)
 
     for source in $files
     do
         echo "$source"
-        pycodestyle "$source"
+        $PYTHON_VENV_DIR/bin/pycodestyle "$source"
         if [ $? -eq 0 ]
         then
             echo "    Pass"
diff --git a/hack/check-python/check-python-docstyle.sh b/hack/check-python/check-python-docstyle.sh
index 28bb2e64..d526aec2 100755
--- a/hack/check-python/check-python-docstyle.sh
+++ b/hack/check-python/check-python-docstyle.sh
@@ -7,7 +7,7 @@ function check_files() {
     for source in $1
     do
         echo "$source"
-        pydocstyle --count "$source"
+        $PYTHON_VENV_DIR/bin/pydocstyle --count "$source"
         if [ $? -eq 0 ]
         then
             echo "    Pass"
@@ -36,7 +36,7 @@ echo
 # checks for the whole directories
 for directory in $directories
 do
-    files=$(find "$directory" -path "$directory/venv" -prune -o -name '*.py' -print)
+    files=$(find "$directory" -path "$PYTHON_VENV_DIR" -prune -o -name '*.py' -print)
 
     check_files "$files"
 done
diff --git a/hack/check-python/detect-common-errors.sh b/hack/check-python/detect-common-errors.sh
index 13b0499e..0e8ee26a 100755
--- a/hack/check-python/detect-common-errors.sh
+++ b/hack/check-python/detect-common-errors.sh
@@ -7,7 +7,7 @@ function check_files() {
     for source in $1
     do
         echo "$source"
-        pyflakes "$source"
+        $PYTHON_VENV_DIR/bin/pyflakes "$source"
         if [ $? -eq 0 ]
         then
             echo "    Pass"
@@ -36,7 +36,8 @@ echo
 # checks for the whole directories
 for directory in $directories
 do
-    files=$(find "$directory" -path "$directory/venv" -prune -o -name '*.py' -print)
+    pwd
+    files=$(find "$directory" -path "$PYTHON_VENV_DIR" -prune -o -name '*.py' -print)
 
     check_files "$files"
 done
diff --git a/hack/check-python/detect-dead-code.sh b/hack/check-python/detect-dead-code.sh
index 2c8c0e14..2e409a7c 100755
--- a/hack/check-python/detect-dead-code.sh
+++ b/hack/check-python/detect-dead-code.sh
@@ -7,7 +7,7 @@ function check_files() {
     for source in $1
     do
         echo "$source"
-        vulture --min-confidence 90 "$source"
+        $PYTHON_VENV_DIR/bin/vulture --min-confidence 90 "$source"
         if [ $? -eq 0 ]
         then
             echo "    Pass"
@@ -36,7 +36,7 @@ echo
 # checks for the whole directories
 for directory in $directories
 do
-    files=$(find "$directory" -path "$directory/venv" -prune -o -name '*.py' -print)
+    files=$(find "$directory" -prune -o -name '*.py' -print)
 
     check_files "$files"
 done
diff --git a/hack/check-python/measure-cyclomatic-complexity.sh b/hack/check-python/measure-cyclomatic-complexity.sh
index 4523bf5b..6d74cb09 100755
--- a/hack/check-python/measure-cyclomatic-complexity.sh
+++ b/hack/check-python/measure-cyclomatic-complexity.sh
@@ -7,5 +7,5 @@
 SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
 
 pushd "${SCRIPT_DIR}/.."
-radon cc -s -a -i venv .
+$PYTHON_VENV_DIR/bin/radon cc -s -a -i "$PYTHON_VENV_DIR" .
 popd
diff --git a/hack/check-python/measure-maintainability-index.sh b/hack/check-python/measure-maintainability-index.sh
index 2abb7e69..3d787601 100755
--- a/hack/check-python/measure-maintainability-index.sh
+++ b/hack/check-python/measure-maintainability-index.sh
@@ -7,5 +7,5 @@
 SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
 
 pushd "${SCRIPT_DIR}/.."
-radon mi -s -i venv .
+$PYTHON_VENV_DIR/bin/radon mi -s -i "$PYTHON_VENV_DIR" .
 popd
diff --git a/hack/check-python/prepare-env.sh b/hack/check-python/prepare-env.sh
index ee4c68b0..26c229d4 100644
--- a/hack/check-python/prepare-env.sh
+++ b/hack/check-python/prepare-env.sh
@@ -1,10 +1,11 @@
-
-directories=${directories:-"./test/acceptance/features"}
+directories=${directories:-"test/acceptance/features"}
 pass=0
 fail=0
 
+export PYTHON_VENV_DIR=${PYTHON_VENV_DIR:-venv}
+
 function prepare_venv() {
-    python3 -m venv venv && source venv/bin/activate
+    python3 -m venv "$PYTHON_VENV_DIR" && source "$PYTHON_VENV_DIR/bin/activate"
     for req in $(find . -name 'requirements.txt'); do
         python3 "$(which pip3)" install -q -r $req;
     done
diff --git a/hack/deploy-sbo-local.sh b/hack/deploy-sbo-local.sh
index edbabd03..a4fa54bc 100755
--- a/hack/deploy-sbo-local.sh
+++ b/hack/deploy-sbo-local.sh
@@ -3,9 +3,22 @@
 OPERATOR_NAMESPACE=${OPERATOR_NAMESPACE:-}
 ZAP_FLAGS=${ZAP_FLAGS:-}
 
-SBO_LOCAL_LOG=out/sbo-local.log
+OUTPUT="${OUTPUT:-out/acceptance-tests}"
+mkdir -p "$OUTPUT"
 
-killall operator-sdk service-binding-operator-local
+SBO_LOCAL_LOG="$OUTPUT/sbo-local.log"
+
+_killall(){
+    which killall &> /dev/null
+    if [ $? -eq 0 ]; then
+        killall $1
+    else
+        for i in "$(ps -l | grep $1)"; do if [ -n "$i" ]; then kill $(echo "$i" | sed -e 's,\s\+,#,g' | cut -d "#" -f4); fi; done
+    fi
+}
+
+_killall operator-sdk
+_killall service-binding-operator-local
 
 operator-sdk --verbose run --local --namespace="$OPERATOR_NAMESPACE" --operator-flags "$ZAP_FLAGS" > $SBO_LOCAL_LOG 2>&1 &
 
diff --git a/pkg/controller/servicebindingrequest/annotations/attribute.go b/pkg/controller/servicebindingrequest/annotations/attribute.go
index 2d1a27ee..40a63612 100644
--- a/pkg/controller/servicebindingrequest/annotations/attribute.go
+++ b/pkg/controller/servicebindingrequest/annotations/attribute.go
@@ -47,6 +47,10 @@ func newAttributeHandler(
 	if len(bi.ResourceReferencePath) > 0 {
 		outputPath = bi.ResourceReferencePath
 	}
+	inputPath := bi.SourcePath
+	if inputPath == "" {
+		inputPath = bi.ResourceReferencePath
+	}
 
 	// the current implementation removes "status." and "spec." from fields exported through
 	// annotations.
@@ -57,7 +61,7 @@ func newAttributeHandler(
 	}
 
 	return &attributeHandler{
-		inputPath:  bi.SourcePath,
+		inputPath:  inputPath,
 		outputPath: outputPath,
 		resource:   resource,
 	}
diff --git a/pkg/controller/servicebindingrequest/annotations/bindinginfo.go b/pkg/controller/servicebindingrequest/annotations/bindinginfo.go
index 0617dfc8..970db4e2 100644
--- a/pkg/controller/servicebindingrequest/annotations/bindinginfo.go
+++ b/pkg/controller/servicebindingrequest/annotations/bindinginfo.go
@@ -38,21 +38,24 @@ func NewBindingInfo(name string, value string) (*bindingInfo, error) {
 	if len(cleanName) == 0 {
 		return nil, ErrEmptyAnnotationName
 	}
-
 	parts := strings.SplitN(cleanName, "-", 2)
 
 	resourceReferencePath := parts[0]
-	sourcePath := parts[0]
-
+	// sourcePath := parts[0]
+	sourcePath := ""
+	descriptor := ""
 	// the annotation is a reference to another manifest
 	if len(parts) == 2 {
 		sourcePath = parts[1]
+		descriptor = strings.Join([]string{value, sourcePath}, ":")
+	} else {
+		descriptor = strings.Join([]string{value, resourceReferencePath}, ":")
 	}
 
 	return &bindingInfo{
 		ResourceReferencePath: resourceReferencePath,
 		SourcePath:            sourcePath,
-		Descriptor:            strings.Join([]string{value, sourcePath}, ":"),
+		Descriptor:            descriptor,
 		Value:                 value,
 	}, nil
 }
diff --git a/pkg/controller/servicebindingrequest/annotations/resource.go b/pkg/controller/servicebindingrequest/annotations/resource.go
index ec309570..3438a6c6 100644
--- a/pkg/controller/servicebindingrequest/annotations/resource.go
+++ b/pkg/controller/servicebindingrequest/annotations/resource.go
@@ -85,7 +85,7 @@ func discoverBindingType(val string) (bindingType, error) {
 // the resulting slice.
 func getInputPathFields(bi *bindingInfo, inputPathPrefix *string) []string {
 	inputPathFields := []string{}
-	if bi.ResourceReferencePath != bi.SourcePath {
+	if bi.SourcePath != "" {
 		inputPathFields = append(inputPathFields, bi.SourcePath)
 	}
 	if inputPathPrefix != nil && len(*inputPathPrefix) > 0 {
@@ -105,7 +105,6 @@ func (h *resourceHandler) Handle() (result, error) {
 	if err != nil {
 		return result{}, err
 	}
-
 	inputPathFields := getInputPathFields(h.bindingInfo, h.inputPathRoot)
 	val, ok, err := unstructured.NestedFieldCopy(resource.Object, inputPathFields...)
 	if !ok {
@@ -121,8 +120,6 @@ func (h *resourceHandler) Handle() (result, error) {
 		return result{}, err
 	}
 
-	outputPathParts := []string{strings.ToLower(gvk.Kind)}
-
 	if mapVal, ok := val.(map[string]interface{}); ok {
 		tmpVal := make(map[string]interface{})
 		for k, v := range mapVal {
@@ -138,7 +135,6 @@ func (h *resourceHandler) Handle() (result, error) {
 		if err != nil {
 			return result{}, err
 		}
-		outputPathParts = append(outputPathParts, nested.NewPath(h.bindingInfo.SourcePath).GetParts()...)
 	}
 
 	typ, err := discoverBindingType(h.bindingInfo.Value)
@@ -147,18 +143,27 @@ func (h *resourceHandler) Handle() (result, error) {
 	}
 
 	// prefix the output path with the kind of the resource.
-	outputPath := strings.Join(outputPathParts, ".")
+	outputPath := ""
+	rawDataPath := ""
 
-	rawDataPath := strings.Join([]string{
-		h.bindingInfo.ResourceReferencePath,
-		h.bindingInfo.SourcePath,
-	}, ".")
+	if h.bindingInfo.SourcePath == "" {
+		outputPath = strings.ToLower(gvk.Kind)
+		rawDataPath = h.bindingInfo.ResourceReferencePath
+	} else {
+		outputPath = strings.Join([]string{
+			strings.ToLower(gvk.Kind),
+			h.bindingInfo.SourcePath,
+		}, ".")
+		rawDataPath = strings.Join([]string{
+			h.bindingInfo.ResourceReferencePath,
+			h.bindingInfo.SourcePath,
+		}, ".")
+	}
 
 	return result{
-		Data: nested.ComposeValue(val, nested.NewPath(outputPath)),
-		Type: typ,
-		Path: outputPath,
-
+		Data:    nested.ComposeValue(val, nested.NewPath(outputPath)),
+		Type:    typ,
+		Path:    outputPath,
 		RawData: nested.ComposeValue(val, nested.NewPath(rawDataPath)),
 	}, nil
 }
@@ -189,10 +194,6 @@ func NewResourceHandler(
 		return nil, invalidArgumentErr("bi")
 	}
 
-	if len(bi.SourcePath) == 0 {
-		return nil, invalidArgumentErr("bi.Path")
-	}
-
 	if len(bi.ResourceReferencePath) == 0 {
 		return nil, invalidArgumentErr("bi.ResourceReferencePath")
 	}
diff --git a/pkg/controller/servicebindingrequest/nested/path.go b/pkg/controller/servicebindingrequest/nested/path.go
index ba9d2501..3bdb7ca2 100644
--- a/pkg/controller/servicebindingrequest/nested/path.go
+++ b/pkg/controller/servicebindingrequest/nested/path.go
@@ -129,6 +129,9 @@ func (p path) clean() path {
 
 // NewPath creates a new path with the given string in the format 'a.b.c'.
 func NewPath(s string) path {
+	if s == "" {
+		return path{}
+	}
 	parts := strings.Split(s, ".")
 	return newPathWithParts(parts)
 }


Test patch:
diff --git a/pkg/controller/servicebindingrequest/annotations/bindinginfo_test.go b/pkg/controller/servicebindingrequest/annotations/bindinginfo_test.go
index 98dbe1ee..05f88213 100644
--- a/pkg/controller/servicebindingrequest/annotations/bindinginfo_test.go
+++ b/pkg/controller/servicebindingrequest/annotations/bindinginfo_test.go
@@ -39,7 +39,7 @@ func TestNewBindingInfo(t *testing.T) {
 			want: &bindingInfo{
 				Descriptor:            "binding:status.connectionString",
 				ResourceReferencePath: "status.connectionString",
-				SourcePath:            "status.connectionString",
+				SourcePath:            "",
 				Value:                 "binding",
 			},
 			name:    "{path} annotation",
diff --git a/pkg/controller/servicebindingrequest/annotations/configmap_test.go b/pkg/controller/servicebindingrequest/annotations/configmap_test.go
index d717f805..34b82f33 100644
--- a/pkg/controller/servicebindingrequest/annotations/configmap_test.go
+++ b/pkg/controller/servicebindingrequest/annotations/configmap_test.go
@@ -16,11 +16,12 @@ import (
 
 func TestConfigMapHandler(t *testing.T) {
 	type args struct {
-		name      string
-		value     string
-		service   map[string]interface{}
-		resources []runtime.Object
-		expected  map[string]interface{}
+		name            string
+		value           string
+		service         map[string]interface{}
+		resources       []runtime.Object
+		expectedData    map[string]interface{}
+		expectedRawData map[string]interface{}
 	}
 
 	assertHandler := func(args args) func(*testing.T) {
@@ -45,7 +46,8 @@ func TestConfigMapHandler(t *testing.T) {
 			got, err := handler.Handle()
 			require.NoError(t, err)
 			require.NotNil(t, got)
-			require.Equal(t, args.expected, got.Data)
+			require.Equal(t, args.expectedData, got.Data)
+			require.Equal(t, args.expectedRawData, got.RawData)
 		}
 	}
 
@@ -75,11 +77,18 @@ func TestConfigMapHandler(t *testing.T) {
 				},
 			},
 		},
-		expected: map[string]interface{}{
+		expectedData: map[string]interface{}{
 			"configmap": map[string]interface{}{
 				"password": "hunter2",
 			},
 		},
+		expectedRawData: map[string]interface{}{
+			"status": map[string]interface{}{
+				"dbCredentials": map[string]interface{}{
+					"password": "hunter2",
+				},
+			},
+		},
 	}))
 
 	t.Run("configmap/map", assertHandler(args{
@@ -108,11 +117,19 @@ func TestConfigMapHandler(t *testing.T) {
 				},
 			},
 		},
-		expected: map[string]interface{}{
+		expectedData: map[string]interface{}{
 			"configmap": map[string]interface{}{
 				"username": "AzureDiamond",
 				"password": "hunter2",
 			},
 		},
+		expectedRawData: map[string]interface{}{
+			"status": map[string]interface{}{
+				"dbCredentials": map[string]interface{}{
+					"username": "AzureDiamond",
+					"password": "hunter2",
+				},
+			},
+		},
 	}))
 }
diff --git a/pkg/controller/servicebindingrequest/annotations/secret_test.go b/pkg/controller/servicebindingrequest/annotations/secret_test.go
index f8b5d596..e6b0d2bd 100644
--- a/pkg/controller/servicebindingrequest/annotations/secret_test.go
+++ b/pkg/controller/servicebindingrequest/annotations/secret_test.go
@@ -16,11 +16,12 @@ import (
 
 func TestSecretHandler(t *testing.T) {
 	type args struct {
-		name      string
-		value     string
-		service   map[string]interface{}
-		resources []runtime.Object
-		expected  map[string]interface{}
+		name            string
+		value           string
+		service         map[string]interface{}
+		resources       []runtime.Object
+		expectedData    map[string]interface{}
+		expectedRawData map[string]interface{}
 	}
 
 	assertHandler := func(args args) func(*testing.T) {
@@ -45,7 +46,8 @@ func TestSecretHandler(t *testing.T) {
 			got, err := handler.Handle()
 			require.NoError(t, err)
 			require.NotNil(t, got)
-			require.Equal(t, args.expected, got.Data)
+			require.Equal(t, args.expectedData, got.Data)
+			require.Equal(t, args.expectedRawData, got.RawData)
 		}
 	}
 
@@ -74,11 +76,18 @@ func TestSecretHandler(t *testing.T) {
 				},
 			},
 		},
-		expected: map[string]interface{}{
+		expectedData: map[string]interface{}{
 			"secret": map[string]interface{}{
 				"password": "hunter2",
 			},
 		},
+		expectedRawData: map[string]interface{}{
+			"status": map[string]interface{}{
+				"dbCredentials": map[string]interface{}{
+					"password": "hunter2",
+				},
+			},
+		},
 	}))
 
 	t.Run("secret/map", assertHandler(args{
@@ -107,11 +116,19 @@ func TestSecretHandler(t *testing.T) {
 				},
 			},
 		},
-		expected: map[string]interface{}{
+		expectedData: map[string]interface{}{
 			"secret": map[string]interface{}{
 				"username": "AzureDiamond",
 				"password": "hunter2",
 			},
 		},
+		expectedRawData: map[string]interface{}{
+			"status": map[string]interface{}{
+				"dbCredentials": map[string]interface{}{
+					"username": "AzureDiamond",
+					"password": "hunter2",
+				},
+			},
+		},
 	}))
 }
diff --git a/test/acceptance/features/steps/dboperator.py b/test/acceptance/features/steps/dboperator.py
index 01187d3e..05910b97 100644
--- a/test/acceptance/features/steps/dboperator.py
+++ b/test/acceptance/features/steps/dboperator.py
@@ -14,9 +14,9 @@ class DbOperator():
 
     name = ""
     namespace = ""
-    operator_source_name = "db-operators"
-    operator_registry_namespace = "pmacik"
-    operator_registry_channel = "stable"
+    operator_catalog_source_name = "sample-db-operators"
+    operator_catalog_image = "quay.io/redhat-developer/sample-db-operators-olm:v1"
+    operator_catalog_channel = "stable"
     package_name = "db-operators"
 
     def __init__(self, name="postgresql-operator", namespace="openshift-operators"):
@@ -35,16 +35,19 @@ class DbOperator():
         else:
             return False
 
-    def install_operator_source(self):
-        install_src_output = self.openshift.create_operator_source(self.operator_source_name, self.operator_registry_namespace)
-        if not re.search(r'.*operatorsource.operators.coreos.com/%s\s(unchanged|created)' % self.operator_source_name, install_src_output):
-            print("Failed to create {} operator source".format(self.operator_source_name))
+    def install_catalog_source(self):
+        install_src_output = self.openshift.create_catalog_source(self.operator_catalog_source_name, self.operator_catalog_image)
+        if re.search(r'.*catalogsource.operators.coreos.com/%s\s(unchanged|created)' % self.operator_catalog_source_name, install_src_output) is None:
+            print("Failed to create {} catalog source".format(self.operator_catalog_source_name))
             return False
-        return self.openshift.wait_for_package_manifest(self.package_name, self.operator_source_name, self.operator_registry_channel)
+        return self.openshift.wait_for_package_manifest(self.package_name, self.operator_catalog_source_name, self.operator_catalog_channel)
 
     def install_operator_subscription(self):
-        install_sub_output = self.openshift.create_operator_subscription(self.package_name, self.operator_source_name, self.operator_registry_channel)
-        return re.search(r'.*subscription.operators.coreos.com/%s\s(unchanged|created)' % self.operator_source_name, install_sub_output)
+        install_sub_output = self.openshift.create_operator_subscription(self.package_name, self.operator_catalog_source_name, self.operator_catalog_channel)
+        if re.search(r'.*subscription.operators.coreos.com/%s\s(unchanged|created)' % self.package_name, install_sub_output) is None:
+            print("Failed to create {} operator subscription".format(self.package_name))
+            return False
+        return True
 
     def get_package_manifest(self):
         cmd = 'oc get packagemanifest %s -o "jsonpath={.metadata.name}"' % self.pkgManifest
diff --git a/test/acceptance/features/steps/openshift.py b/test/acceptance/features/steps/openshift.py
index 10bafe93..13322b68 100644
--- a/test/acceptance/features/steps/openshift.py
+++ b/test/acceptance/features/steps/openshift.py
@@ -9,17 +9,19 @@ nodejs_app = "https://github.com/pmacik/nodejs-rest-http-crud"
 class Openshift(object):
     def __init__(self):
         self.cmd = Command()
-        self.operator_source_yaml_template = '''
----
-apiVersion: operators.coreos.com/v1
-kind: OperatorSource
+        self.catalog_source_yaml_template = '''
+apiVersion: operators.coreos.com/v1alpha1
+kind: CatalogSource
 metadata:
     name: {name}
     namespace: openshift-marketplace
 spec:
-    type: appregistry
-    endpoint: https://quay.io/cnr
-    registryNamespace: {registry_namespace}
+    sourceType: grpc
+    image: {catalog_image}
+    displayName: {name} OLM registry
+    updateStrategy:
+        registryPoll:
+            interval: 30m
 '''
         self.operator_subscription_yaml_template = '''
 ---
@@ -102,9 +104,9 @@ spec:
         (output, exit_code) = self.cmd.run("oc apply -f -", yaml)
         return output
 
-    def create_operator_source(self, name, registry_namespace):
-        operator_source = self.operator_source_yaml_template.format(name=name, registry_namespace=registry_namespace)
-        return self.oc_apply(operator_source)
+    def create_catalog_source(self, name, catalog_image):
+        catalog_source = self.catalog_source_yaml_template.format(name=name, catalog_image=catalog_image)
+        return self.oc_apply(catalog_source)
 
     def get_current_csv(self, package_name, catalog, channel):
         cmd = f'oc get packagemanifests -o json | jq -r \'.items[] \
diff --git a/test/acceptance/features/steps/steps.py b/test/acceptance/features/steps/steps.py
index ec881eb2..ed29f87c 100644
--- a/test/acceptance/features/steps/steps.py
+++ b/test/acceptance/features/steps/steps.py
@@ -82,7 +82,7 @@ def given_db_operator_is_installed(context):
     db_operator = DbOperator()
     if not db_operator.is_running():
         print("DB operator is not installed, installing...")
-        db_operator.install_operator_source() | should.be_truthy.desc("DB operator source installed")
+        db_operator.install_catalog_source() | should.be_truthy.desc("DB catalog source installed")
         db_operator.install_operator_subscription() | should.be_truthy.desc("DB operator subscription installed")
         db_operator.is_running(wait=True) | should.be_truthy.desc("DB operator installed")
     print("PostgresSQL DB operator is running!!!")