Repository: redhat-developer/service-binding-operator (go)

Issue:
A name and selector MUST NOT be defined in the same application reference
As per the [spec](https://github.com/k8s-service-bindings/spec#service-binding) in the context of an application reference, there is a normative statement like this :

> A name and selector **MUST NOT** be defined in the same reference.

The status condition can add the reason for failure: `InvalidApplicationReference`


Pull request description:
A name and selector MUST NOT be defined together
A name and selector MUST NOT be defined in the same application
reference

Fix #974


Patch:
diff --git a/README.md b/README.md
index 4dfedc40..d9d3a6fd 100644
--- a/README.md
+++ b/README.md
@@ -114,38 +114,6 @@ Clone the repository and run `make run` using an existing `kube:admin` kube cont
 * Auto-detect binding resources in the absence of binding decorators.
 
 
-## Getting Started
-
-The best way to get started with the Service Binding Operator is to see it in action.
-
-A number of example scenarios for using the operator are included in this
-repo. The examples are found in the "/examples" directory. Each of these
-examples illustrates a usage scenario for the operator. Each example also
-includes a README file with step-by-step instructions for how to run the
-example.
-
-The following section in this README file includes links to the current set of examples.
-
-## Example Scenarios
-
-The following example scenarios are available:
-
-[Binding an Imported app with an In-cluster Operator Managed PostgreSQL Database](examples/nodejs_postgresql/README.md)
-
-[Binding an Imported app with an Off-cluster Operator Managed AWS RDS Database](examples/nodejs_awsrds_varprefix/README.md)
-
-[Binding an Imported Java Spring Boot app with an In-cluster Operator Managed PostgreSQL Database](examples/java_postgresql_customvar/README.md)
-
-[Binding an Imported Quarkus app deployed as Knative service with an In-cluster Operator Managed PostgreSQL Database](examples/knative_postgresql_customvar/README.md)
-
-[Binding an Imported app with an In-cluster Operator Managed ETCD Database](examples/nodejs_etcd_operator/README.md)
-
-[Binding an Imported app to an Off-cluster Operator Managed IBM Cloud Service](examples/nodejs_ibmcloud_operator/README.md)
-
-[Binding an Imported app in one namespace with an In-cluster Managed PostgreSQL Database in another namespace](examples/nodejs_postgresql_namespaces/README.md)
-
-[Binding an Imported app to a Route/Ingress](examples/route_k8s_resource/README.md)
-
 ## Roadmap
 
 The [Service Binding Operator roadmap uses the label roadmap](https://github.com/redhat-developer/service-binding-operator/labels/roadmap) to track the direction of the project.
diff --git a/apis/binding/v1alpha1/servicebinding_webhook.go b/apis/binding/v1alpha1/servicebinding_webhook.go
index 074b1a78..c0a3b245 100644
--- a/apis/binding/v1alpha1/servicebinding_webhook.go
+++ b/apis/binding/v1alpha1/servicebinding_webhook.go
@@ -15,6 +15,7 @@ package v1alpha1
 
 import (
 	"errors"
+
 	"github.com/redhat-developer/service-binding-operator/apis"
 	"k8s.io/apimachinery/pkg/runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -31,13 +32,22 @@ func (r *ServiceBinding) SetupWebhookWithManager(mgr ctrl.Manager) error {
 		Complete()
 }
 
-// +kubebuilder:webhook:path=/validate-binding-operators-coreos-com-v1alpha1-servicebinding,mutating=false,failurePolicy=fail,sideEffects=None,groups=binding.operators.coreos.com,resources=servicebindings,verbs=update,versions=v1alpha1,name=vservicebinding.kb.io,admissionReviewVersions={v1beta1}
+// +kubebuilder:webhook:path=/validate-binding-operators-coreos-com-v1alpha1-servicebinding,mutating=false,failurePolicy=fail,sideEffects=None,groups=binding.operators.coreos.com,resources=servicebindings,verbs=create;update,versions=v1alpha1,name=vservicebinding.kb.io,admissionReviewVersions={v1beta1}
 
 var _ webhook.Validator = &ServiceBinding{}
 
+func checkNameAndSelector(r *ServiceBinding) error {
+	if r.Spec.Application.Name != "" && r.Spec.Application.LabelSelector != nil && r.Spec.Application.LabelSelector.MatchLabels != nil {
+		err := errors.New("name and selector MUST NOT be defined in the application reference")
+		log.Error(err, "name and selector check failed")
+		return err
+	}
+	return nil
+}
+
 // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
 func (r *ServiceBinding) ValidateCreate() error {
-	return nil
+	return checkNameAndSelector(r)
 }
 
 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
@@ -49,8 +59,9 @@ func (r *ServiceBinding) ValidateUpdate(old runtime.Object) error {
 	err := apis.CanUpdateBinding(r, oldSb)
 	if err != nil {
 		log.Error(err, "Update failed")
+		return err
 	}
-	return err
+	return checkNameAndSelector(r)
 }
 
 // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
diff --git a/apis/spec/v1alpha3/servicebinding_webhook.go b/apis/spec/v1alpha3/servicebinding_webhook.go
index 13e929a0..7dbd60c0 100644
--- a/apis/spec/v1alpha3/servicebinding_webhook.go
+++ b/apis/spec/v1alpha3/servicebinding_webhook.go
@@ -19,6 +19,7 @@ package v1alpha3
 
 import (
 	"errors"
+
 	"github.com/redhat-developer/service-binding-operator/apis"
 	"k8s.io/apimachinery/pkg/runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -38,13 +39,22 @@ func (r *ServiceBinding) SetupWebhookWithManager(mgr ctrl.Manager) error {
 // EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
 
 // TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
-// +kubebuilder:webhook:path=/validate-servicebinding-io-v1alpha3-servicebinding,mutating=false,failurePolicy=fail,sideEffects=None,groups=servicebinding.io,resources=servicebindings,verbs=update,versions=v1alpha3,name=vspecservicebinding.kb.io,admissionReviewVersions={v1beta1}
+// +kubebuilder:webhook:path=/validate-servicebinding-io-v1alpha3-servicebinding,mutating=false,failurePolicy=fail,sideEffects=None,groups=servicebinding.io,resources=servicebindings,verbs=create;update,versions=v1alpha3,name=vspecservicebinding.kb.io,admissionReviewVersions={v1beta1}
 
 var _ webhook.Validator = &ServiceBinding{}
 
+func checkNameAndSelector(r *ServiceBinding) error {
+	if r.Spec.Workload.Name != "" && r.Spec.Workload.Selector != nil && r.Spec.Workload.Selector.MatchLabels != nil {
+		err := errors.New("name and selector MUST NOT be defined in the application reference")
+		log.Error(err, "name and selector check failed")
+		return err
+	}
+	return nil
+}
+
 // ValidateCreate implements webhook.Validator so a webhook will be registered for the type
 func (r *ServiceBinding) ValidateCreate() error {
-	return nil
+	return checkNameAndSelector(r)
 }
 
 // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
@@ -56,8 +66,9 @@ func (sb *ServiceBinding) ValidateUpdate(old runtime.Object) error {
 	err := apis.CanUpdateBinding(sb, oldSb)
 	if err != nil {
 		log.Error(err, "Update failed")
+		return err
 	}
-	return err
+	return checkNameAndSelector(sb)
 }
 
 // ValidateDelete implements webhook.Validator so a webhook will be registered for the type
diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml
index 7391fdb6..0bcbb6fe 100644
--- a/config/webhook/manifests.yaml
+++ b/config/webhook/manifests.yaml
@@ -69,6 +69,7 @@ webhooks:
     apiVersions:
     - v1alpha1
     operations:
+    - CREATE
     - UPDATE
     resources:
     - servicebindings
@@ -88,6 +89,7 @@ webhooks:
     apiVersions:
     - v1alpha3
     operations:
+    - CREATE
     - UPDATE
     resources:
     - servicebindings
diff --git a/pkg/reconcile/pipeline/context/spec_binding_impl.go b/pkg/reconcile/pipeline/context/spec_binding_impl.go
index f2d82649..772fc4f2 100644
--- a/pkg/reconcile/pipeline/context/spec_binding_impl.go
+++ b/pkg/reconcile/pipeline/context/spec_binding_impl.go
@@ -3,6 +3,7 @@ package context
 import (
 	"context"
 	"fmt"
+
 	"github.com/redhat-developer/service-binding-operator/apis"
 	"github.com/redhat-developer/service-binding-operator/apis/spec/v1alpha3"
 	"github.com/redhat-developer/service-binding-operator/pkg/client/kubernetes"
@@ -151,7 +152,7 @@ func (i *specImpl) Applications() ([]pipeline.Application, error) {
 			}
 			i.applications = append(i.applications, &application{gvr: gvr, persistedResource: u, bindableContainerNames: sets.NewString(i.serviceBinding.Spec.Workload.Containers...)})
 		}
-		if i.serviceBinding.Spec.Workload.Selector != nil {
+		if i.serviceBinding.Spec.Workload.Selector != nil && i.serviceBinding.Spec.Workload.Selector.MatchLabels != nil {
 			matchLabels := i.serviceBinding.Spec.Workload.Selector.MatchLabels
 			opts := metav1.ListOptions{
 				LabelSelector: labels.Set(matchLabels).String(),


Test patch:
diff --git a/apis/binding/v1alpha1/servicebinding_webhook_test.go b/apis/binding/v1alpha1/servicebinding_webhook_test.go
index 2630c453..e3dd930f 100644
--- a/apis/binding/v1alpha1/servicebinding_webhook_test.go
+++ b/apis/binding/v1alpha1/servicebinding_webhook_test.go
@@ -4,6 +4,7 @@ import (
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/redhat-developer/service-binding-operator/apis"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -44,4 +45,63 @@ var _ = Describe("Validation Webhook", func() {
 		}
 		Expect(sb.ValidateUpdate(old)).ShouldNot(HaveOccurred())
 	})
+
+	It("should return error if both application name and selecter is specified during creation", func() {
+
+		ls := &metav1.LabelSelector{
+			MatchLabels: map[string]string{"env": "prod"},
+		}
+
+		ref := Application{
+			Ref: Ref{
+				Group:   "app",
+				Version: "v1",
+				Kind:    "Foo",
+				Name:    "app1",
+			},
+			LabelSelector: ls,
+		}
+
+		sb := &ServiceBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "sb1",
+				Namespace: "ns1",
+			},
+			Spec: ServiceBindingSpec{
+				Application: ref,
+			},
+		}
+		Expect(sb.ValidateCreate()).Should(HaveOccurred())
+
+	})
+
+	It("should return error if both application name and selecter is specified during update", func() {
+
+		ls := &metav1.LabelSelector{
+			MatchLabels: map[string]string{"env": "prod"},
+		}
+
+		ref := Application{
+			Ref: Ref{
+				Group:   "app",
+				Version: "v1",
+				Kind:    "Foo",
+				Name:    "app1",
+			},
+			LabelSelector: ls,
+		}
+
+		sb := &ServiceBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "sb1",
+				Namespace: "ns1",
+			},
+			Spec: ServiceBindingSpec{
+				Application: ref,
+			},
+		}
+		Expect(sb.ValidateUpdate(sb)).Should(HaveOccurred())
+
+	})
+
 })
diff --git a/apis/spec/v1alpha3/servicebinding_webhook_test.go b/apis/spec/v1alpha3/servicebinding_webhook_test.go
index ee93f23e..249426dc 100644
--- a/apis/spec/v1alpha3/servicebinding_webhook_test.go
+++ b/apis/spec/v1alpha3/servicebinding_webhook_test.go
@@ -4,6 +4,7 @@ import (
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	"github.com/redhat-developer/service-binding-operator/apis"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -44,4 +45,57 @@ var _ = Describe("Validation Webhook", func() {
 		}
 		Expect(sb.ValidateUpdate(old)).ShouldNot(HaveOccurred())
 	})
+
+	It("should return error if both application name and selecter is specified during creation", func() {
+
+		ls := &metav1.LabelSelector{
+			MatchLabels: map[string]string{"env": "prod"},
+		}
+
+		ref := ServiceBindingWorkloadReference{
+			APIVersion: "app/v1",
+			Kind:       "Foo",
+			Name:       "app1",
+			Selector:   ls,
+		}
+
+		sb := &ServiceBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "sb1",
+				Namespace: "ns1",
+			},
+			Spec: ServiceBindingSpec{
+				Workload: ref,
+			},
+		}
+		Expect(sb.ValidateCreate()).Should(HaveOccurred())
+
+	})
+
+	It("should return error if both application name and selecter is specified during update", func() {
+
+		ls := &metav1.LabelSelector{
+			MatchLabels: map[string]string{"env": "prod"},
+		}
+
+		ref := ServiceBindingWorkloadReference{
+			APIVersion: "app/v1",
+			Kind:       "Foo",
+			Name:       "app1",
+			Selector:   ls,
+		}
+
+		sb := &ServiceBinding{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "sb1",
+				Namespace: "ns1",
+			},
+			Spec: ServiceBindingSpec{
+				Workload: ref,
+			},
+		}
+		Expect(sb.ValidateUpdate(sb)).Should(HaveOccurred())
+
+	})
+
 })
diff --git a/test/acceptance/features/bindAppToService.feature b/test/acceptance/features/bindAppToService.feature
index acf29946..4449010f 100644
--- a/test/acceptance/features/bindAppToService.feature
+++ b/test/acceptance/features/bindAppToService.feature
@@ -331,6 +331,199 @@ Feature: Bind an application to a service
         Then Error message is thrown
         And Service Binding "binding-request-empty-app" is not persistent in the cluster
 
+    @negative
+    Scenario: Cannot create Service Binding with name and label selector
+        Given CustomResourceDefinition backends.stable.example.com is available
+        * The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: Backend
+            metadata:
+                name: backend-demo-empty-app
+                annotations:
+                    service.binding/host: path={.spec.host}
+                    service.binding/username: path={.spec.username}
+            spec:
+                host: example.common
+                username: foo
+            """
+        When Invalid Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: binding-request-with-name-label-selector
+            spec:
+                bindAsFiles: false
+                application:
+                    name: gen-missing-app
+                    group: apps
+                    version: v1
+                    resource: deployments
+                    labelSelector:
+                      matchLabels:
+                        name: backend-operator
+                services:
+                -   group: stable.example.com
+                    version: v1
+                    kind: Backend
+                    name: backend-demo-empty-app
+            """
+        Then Error message "name and selector MUST NOT be defined in the application reference" is thrown
+
+    @spec
+    Scenario: Cannot create Service Binding with name and label selector in the spec API
+        Given CustomResourceDefinition backends.stable.example.com is available
+        * The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: Backend
+            metadata:
+                name: backend-demo-empty-app
+                annotations:
+                    service.binding/host: path={.spec.host}
+                    service.binding/username: path={.spec.username}
+            spec:
+                host: example.common
+                username: foo
+            """
+        When Invalid Service Binding is applied
+            """
+            apiVersion: servicebinding.io/v1alpha3
+            kind: ServiceBinding
+            metadata:
+                name: binding-request-with-name-label-selector-spec
+            spec:
+                workload:
+                  name: gen-missing-app
+                  apiVersion: apps/v1
+                  kind: Deployment
+                  selector:
+                    matchLabels:
+                      name: backend-operator
+                service:
+                  apiVersion: stable.example.com/v1
+                  kind: Backend
+                  name: backend-demo-empty-app
+            """
+        Then Error message "name and selector MUST NOT be defined in the application reference" is thrown
+
+    @negative
+    Scenario: Cannot update Service Binding with name and label selector
+        Given CustomResourceDefinition backends.stable.example.com is available
+        * The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: Backend
+            metadata:
+                name: backend-demo-empty-app
+                annotations:
+                    service.binding/host: path={.spec.host}
+                    service.binding/username: path={.spec.username}
+            spec:
+                host: example.common
+                username: foo
+            """
+        When Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: binding-request-with-name-label-selector2
+            spec:
+                bindAsFiles: false
+                application:
+                    group: apps
+                    version: v1
+                    resource: deployments
+                    labelSelector:
+                      matchLabels:
+                        name: backend-operator
+                services:
+                -   group: stable.example.com
+                    version: v1
+                    kind: Backend
+                    name: backend-demo-empty-app
+            """
+        When Invalid Service Binding is applied
+            """
+            apiVersion: binding.operators.coreos.com/v1alpha1
+            kind: ServiceBinding
+            metadata:
+                name: binding-request-with-name-label-selector2
+            spec:
+                bindAsFiles: false
+                application:
+                    name: gen-missing-app
+                    group: apps
+                    version: v1
+                    resource: deployments
+                    labelSelector:
+                      matchLabels:
+                        name: backend-operator
+                services:
+                -   group: stable.example.com
+                    version: v1
+                    kind: Backend
+                    name: backend-demo-empty-app
+            """
+        Then Error message "name and selector MUST NOT be defined in the application reference" is thrown
+
+    @spec
+    Scenario: Cannot update Service Binding with name and label selector in the spec API
+        Given CustomResourceDefinition backends.stable.example.com is available
+        * The Custom Resource is present
+            """
+            apiVersion: "stable.example.com/v1"
+            kind: Backend
+            metadata:
+                name: backend-demo-empty-app
+                annotations:
+                    service.binding/host: path={.spec.host}
+                    service.binding/username: path={.spec.username}
+            spec:
+                host: example.common
+                username: foo
+            """
+        When Service Binding is applied
+            """
+            apiVersion: servicebinding.io/v1alpha3
+            kind: ServiceBinding
+            metadata:
+                name: binding-request-with-name-label-selector-spec2
+            spec:
+                workload:
+                  apiVersion: apps/v1
+                  kind: Deployment
+                  selector:
+                    matchLabels:
+                      name: backend-operator
+                service:
+                  apiVersion: stable.example.com/v1
+                  kind: Backend
+                  name: backend-demo-empty-app
+            """
+        When Invalid Service Binding is applied
+            """
+            apiVersion: servicebinding.io/v1alpha3
+            kind: ServiceBinding
+            metadata:
+                name: binding-request-with-name-label-selector-spec2
+            spec:
+                workload:
+                  name: gen-missing-app
+                  apiVersion: apps/v1
+                  kind: Deployment
+                  selector:
+                    matchLabels:
+                      name: backend-operator
+                service:
+                  apiVersion: stable.example.com/v1
+                  kind: Backend
+                  name: backend-demo-empty-app
+            """
+        Then Error message "name and selector MUST NOT be defined in the application reference" is thrown
+
     @olm
     Scenario: Bind service to application using binding definition available in x-descriptors
         Given OLM Operator "backend-new-spec" is running
diff --git a/test/acceptance/features/injectSecretAtCustomSchemaPath.feature b/test/acceptance/features/injectSecretAtCustomSchemaPath.feature
index ad0c93c9..3f78fa96 100644
--- a/test/acceptance/features/injectSecretAtCustomSchemaPath.feature
+++ b/test/acceptance/features/injectSecretAtCustomSchemaPath.feature
@@ -32,7 +32,6 @@ Feature: Insert service binding to a custom location in application resource
             """
         * OLM Operator "custom_app" is running
 
-    # https://github.com/redhat-developer/service-binding-operator/tree/master/examples/pod_spec_path
     Scenario: Specify container's path in Service Binding
         Given The Custom Resource is present
             """