Repository: redhat-developer/service-binding-operator
Pull request #1181 (2022-07-06 23:43:24)

Update sample apps
# Changes

Updates the sample apps to the latest upstreams.  Please `make push-app-multiarch` in each directory to propogate new builds.

/kind documentation

# Submitter Checklist

As the author of this PR, please check off the items in this checklist:

- [x] [Docs](https://github.com/redhat-developer/service-binding-operator/blob/master/CONTRIBUTING.md#docs) 
  included if any changes are user facing
- [x] [Tests](https://github.com/redhat-developer/service-binding-operator/blob/master/CONTRIBUTING.md#tests)
  included if any functionality added or changed. For bugfixes please include tests that can catch regressions
- [ ] All acceptance test scenarios included in the PR which verifies a bugfix or a requested feature reported by a non-member are tagged with `@external-feedback` tag.
- [ ] Follows the [commit message standard](https://github.com/redhat-developer/service-binding-operator/blob/master/CONTRIBUTING.md#commits)



openshift-ci[bot]: [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** after the PR has been reviewed.
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"]} -->
openshift-ci[bot]: Hi @yselkowitz. 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>

codecov[bot]: # [Codecov](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1181?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) Report
> Merging [#1181](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1181?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) (8f484b1) into [master](https://codecov.io/gh/redhat-developer/service-binding-operator/commit/3afddcc850e405aca3856e96560cd44fae05179b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer) (3afddcc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1181/graphs/tree.svg?width=650&height=150&src=pr&token=CICBeVWlrC&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer)](https://codecov.io/gh/redhat-developer/service-binding-operator/pull/1181?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=redhat-developer)

```diff
@@           Coverage Diff           @@
##           master    #1181   +/-   ##
=======================================
  Coverage   58.69%   58.69%           
=======================================
  Files          33       33           
  Lines        2782     2782           
=======================================
  Hits         1633     1633           
  Misses        988      988           
  Partials      161      161           
```



------

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


Patch:
diff --git a/.github/workflows/pr-checks.yaml b/.github/workflows/pr-checks.yaml
index 6cee54e9..1aa34483 100644
--- a/.github/workflows/pr-checks.yaml
+++ b/.github/workflows/pr-checks.yaml
@@ -67,7 +67,7 @@ jobs:
     runs-on: ubuntu-20.04
 
     env:
-      EXTRA_BEHAVE_ARGS: "--tags=~@knative --tags=~@openshift --tags=~@examples --tags=~@supported-operator --tags=~@disable-github-actions"
+      EXTRA_BEHAVE_ARGS: "--tags=~@knative --tags=~@openshift --tags=~@examples --tags=~@supported-operator --tags=~@optional-annotations --tags=~@disable-github-actions"
       TEST_RUN: Acceptance_tests_Kubernetes_with_OLM
 
     steps:
@@ -142,6 +142,87 @@ jobs:
           name: kubernetes-with-olm-test-results
           path: ${{ env.TEST_RESULTS }}
 
+  acceptance-optional-annotations:
+    name: Optional Annotations Acceptance Tests with Kubernetes and using OLM
+    runs-on: ubuntu-20.04
+
+    env:
+      EXTRA_BEHAVE_ARGS: "--tags=@optional-annotations --tags=~@disable-github-actions"
+      TEST_RUN: Optional_Annotations_Acceptance_tests_Kubernetes_with_OLM
+
+    steps:
+      - name: Checkout Git Repository
+        uses: actions/checkout@v2
+
+      - name: Check if acceptance tests can be skipped
+        id: check-skip-acceptance
+        uses: ./.github/actions/check-skip-acceptance-tests
+
+      - name: Set up Python
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: "3.7"
+          architecture: "x64"
+
+      - name: Setup-cli
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        uses: ./.github/actions/setup-cli
+        with:
+          start-minikube: true
+
+      - name: Wait for push
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        uses: lewagon/wait-on-check-action@1b1630e169116b58a4b933d5ad7effc46d3d312d
+        with:
+          ref: ${{ github.event.pull_request.head.sha }}
+          check-name: "Push operator images (PR)"
+          repo-token: ${{ secrets.GITHUB_TOKEN }}
+          wait-interval: 60
+
+      - name: Extract image references
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        uses: marcofaggian/action-download-multiple-artifacts@v3.0.8
+        with:
+          names: operator-refs-${{github.event.pull_request.number}}-${{github.event.pull_request.head.sha}}
+
+      - name: Acceptance tests
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        timeout-minutes: 60
+        run: |
+          source ./operator.refs
+          export CATSRC_NAME=sbo-pr-checks
+
+          make SKIP_REGISTRY_LOGIN=true -o registry-login test-acceptance-with-bundle
+
+      - name: Collect Kube resources
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        continue-on-error: true
+        uses: ./.github/actions/collect-kube-resources
+        with:
+          operator-namespace: operators
+          olm-namespace: olm
+          test-namespace-file: out/test-namespace
+          output-path: ${{env.TEST_RESULTS}}
+
+      - name: Setup Testspace
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        uses: testspace-com/setup-testspace@v1
+        with:
+          domain: ${{ github.repository_owner }}
+
+      - name: Publish tests results to Testspace
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        run: |
+          testspace [${{ env.TEST_RUN }}]${{ env.TEST_RESULTS }}/TEST*.xml
+
+      - uses: actions/upload-artifact@v2
+        if: ${{ steps.check-skip-acceptance.outputs.can_skip != 'true' }}
+        with:
+          name: supported-operators-kubernetes
+          path: ${{ env.TEST_RESULTS }}
+
+
   acceptance-supported-operators:
     name: Supported Operators Acceptance Tests with Kubernetes and using OLM
     runs-on: ubuntu-20.04
diff --git a/docs/userguide/modules/exposing-binding-data/pages/adding-annotation.adoc b/docs/userguide/modules/exposing-binding-data/pages/adding-annotation.adoc
index 0565f6d9..9bcb44c2 100644
--- a/docs/userguide/modules/exposing-binding-data/pages/adding-annotation.adoc
+++ b/docs/userguide/modules/exposing-binding-data/pages/adding-annotation.adoc
@@ -294,3 +294,33 @@ The following example shows how the previous values of a collection in annotatio
 ----
 
 The previous example helps you project all those values with key such as `prefix_0`, `prefix_1`, and so on. The default prefix is the name of the resource `kind:`.
+
+[#setting-annotations-mapping-to-be-optional] 
+== Setting annotations mapping to be optional
+
+You can have optional fields in the annotations. For example, a link to the credentials might not be there if the service endpoint does not require authentication. In such cases, a field might not exist in the target path of the annotations. As a result, the {servicebinding-title} generates an error, by default. As a service provider, to indicate whether you require annotations mapping, you can set a value to the `optional` flag in your annotations while enabling services.
+
+{servicebinding-title} provides annotations mapping only if the target path is available. When the target path is not available the {servicebinding-title} skips providing the annotations mapping. 
+
+.Procedure
+
+. To enable {servicebinding-title} consider a new field in the annotations, provide the mapping by setting the `optional` flag value to `true`:
+
+.Example
+[source,yaml]
+----
+apiVersion: apps.example.org/v1beta1
+kind: Database
+metadata:
+  name: my-db
+  namespace: my-petclinic
+  annotations:
+    service.binding/username: path={.spec.name},optional=true
+    ...
+----
+
+[NOTE]
+====
+* If you set the `optional` flag value to `false` and when the {servicebinding-title} is unable to find the target path, the Operator fails the annotations mapping.
+* If there is no value set for the `optional` flag, the {servicebinding-title} considers the value as `false`, by default and fails the annotations mapping. 
+====
diff --git a/pkg/binding/annotationmapper.go b/pkg/binding/annotationmapper.go
index 20f839e7..96bad34e 100644
--- a/pkg/binding/annotationmapper.go
+++ b/pkg/binding/annotationmapper.go
@@ -26,6 +26,7 @@ type modelKey string
 
 const (
 	pathModelKey                    modelKey = "path"
+	optionalKey                     modelKey = "optional"
 	objectTypeModelKey              modelKey = "objectType"
 	sourceKeyModelKey               modelKey = "sourceKey"
 	sourceValueModelKey             modelKey = "sourceValue"
@@ -99,7 +100,8 @@ func (m *annotationBackedDefinitionBuilder) Build() (Definition, error) {
 			outputName: outputName,
 			value:      mod.value,
 			definition: definition{
-				path: mod.path,
+				path:     mod.path,
+				optional: mod.optional,
 			},
 		}, nil
 
@@ -109,7 +111,8 @@ func (m *annotationBackedDefinitionBuilder) Build() (Definition, error) {
 			objectType:            mod.objectType,
 			outputName:            outputName,
 			definition: definition{
-				path: mod.path,
+				path:     mod.path,
+				optional: mod.optional,
 			},
 			sourceKey: mod.sourceKey,
 		}, nil
@@ -120,7 +123,8 @@ func (m *annotationBackedDefinitionBuilder) Build() (Definition, error) {
 			objectType:            mod.objectType,
 			outputName:            outputName,
 			definition: definition{
-				path: mod.path,
+				path:     mod.path,
+				optional: mod.optional,
 			},
 			sourceValue: mod.sourceValue,
 		}, nil
@@ -129,7 +133,8 @@ func (m *annotationBackedDefinitionBuilder) Build() (Definition, error) {
 		return &stringOfMapDefinition{
 			outputName: outputName,
 			definition: definition{
-				path: mod.path,
+				path:     mod.path,
+				optional: mod.optional,
 			},
 		}, nil
 
@@ -137,7 +142,8 @@ func (m *annotationBackedDefinitionBuilder) Build() (Definition, error) {
 		return &sliceOfMapsFromPathDefinition{
 			outputName: outputName,
 			definition: definition{
-				path: mod.path,
+				path:     mod.path,
+				optional: mod.optional,
 			},
 			sourceKey:   mod.sourceKey,
 			sourceValue: mod.sourceValue,
@@ -147,7 +153,8 @@ func (m *annotationBackedDefinitionBuilder) Build() (Definition, error) {
 		return &sliceOfStringsFromPathDefinition{
 			outputName: outputName,
 			definition: definition{
-				path: mod.path,
+				path:     mod.path,
+				optional: mod.optional,
 			},
 			sourceValue: mod.sourceValue,
 		}, nil
diff --git a/pkg/binding/definition.go b/pkg/binding/definition.go
index ba4679a2..6031ccc4 100644
--- a/pkg/binding/definition.go
+++ b/pkg/binding/definition.go
@@ -4,6 +4,7 @@ import (
 	"encoding/base64"
 	"errors"
 	"fmt"
+
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
@@ -39,6 +40,7 @@ const (
 type Definition interface {
 	Apply(u *unstructured.Unstructured) (Value, error)
 	GetPath() string
+	NonExistingOptional(Value) bool
 }
 
 type DefinitionBuilder interface {
@@ -46,7 +48,8 @@ type DefinitionBuilder interface {
 }
 
 type definition struct {
-	path string
+	path     string
+	optional bool
 }
 
 func (d *definition) GetPath() string {
@@ -61,6 +64,13 @@ type stringDefinition struct {
 
 var _ Definition = (*stringDefinition)(nil)
 
+func (d *stringDefinition) NonExistingOptional(v Value) bool {
+	if d.optional && v.Get() == nil {
+		return true
+	}
+	return false
+}
+
 func (d *stringDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
 	if d.outputName == "" {
 		return nil, fmt.Errorf("cannot use generic service.binding annotation for string elements, need to specify binding key like service.binding/foo")
@@ -74,6 +84,9 @@ func (d *stringDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
 	}
 	val, err := getValuesByJSONPath(u.Object, d.path)
 	if err != nil {
+		if d.optional {
+			return &value{}, nil
+		}
 		return nil, err
 	}
 
@@ -98,6 +111,14 @@ type stringFromDataFieldDefinition struct {
 
 var _ Definition = (*stringFromDataFieldDefinition)(nil)
 
+func (d *stringFromDataFieldDefinition) NonExistingOptional(v Value) bool {
+	if d.optional && v.Get() == nil {
+		return true
+	}
+	return false
+
+}
+
 func (d *stringFromDataFieldDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
 	if d.secretConfigMapReader == nil {
 		return nil, errors.New("kubeClient required for this functionality")
@@ -105,6 +126,9 @@ func (d *stringFromDataFieldDefinition) Apply(u *unstructured.Unstructured) (Val
 
 	res, err := getValuesByJSONPath(u.Object, d.path)
 	if err != nil {
+		if d.optional {
+			return &value{}, nil
+		}
 		return nil, err
 	}
 
@@ -154,6 +178,13 @@ type mapFromDataFieldDefinition struct {
 
 var _ Definition = (*mapFromDataFieldDefinition)(nil)
 
+func (d *mapFromDataFieldDefinition) NonExistingOptional(v Value) bool {
+	if d.optional && v.Get() == nil {
+		return true
+	}
+	return false
+}
+
 func (d *mapFromDataFieldDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
 	if d.secretConfigMapReader == nil {
 		return nil, errors.New("kubeClient required for this functionality")
@@ -161,6 +192,9 @@ func (d *mapFromDataFieldDefinition) Apply(u *unstructured.Unstructured) (Value,
 
 	res, err := getValuesByJSONPath(u.Object, d.path)
 	if err != nil {
+		if d.optional {
+			return &value{}, nil
+		}
 		return nil, err
 	}
 	if len(res) != 1 {
@@ -220,9 +254,19 @@ type stringOfMapDefinition struct {
 
 var _ Definition = (*stringOfMapDefinition)(nil)
 
+func (d *stringOfMapDefinition) NonExistingOptional(v Value) bool {
+	if d.optional && v.Get() == nil {
+		return true
+	}
+	return false
+}
+
 func (d *stringOfMapDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
 	val, err := getValuesByJSONPath(u.Object, d.path)
 	if err != nil {
+		if d.optional {
+			return &value{}, nil
+		}
 		return nil, err
 	}
 	if len(val) != 1 {
@@ -253,9 +297,19 @@ type sliceOfMapsFromPathDefinition struct {
 
 var _ Definition = (*sliceOfMapsFromPathDefinition)(nil)
 
+func (d *sliceOfMapsFromPathDefinition) NonExistingOptional(v Value) bool {
+	if d.optional && v.Get() == nil {
+		return true
+	}
+	return false
+}
+
 func (d *sliceOfMapsFromPathDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
 	val, err := getValuesByJSONPath(u.Object, d.path)
 	if err != nil {
+		if d.optional {
+			return &value{}, nil
+		}
 		return nil, err
 	}
 
@@ -296,9 +350,19 @@ type sliceOfStringsFromPathDefinition struct {
 
 var _ Definition = (*sliceOfStringsFromPathDefinition)(nil)
 
+func (d *sliceOfStringsFromPathDefinition) NonExistingOptional(v Value) bool {
+	if d.optional && v.Get() == nil {
+		return true
+	}
+	return false
+}
+
 func (d *sliceOfStringsFromPathDefinition) Apply(u *unstructured.Unstructured) (Value, error) {
 	val, err := getValuesByJSONPath(u.Object, d.path)
 	if err != nil {
+		if d.optional {
+			return &value{}, nil
+		}
 		return nil, err
 	}
 	var res []interface{}
diff --git a/pkg/binding/mocks/mocks.go b/pkg/binding/mocks/mocks.go
index f2855691..a769fef0 100644
--- a/pkg/binding/mocks/mocks.go
+++ b/pkg/binding/mocks/mocks.go
@@ -64,6 +64,20 @@ func (mr *MockDefinitionMockRecorder) GetPath() *gomock.Call {
 	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPath", reflect.TypeOf((*MockDefinition)(nil).GetPath))
 }
 
+// NonExistingOptional mock
+func (m *MockDefinition) NonExistingOptional(v binding.Value) bool {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "NonExistingOptional")
+	ret0, _ := ret[0].(bool)
+	return ret0
+}
+
+// NonExistingOptional indicates an expected call of NonExistingOptional.
+func (mr *MockDefinitionMockRecorder) NonExistingOptional() *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NonExistingOptional", reflect.TypeOf((*MockDefinition)(nil).NonExistingOptional))
+}
+
 // MockValue is a mock of Value interface.
 type MockValue struct {
 	ctrl     *gomock.Controller
diff --git a/pkg/binding/model.go b/pkg/binding/model.go
index 3d60dd79..ae3c1121 100644
--- a/pkg/binding/model.go
+++ b/pkg/binding/model.go
@@ -13,6 +13,7 @@ type model struct {
 	sourceKey   string
 	sourceValue string
 	value       string
+	optional    bool
 }
 
 func (m *model) isStringElementType() bool {
@@ -39,7 +40,7 @@ func (m *model) hasDataField() bool {
 	return m.objectType == secretObjectType || m.objectType == configMapObjectType
 }
 
-var keys = []modelKey{pathModelKey, objectTypeModelKey, elementTypeModelKey, sourceKeyModelKey, sourceValueModelKey}
+var keys = []modelKey{pathModelKey, optionalKey, objectTypeModelKey, elementTypeModelKey, sourceKeyModelKey, sourceValueModelKey}
 
 func newModel(annotationValue string) (*model, error) {
 
@@ -81,6 +82,14 @@ func newModel(annotationValue string) (*model, error) {
 		}
 	}
 
+	optional := false
+	optionalString, found := raw[optionalKey]
+	if found {
+		if optionalString == "true" {
+			optional = true
+		}
+	}
+
 	// ensure sourceKey has a default value
 	sourceKey, found := raw[sourceKeyModelKey]
 	if !found {
@@ -125,5 +134,6 @@ func newModel(annotationValue string) (*model, error) {
 		objectType:  objType,
 		sourceValue: sourceValue,
 		sourceKey:   sourceKey,
+		optional:    optional,
 	}, nil
 }
diff --git a/pkg/binding/registry/registry.go b/pkg/binding/registry/registry.go
index ad2f1886..982e3dbc 100644
--- a/pkg/binding/registry/registry.go
+++ b/pkg/binding/registry/registry.go
@@ -19,7 +19,7 @@ func New() Registry {
 			schema.GroupVersionKind{Group: "redis.redis.opstreelabs.in", Version: "v1beta1", Kind: "Redis"}: {
 				"service.binding/type":     "redis",
 				"service.binding/host":     "path={.metadata.name}",
-				"service.binding/password": "path={.spec.kubernetesConfig.redisSecret.name},objectType=Secret,sourceKey=password",
+				"service.binding/password": "path={.spec.kubernetesConfig.redisSecret.name},objectType=Secret,sourceKey=password,optional=true",
 			},
 			schema.GroupVersionKind{Group: "postgres-operator.crunchydata.com", Version: "v1beta1", Kind: "PostgresCluster"}: {
 				"service.binding/type":     "postgresql",
diff --git a/pkg/reconcile/pipeline/handler/collect/impl.go b/pkg/reconcile/pipeline/handler/collect/impl.go
index 959efdd1..283d1e20 100644
--- a/pkg/reconcile/pipeline/handler/collect/impl.go
+++ b/pkg/reconcile/pipeline/handler/collect/impl.go
@@ -88,6 +88,9 @@ func BindingItems(ctx pipeline.Context) {
 				requestRetry(ctx, ErrorReadingBindingReason, err)
 				return
 			}
+			if bd.NonExistingOptional(bindingValue) {
+				continue
+			}
 			val := bindingValue.Get()
 			v := reflect.ValueOf(val)
 			if v.Kind() != reflect.Map {
diff --git a/samples/apps/spring-elasticsearch/Dockerfile.app b/samples/apps/spring-elasticsearch/Dockerfile.app
index c8a197ed..f82ffea7 100644
--- a/samples/apps/spring-elasticsearch/Dockerfile.app
+++ b/samples/apps/spring-elasticsearch/Dockerfile.app
@@ -2,7 +2,7 @@
 FROM --platform=amd64 maven:3.8.4-openjdk-11 as builder
 
 ARG APP_REPO=https://github.com/thombergs/code-examples
-ARG APP_COMMIT=1f37a81b90003c35beed6216432f689936917cb1
+ARG APP_COMMIT=351804a083d3fced44437b912b7fd8f61d9de85a
 ENV APP_DIR=/app
 
 RUN git clone "${APP_REPO}" ${APP_DIR} && \
diff --git a/samples/apps/spring-petclinic/Dockerfile.app b/samples/apps/spring-petclinic/Dockerfile.app
index 16577bd4..b3d68117 100644
--- a/samples/apps/spring-petclinic/Dockerfile.app
+++ b/samples/apps/spring-petclinic/Dockerfile.app
@@ -2,7 +2,7 @@
 FROM --platform=amd64 maven:3.8.4-openjdk-11 as builder
 
 ARG PETCLINIC_REPO=https://github.com/spring-projects/spring-petclinic.git
-ARG PETCLINIC_COMMIT=a7439c74ea718c4f59fe6c7c643c4afe59d7e718
+ARG PETCLINIC_COMMIT=3f8468432d86dcbd0cf4372e93e06d2f47aed26a
 ENV PETCLINIC_DIR=/petclinic
 
 RUN git clone "${PETCLINIC_REPO}" ${PETCLINIC_DIR} && \


Test patch:
diff --git a/pkg/binding/definition_test.go b/pkg/binding/definition_test.go
index 3ab7defb..ebf0653b 100644
--- a/pkg/binding/definition_test.go
+++ b/pkg/binding/definition_test.go
@@ -15,6 +15,7 @@ func TestStringDefinition(t *testing.T) {
 		path          string
 		value         string
 		expectedValue interface{}
+		optional      bool
 	}
 
 	testCases := []args{
@@ -58,6 +59,13 @@ func TestStringDefinition(t *testing.T) {
 				"foo": "AzureDiamond-foo",
 			},
 		},
+		{
+			description:   "optional true",
+			outputName:    "optionalvalue",
+			path:          "{.status.dbCredentials.donotexist}",
+			expectedValue: nil,
+			optional:      true,
+		},
 	}
 
 	u := &unstructured.Unstructured{
@@ -76,7 +84,8 @@ func TestStringDefinition(t *testing.T) {
 			d := &stringDefinition{
 				outputName: tc.outputName,
 				definition: definition{
-					path: tc.path,
+					path:     tc.path,
+					optional: tc.optional,
 				},
 				value: tc.value,
 			}
@@ -94,6 +103,7 @@ func TestStringOfMap(t *testing.T) {
 		path          string
 		expectedValue interface{}
 		object        *unstructured.Unstructured
+		optional      bool
 	}
 
 	u := &unstructured.Unstructured{
@@ -131,6 +141,13 @@ func TestStringOfMap(t *testing.T) {
 			object: u,
 			path:   "{.status.dbCredentials}",
 		},
+		{
+			description:   "optional true",
+			expectedValue: nil,
+			object:        u,
+			path:          "{.status.dbCredentials.donotexist}",
+			optional:      true,
+		},
 	}
 
 	for _, tc := range testCases {
@@ -138,7 +155,8 @@ func TestStringOfMap(t *testing.T) {
 			d := &stringOfMapDefinition{
 				outputName: tc.outputName,
 				definition: definition{
-					path: tc.path,
+					path:     tc.path,
+					optional: tc.optional,
 				},
 			}
 			val, err := d.Apply(tc.object)
diff --git a/pkg/reconcile/pipeline/handler/collect/impl_test.go b/pkg/reconcile/pipeline/handler/collect/impl_test.go
index 8d91efd0..400744bb 100644
--- a/pkg/reconcile/pipeline/handler/collect/impl_test.go
+++ b/pkg/reconcile/pipeline/handler/collect/impl_test.go
@@ -6,10 +6,11 @@ import (
 	"fmt"
 	"reflect"
 
+	"strings"
+
 	"github.com/golang/mock/gomock"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/ginkgo/extensions/table"
-	"strings"
 
 	olmv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
 	"github.com/redhat-developer/service-binding-operator/apis"
@@ -405,6 +406,7 @@ var _ = Describe("Collect Binding Data", func() {
 					service.EXPECT().Resource().Return(serviceResource)
 
 					definition.EXPECT().Apply(serviceResource).Return(value, nil)
+					definition.EXPECT().NonExistingOptional().Return(false)
 					value.EXPECT().Get().Return(map[string]map[string]interface{}{"java-maven_port": {"foo": nil}})
 
 					ctx.EXPECT().SetCondition(
@@ -478,6 +480,7 @@ var _ = Describe("Collect Binding Data", func() {
 				bv := bindingmocks.NewMockValue(mockCtrl)
 				bv.EXPECT().Get().Return("we should not return strings")
 				bd.EXPECT().Apply(serviceContent).Return(bv, nil)
+				bd.EXPECT().NonExistingOptional().Return(false)
 
 				service.EXPECT().BindingDefs().Return([]binding.Definition{bd})
 
@@ -528,6 +531,7 @@ var _ = Describe("Collect Binding Data", func() {
 						bv := bindingmocks.NewMockValue(mockCtrl)
 						bv.EXPECT().Get().Return(val)
 						bd.EXPECT().Apply(res).Return(bv, nil)
+						bd.EXPECT().NonExistingOptional().Return(false)
 						bindings = append(bindings, bd)
 						for k, v := range val.(map[string]interface{}) {
 							ctx.EXPECT().AddBindingItem(&pipeline.BindingItem{Name: k, Value: v, Source: service})
@@ -553,6 +557,7 @@ var _ = Describe("Collect Binding Data", func() {
 			bv := bindingmocks.NewMockValue(mockCtrl)
 			bv.EXPECT().Get().Return(val)
 			bd.EXPECT().Apply(res).Return(bv, nil)
+			bd.EXPECT().NonExistingOptional().Return(false)
 			bindings = append(bindings, bd)
 			ctx.EXPECT().AddBindingItem(&pipeline.BindingItem{Name: "foo_bar", Value: "bla", Source: service})
 			ctx.EXPECT().AddBindingItem(&pipeline.BindingItem{Name: "foo_bar2", Value: "bla2", Source: service})
@@ -571,6 +576,7 @@ var _ = Describe("Collect Binding Data", func() {
 			bv := bindingmocks.NewMockValue(mockCtrl)
 			bv.EXPECT().Get().Return(val)
 			bd.EXPECT().Apply(res).Return(bv, nil)
+			bd.EXPECT().NonExistingOptional().Return(false)
 			bindings = append(bindings, bd)
 			ctx.EXPECT().AddBindingItem(&pipeline.BindingItem{Name: "foo_bar_0", Value: "bla", Source: service})
 			ctx.EXPECT().AddBindingItem(&pipeline.BindingItem{Name: "foo_bar_1", Value: "bla2", Source: service})
diff --git a/test/acceptance/features/bindAppToServiceAnnotationsOptional.feature b/test/acceptance/features/bindAppToServiceAnnotationsOptional.feature
new file mode 100644
index 00000000..ec02515e
--- /dev/null
+++ b/test/acceptance/features/bindAppToServiceAnnotationsOptional.feature
@@ -0,0 +1,171 @@
+@optional-annotations
+@external-feedback
+Feature: Bind an application to a service using optional annotations
+
+    As a user of Service Binding Operator
+    I want to bind application to services that expose optional bindable information
+    via annotations placed on service's CRD or CR.
+
+    Background:
+        Given Namespace [TEST_NAMESPACE] is used
+        * Service Binding Operator is running
+        * OLM Operator "custom_app" is running
+        * CustomResourceDefinition backends.stable.example.com is available
+
+    Scenario: Bind to the application with annotations and with optional source field present
+        Given Generic test application is running
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id
+                annotations:
+                    service.binding/uri: "path={.spec.uri},optional=true"
+                    service.binding/image: "path={.spec.image}"
+            spec:
+                uri: "youknow.where"
+                image: "busybox"
+            """
+        When Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: $scenario_id
+            spec:
+                bindAsFiles: true
+                services:
+                  - group: stable.example.com
+                    version: v1
+                    kind: AppConfig
+                    name: $scenario_id
+                application:
+                    group: apps
+                    version: v1
+                    resource: deployments
+                    name: $scenario_id
+            """
+        Then Service Binding is ready
+        * Content of file "/bindings/$scenario_id/uri" in application pod is
+            """
+            youknow.where
+            """
+        * Content of file "/bindings/$scenario_id/image" in application pod is
+            """
+            busybox
+            """
+
+    Scenario: Bind to the application with annotations and with missing optional source field to be excluded
+        Given Generic test application is running
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id
+                annotations:
+                    service.binding/uri: "path={.spec.uri},optional=true"
+                    service.binding/image: "path={.spec.image}"
+            spec:
+                image: "busybox"
+            """
+        When Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: $scenario_id
+            spec:
+                bindAsFiles: true
+                services:
+                  - group: stable.example.com
+                    version: v1
+                    kind: AppConfig
+                    name: $scenario_id
+                application:
+                    group: apps
+                    version: v1
+                    resource: deployments
+                    name: $scenario_id
+            """
+        Then Service Binding is ready
+        * File "/bindings/$scenario_id/uri" is unavailable in application pod
+        * Content of file "/bindings/$scenario_id/image" in application pod is
+            """
+            busybox
+            """
+
+    @negative
+    Scenario: Bind to the application with annotations and with missing non-optional source field to cause binding failure
+        Given Generic test application is running
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id
+                annotations:
+                    service.binding/uri: "path={.spec.uri}"
+                    service.binding/image: "path={.spec.image}"
+            spec:
+                image: "busybox"
+            """
+        When Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: $scenario_id
+            spec:
+                bindAsFiles: true
+                services:
+                  - group: stable.example.com
+                    version: v1
+                    kind: AppConfig
+                    name: $scenario_id
+                application:
+                    group: apps
+                    version: v1
+                    resource: deployments
+                    name: $scenario_id
+            """
+        Then Service Binding CollectionReady.status is "False"
+        And Service Binding CollectionReady.reason is "ErrorReadingBinding"
+
+    @negative
+    Scenario: Bind to the application with annotations and with missing non-optional source field to cause binding failure with explicit false
+        Given Generic test application is running
+        And The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: AppConfig
+            metadata:
+                name: $scenario_id
+                annotations:
+                    service.binding/uri: "path={.spec.uri}"
+                    service.binding/image: "path={.spec.image}"
+            spec:
+                image: "busybox"
+            """
+        When Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: $scenario_id
+            spec:
+                bindAsFiles: true
+                services:
+                  - group: stable.example.com
+                    version: v1
+                    kind: AppConfig
+                    name: $scenario_id
+                application:
+                    group: apps
+                    version: v1
+                    resource: deployments
+                    name: $scenario_id
+            """
+        Then Service Binding CollectionReady.status is "False"
+        And Service Binding CollectionReady.reason is "ErrorReadingBinding"