diff --git a/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml b/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml index 67b318304c..2e6d63be8b 100644 --- a/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml +++ b/deploy/crds/apps.openshift.io_servicebindingrequests_crd.yaml @@ -37,7 +37,7 @@ spec: properties: applicationSelector: description: ApplicationSelector is used to identify the application - connecting to the backing service operator. + connecting to the backing service operator. Deprecated. properties: group: type: string @@ -99,10 +99,79 @@ spec: - resource - version type: object + applications: + description: Applications are used to identify the application connecting + to the backing service. + items: + description: ApplicationSelector defines the selector based on labels + and GVR + properties: + group: + type: string + labelSelector: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An + empty label selector matches all objects. A null label selector + matches no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: A label selector requirement is a selector + that contains values, a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, + Exists and DoesNotExist. + type: string + values: + description: values is an array of string values. If + the operator is In or NotIn, the values array must + be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced + during a strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A + single {key,value} in the matchLabels map is equivalent + to an element of matchExpressions, whose key field is "key", + the operator is "In", and the values array contains only + "value". The requirements are ANDed. + type: object + type: object + resource: + type: string + resourceRef: + type: string + version: + type: string + required: + - group + - resource + - version + type: object + type: array backingServiceSelector: - description: 'BackingServiceSelector is used to identify the backing - service operator. Deprecation Notice: In the upcoming release, this - field would be depcreated. It would be mandatory to set "backingServiceSelectors".' + description: BackingServiceSelector is used to identify the backing + service operator or other Kubernetes resource. Deprecated. In the + upcoming release, this field would be depcreated. It would be mandatory + to set "backingServiceSelectors". properties: group: type: string @@ -122,8 +191,8 @@ spec: type: object backingServiceSelectors: description: BackingServiceSelectors is used to identify multiple backing - services. This would be made a required field after 'BackingServiceSelector' - is removed. + services or any other Kubernetes resource. Deprecated. This would + be made a required field after 'BackingServiceSelector' is removed. items: description: BackingServiceSelector defines the selector based on resource name, version, and resource kind @@ -253,6 +322,29 @@ spec: mountPathPrefix: description: MountPathPrefix is the prefix for volume mount type: string + services: + description: Services are used to identify multiple backing services. + items: + description: BackingServiceSelector defines the selector based on + resource name, version, and resource kind + properties: + group: + type: string + kind: + type: string + namespace: + type: string + resourceRef: + type: string + version: + type: string + required: + - group + - kind + - resourceRef + - version + type: object + type: array type: object status: description: ServiceBindingRequestStatus defines the observed state of ServiceBindingRequest diff --git a/go.sum b/go.sum index cebff7cac2..d27166e656 100644 --- a/go.sum +++ b/go.sum @@ -1079,6 +1079,7 @@ sigs.k8s.io/controller-runtime v0.1.10 h1:amLOmcekVdnsD1uIpmgRqfTbQWJ2qxvQkcdeFh sigs.k8s.io/controller-runtime v0.1.10/go.mod h1:HFAYoOh6XMV+jKF1UjFwrknPbowfyHEHHRdJMf2jMX8= sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg= sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= +sigs.k8s.io/controller-runtime v0.5.1 h1:TNidCfVoU/cs2i+9xoTcL/l7yhl0bDhYXU0NCG6wmiE= sigs.k8s.io/controller-tools v0.1.10/go.mod h1:6g08p9m9G/So3sBc1AOQifHfhxH/mb6Sc4z0LMI8XMw= sigs.k8s.io/controller-tools v0.2.4 h1:la1h46EzElvWefWLqfsXrnsO3lZjpkI0asTpX6h8PLA= sigs.k8s.io/controller-tools v0.2.4/go.mod h1:m/ztfQNocGYBgTTCmFdnK94uVvgxeZeE3LtJvd/jIzA= diff --git a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go index f29f1a7fb2..66f75a7b6d 100644 --- a/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go +++ b/pkg/apis/apps/v1alpha1/servicebindingrequest_types.go @@ -10,9 +10,7 @@ import ( // ServiceBindingRequestSpec defines the desired state of ServiceBindingRequest type ServiceBindingRequestSpec struct { - // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file - // Add custom validation using kubebuilder tags: - // https://book.kubebuilder.io/beyond_basics/generating_crd.html + // MountPathPrefix is the prefix for volume mount // +optional MountPathPrefix string `json:"mountPathPrefix,omitempty"` @@ -25,14 +23,36 @@ type ServiceBindingRequestSpec struct { // +optional CustomEnvVar []corev1.EnvVar `json:"customEnvVar,omitempty"` - // BackingServiceSelector is used to identify the backing service operator. - // Deprecation Notice: + // NOTE: Services & Applications will be made non-optional + // after deprecated fields are removed. + + // Services are used to identify multiple backing services. + // +optional + Services *[]BackingServiceSelector `json:"services,omitempty"` + + // Applications are used to identify the application connecting to the + // backing service. + // +optional + Applications *[]ApplicationSelector `json:"applications,omitempty"` + + // DetectBindingResources is flag used to bind all non-bindable variables from + // different subresources owned by backing operator CR. + // +optional + DetectBindingResources bool `json:"detectBindingResources"` + + // The following fields are being deprecated + + // BackingServiceSelector is used to identify the backing service operator + // or other Kubernetes resource. + // Deprecated. // In the upcoming release, this field would be depcreated. It would be mandatory // to set "backingServiceSelectors". // +optional BackingServiceSelector *BackingServiceSelector `json:"backingServiceSelector,omitempty"` - // BackingServiceSelectors is used to identify multiple backing services. + // BackingServiceSelectors is used to identify multiple backing services + // or any other Kubernetes resource. + // Deprecated. // This would be made a required field after 'BackingServiceSelector' // is removed. // +optional @@ -40,13 +60,9 @@ type ServiceBindingRequestSpec struct { // ApplicationSelector is used to identify the application connecting to the // backing service operator. + // Deprecated. // +optional - ApplicationSelector ApplicationSelector `json:"applicationSelector"` - - // DetectBindingResources is flag used to bind all non-bindable variables from - // different subresources owned by backing operator CR. - // +optional - DetectBindingResources bool `json:"detectBindingResources,omitempty"` + ApplicationSelector *ApplicationSelector `json:"applicationSelector"` } // ServiceBindingRequestStatus defines the observed state of ServiceBindingRequest diff --git a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go index e00095f50e..1c29e14a93 100644 --- a/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/apps/v1alpha1/zz_generated.deepcopy.go @@ -160,6 +160,28 @@ func (in *ServiceBindingRequestSpec) DeepCopyInto(out *ServiceBindingRequestSpec (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = new([]BackingServiceSelector) + if **in != nil { + in, out := *in, *out + *out = make([]BackingServiceSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + } + if in.Applications != nil { + in, out := &in.Applications, &out.Applications + *out = new([]ApplicationSelector) + if **in != nil { + in, out := *in, *out + *out = make([]ApplicationSelector, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + } if in.BackingServiceSelector != nil { in, out := &in.BackingServiceSelector, &out.BackingServiceSelector *out = new(BackingServiceSelector) @@ -176,7 +198,11 @@ func (in *ServiceBindingRequestSpec) DeepCopyInto(out *ServiceBindingRequestSpec } } } - in.ApplicationSelector.DeepCopyInto(&out.ApplicationSelector) + if in.ApplicationSelector != nil { + in, out := &in.ApplicationSelector, &out.ApplicationSelector + *out = new(ApplicationSelector) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/apis/apps/v1alpha1/zz_generated.openapi.go b/pkg/apis/apps/v1alpha1/zz_generated.openapi.go index 086dbf6743..2548b2395d 100644 --- a/pkg/apis/apps/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/apps/v1alpha1/zz_generated.openapi.go @@ -27,8 +27,8 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequest": schema_pkg_apis_apps_v1alpha1_ServiceBindingRequest(ref), - "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus": schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestStatus(ref), + "./pkg/apis/apps/v1alpha1.ServiceBindingRequest": schema_pkg_apis_apps_v1alpha1_ServiceBindingRequest(ref), + "./pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus": schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestStatus(ref), } } @@ -60,19 +60,19 @@ func schema_pkg_apis_apps_v1alpha1_ServiceBindingRequest(ref common.ReferenceCal }, "spec": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec"), + Ref: ref("./pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec"), }, }, "status": { SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus"), + Ref: ref("./pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus"), }, }, }, }, }, Dependencies: []string{ - "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec", "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "./pkg/apis/apps/v1alpha1.ServiceBindingRequestSpec", "./pkg/apis/apps/v1alpha1.ServiceBindingRequestStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -117,7 +117,7 @@ func schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestStatus(ref common.Refere Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.BoundApplication"), + Ref: ref("./pkg/apis/apps/v1alpha1.BoundApplication"), }, }, }, @@ -128,6 +128,6 @@ func schema_pkg_apis_apps_v1alpha1_ServiceBindingRequestStatus(ref common.Refere }, }, Dependencies: []string{ - "github.com/openshift/custom-resource-status/conditions/v1.Condition", "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1.BoundApplication"}, + "./pkg/apis/apps/v1alpha1.BoundApplication", "github.com/openshift/custom-resource-status/conditions/v1.Condition"}, } } diff --git a/pkg/controller/servicebindingrequest/binder.go b/pkg/controller/servicebindingrequest/binder.go index 4811aafaa5..cc09613a1e 100644 --- a/pkg/controller/servicebindingrequest/binder.go +++ b/pkg/controller/servicebindingrequest/binder.go @@ -50,42 +50,63 @@ var EmptyApplicationSelectorErr = errors.New("application ResourceRef or MatchLa // search objects based in Kind/APIVersion, which contain the labels defined in ApplicationSelector. func (b *Binder) search() (*unstructured.UnstructuredList, error) { ns := b.sbr.GetNamespace() - gvr := schema.GroupVersionResource{ - Group: b.sbr.Spec.ApplicationSelector.GroupVersionResource.Group, - Version: b.sbr.Spec.ApplicationSelector.GroupVersionResource.Version, - Resource: b.sbr.Spec.ApplicationSelector.GroupVersionResource.Resource, + + var objList *unstructured.UnstructuredList = &unstructured.UnstructuredList{} + var appSelectors = []v1alpha1.ApplicationSelector{} + var err error + + if b.sbr.Spec.Applications != nil { + appSelectors = append(appSelectors, *b.sbr.Spec.Applications...) } - var opts metav1.ListOptions + // TODO: Deprecation, will be removed + if b.sbr.Spec.ApplicationSelector != nil { + appSelectors = append(appSelectors, *b.sbr.Spec.ApplicationSelector) + } - // If Application name is present - if b.sbr.Spec.ApplicationSelector.ResourceRef != "" { - fieldName := make(map[string]string) - fieldName["metadata.name"] = b.sbr.Spec.ApplicationSelector.ResourceRef - opts = metav1.ListOptions{ - FieldSelector: fields.Set(fieldName).String(), - } - } else if b.sbr.Spec.ApplicationSelector.LabelSelector != nil { - matchLabels := b.sbr.Spec.ApplicationSelector.LabelSelector.MatchLabels - opts = metav1.ListOptions{ - LabelSelector: labels.Set(matchLabels).String(), - } - } else { + if len(appSelectors) == 0 { return nil, EmptyApplicationSelectorErr } - objList, err := b.dynClient.Resource(gvr).Namespace(ns).List(opts) - if err != nil { - return nil, err + for _, app := range appSelectors { + + var opts metav1.ListOptions + gvr := schema.GroupVersionResource{ + Group: app.GroupVersionResource.Group, + Version: app.GroupVersionResource.Version, + Resource: app.GroupVersionResource.Resource, + } + + if app.ResourceRef != "" { + fieldName := make(map[string]string) + fieldName["metadata.name"] = app.ResourceRef + opts = metav1.ListOptions{ + FieldSelector: fields.Set(fieldName).String(), + } + } else if app.LabelSelector != nil { + matchLabels := app.LabelSelector.MatchLabels + opts = metav1.ListOptions{ + LabelSelector: labels.Set(matchLabels).String(), + } + } + appObjList, err := b.dynClient.Resource(gvr).Namespace(ns).List(opts) + if err != nil { + return nil, err + } + + if len(appObjList.Items) == 0 { + return nil, k8serror.NewNotFound( + gvr.GroupResource(), + app.GroupVersionResource.Resource, + ) + } + objList.Items = append(objList.Items, appObjList.Items...) } - // Return fake NotFound error explicitly to ensure requeue when objList(^) is empty. if len(objList.Items) == 0 { - return nil, k8serror.NewNotFound( - gvr.GroupResource(), - b.sbr.Spec.ApplicationSelector.GroupVersionResource.Resource, - ) + return nil, EmptyApplicationSelectorErr } + return objList, err } diff --git a/pkg/controller/servicebindingrequest/binder_test.go b/pkg/controller/servicebindingrequest/binder_test.go index c6f683ed5e..17fef84496 100644 --- a/pkg/controller/servicebindingrequest/binder_test.go +++ b/pkg/controller/servicebindingrequest/binder_test.go @@ -162,6 +162,41 @@ func TestBinderNew(t *testing.T) { }) } +func TestEmptyApplicationSelectors(t *testing.T) { + + ns := "binder" + name := "service-binding-request" + + f := mocks.NewFake(t, ns) + sbr := f.AddMockedServiceBindingRequest(name, &ns, "ref", "", deploymentsGVR, map[string]string{}) + sbr.Spec.ApplicationSelector = nil + sbr.Spec.Applications = nil + + binder := NewBinder( + context.TODO(), + f.FakeClient(), + f.FakeDynClient(), + sbr, + []string{}, + ) + + t.Run("search no app specified", func(t *testing.T) { + list, err := binder.search() + require.Error(t, err) + require.Nil(t, list) + }) + + sbr = f.AddMockedServiceBindingRequest(name, &ns, "foo", "app", deploymentsGVR, map[string]string{}) + f.AddMockedUnstructuredDeployment("bar", map[string]string{}) + binder.sbr = sbr + + t.Run("search app not found", func(t *testing.T) { + list, err := binder.search() + require.Error(t, err) + require.Nil(t, list) + }) +} + func TestBinderAppendEnvVar(t *testing.T) { envName := "lastbound" envList := []corev1.EnvVar{ diff --git a/pkg/controller/servicebindingrequest/binding_test.go b/pkg/controller/servicebindingrequest/binding_test.go index d71ae35518..5c5177e075 100644 --- a/pkg/controller/servicebindingrequest/binding_test.go +++ b/pkg/controller/servicebindingrequest/binding_test.go @@ -230,6 +230,42 @@ func TestServiceBinder_Bind(t *testing.T) { } f.AddMockedSecret("db2") + sbrWithApplications := &v1alpha1.ServiceBindingRequest{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "apps.openshift.io/v1alpha1", + Kind: "ServiceBindingRequest", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "multiple-sbr-with-applications-param", + }, + Spec: v1alpha1.ServiceBindingRequestSpec{ + + Applications: &[]v1alpha1.ApplicationSelector{ + { + GroupVersionResource: metav1.GroupVersionResource{ + Group: d.GetObjectKind().GroupVersionKind().Group, + Version: d.GetObjectKind().GroupVersionKind().Version, + Resource: "deployments", + }, + ResourceRef: d.GetName(), + }, + }, + Services: &[]v1alpha1.BackingServiceSelector{ + { + GroupVersionKind: metav1.GroupVersionKind{ + Group: db1.GetObjectKind().GroupVersionKind().Group, + Version: db1.GetObjectKind().GroupVersionKind().Version, + Kind: db1.GetObjectKind().GroupVersionKind().Kind, + }, + ResourceRef: db1.GetName(), + }, + }, + }, + Status: v1alpha1.ServiceBindingRequestStatus{}, + } + + f.AddMockResource(sbrWithApplications) + // create the ServiceBindingRequest sbrSingleService := &v1alpha1.ServiceBindingRequest{ TypeMeta: metav1.TypeMeta{ @@ -240,7 +276,7 @@ func TestServiceBinder_Bind(t *testing.T) { Name: "single-sbr", }, Spec: v1alpha1.ServiceBindingRequestSpec{ - ApplicationSelector: v1alpha1.ApplicationSelector{ + ApplicationSelector: &v1alpha1.ApplicationSelector{ LabelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, }, @@ -274,7 +310,7 @@ func TestServiceBinder_Bind(t *testing.T) { Name: "single-sbr-with-customenvvar", }, Spec: v1alpha1.ServiceBindingRequestSpec{ - ApplicationSelector: v1alpha1.ApplicationSelector{ + ApplicationSelector: &v1alpha1.ApplicationSelector{ LabelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, }, @@ -320,7 +356,7 @@ func TestServiceBinder_Bind(t *testing.T) { Name: "multiple-sbr", }, Spec: v1alpha1.ServiceBindingRequestSpec{ - ApplicationSelector: v1alpha1.ApplicationSelector{ + ApplicationSelector: &v1alpha1.ApplicationSelector{ LabelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, }, @@ -363,7 +399,6 @@ func TestServiceBinder_Bind(t *testing.T) { Name: "empty-app-selector", }, Spec: v1alpha1.ServiceBindingRequestSpec{ - ApplicationSelector: v1alpha1.ApplicationSelector{}, BackingServiceSelectors: &[]v1alpha1.BackingServiceSelector{ { GroupVersionKind: metav1.GroupVersionKind{ @@ -388,7 +423,7 @@ func TestServiceBinder_Bind(t *testing.T) { Name: "empty-bss", }, Spec: v1alpha1.ServiceBindingRequestSpec{ - ApplicationSelector: v1alpha1.ApplicationSelector{ + ApplicationSelector: &v1alpha1.ApplicationSelector{ LabelSelector: &metav1.LabelSelector{ MatchLabels: matchLabels, }, @@ -586,4 +621,39 @@ func TestServiceBinder_Bind(t *testing.T) { }, }, })) + + t.Run("multiple services with applications : bind golden path", assertBind(args{ + options: &ServiceBinderOptions{ + Logger: logger, + DynClient: f.FakeDynClient(), + DetectBindingResources: false, + EnvVarPrefix: "", + SBR: sbrWithApplications, + Client: f.FakeClient(), + }, + wantConditions: []wantedCondition{ + { + Type: conditions.BindingReady, + Status: corev1.ConditionTrue, + }, + }, + wantActions: []wantedAction{ + { + resource: "servicebindingrequests", + verb: "update", + name: sbrWithApplications.GetName(), + }, + { + resource: "secrets", + verb: "update", + name: sbrWithApplications.GetName(), + }, + { + resource: "databases", + verb: "update", + name: db1.GetName(), + }, + }, + })) + } diff --git a/pkg/controller/servicebindingrequest/planner.go b/pkg/controller/servicebindingrequest/planner.go index 16248ce37d..af3d77a72f 100644 --- a/pkg/controller/servicebindingrequest/planner.go +++ b/pkg/controller/servicebindingrequest/planner.go @@ -85,6 +85,9 @@ func (p *Planner) Plan() (*Plan, error) { if p.sbr.Spec.BackingServiceSelectors != nil { selectors = append(selectors, *p.sbr.Spec.BackingServiceSelectors...) } + if p.sbr.Spec.Services != nil { + selectors = append(selectors, *p.sbr.Spec.Services...) + } if len(selectors) == 0 { return nil, EmptyBackingServiceSelectorsErr diff --git a/pkg/controller/servicebindingrequest/planner_test.go b/pkg/controller/servicebindingrequest/planner_test.go index be13311cff..e0c2b35a71 100644 --- a/pkg/controller/servicebindingrequest/planner_test.go +++ b/pkg/controller/servicebindingrequest/planner_test.go @@ -5,26 +5,89 @@ import ( "testing" routev1 "github.com/openshift/api/route/v1" - "github.com/stretchr/testify/require" + + //metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" "github.com/redhat-developer/service-binding-operator/pkg/apis/apps/v1alpha1" "github.com/redhat-developer/service-binding-operator/test/mocks" + "github.com/stretchr/testify/require" + logf "sigs.k8s.io/controller-runtime/pkg/log/zap" ) var planner *Planner func init() { - logf.SetLogger(logf.ZapLogger(true)) + logf.Logger(true) } func TestPlanner(t *testing.T) { ns := "planner" name := "service-binding-request" resourceRef := "db-testing" + + f := mocks.NewFake(t, ns) + + // This test is here to stay, we may rename the function after + // deprecated fields are removed completely. + sbr := f.AddMockedServiceBindingRequestV1_1(name, resourceRef, "", deploymentsGVR) + + f.AddMockedUnstructuredCSV("cluster-service-version") + f.AddMockedDatabaseCR(resourceRef, ns) + f.AddMockedUnstructuredDatabaseCRD() + + planner = NewPlanner(context.TODO(), f.FakeDynClient(), sbr) + require.NotNil(t, planner) + + t.Run("searchCR", func(t *testing.T) { + + for _, service := range *sbr.Spec.Services { + cr, err := planner.searchCR(service) + require.NoError(t, err) + require.NotNil(t, cr) + } + + }) + + t.Run("plan", func(t *testing.T) { + plan, err := planner.Plan() + + require.NoError(t, err) + require.NotNil(t, plan) + require.NotEmpty(t, plan.RelatedResources) + require.Equal(t, ns, plan.Ns) + require.Equal(t, name, plan.Name) + }) + +} + +func TestPlannerAnnotation(t *testing.T) { + ns := "planner" + name := "service-binding-request" + resourceRef := "db-testing" + + f := mocks.NewFake(t, ns) + sbr := f.AddMockedServiceBindingRequestV1_1(name, resourceRef, "", deploymentsGVR) + f.AddMockedUnstructuredDatabaseCRD() + cr := f.AddMockedDatabaseCR("database", ns) + + planner = NewPlanner(context.TODO(), f.FakeDynClient(), sbr) + require.NotNil(t, planner) + + t.Run("searchCRD", func(t *testing.T) { + crd, err := planner.searchCRD(cr.GetObjectKind().GroupVersionKind()) + + require.NoError(t, err) + require.NotNil(t, crd) + }) +} + +func TestPlannerDeprecacted(t *testing.T) { + ns := "planner" + name := "service-binding-request" + resourceRef := "db-testing" matchLabels := map[string]string{ "connects-to": "database", "environment": "planner", @@ -111,7 +174,7 @@ func TestPlannerWithExplicitBackingServiceNamespace(t *testing.T) { }) } -func TestPlannerAnnotation(t *testing.T) { +func TestPlannerAnnotationDeprecated(t *testing.T) { ns := "planner" name := "service-binding-request" resourceRef := "db-testing" @@ -158,7 +221,7 @@ func TestPlannerWithCRAnnotations(t *testing.T) { Name: name, }, Spec: v1alpha1.ServiceBindingRequestSpec{ - ApplicationSelector: v1alpha1.ApplicationSelector{ + ApplicationSelector: &v1alpha1.ApplicationSelector{ GroupVersionResource: metav1.GroupVersionResource{Group: "g", Version: "v", Resource: "r"}, ResourceRef: "app", }, diff --git a/test/mocks/fake.go b/test/mocks/fake.go index f76f809ae6..be70fc598c 100644 --- a/test/mocks/fake.go +++ b/test/mocks/fake.go @@ -75,6 +75,54 @@ func (f *Fake) AddMockedUnstructuredServiceBindingRequest( return sbr } +// Add new APIs + +// AddMockedServiceBindingRequestV1_1 adds a mocked object from ServiceBindingRequestMockV1_1. +// This function returns a ServiceBindingRequest with the newer API +// consisting of "applications" & "services". +func (f *Fake) AddMockedServiceBindingRequestV1_1( + name string, + backingServiceResourceRef string, + applicationResourceRef string, + applicationGVR schema.GroupVersionResource, +) *v1alpha1.ServiceBindingRequest { + f.S.AddKnownTypes(v1alpha1.SchemeGroupVersion, &v1alpha1.ServiceBindingRequest{}) + sbr := ServiceBindingRequestMockAlphaV1_1(f.ns, name, backingServiceResourceRef, applicationResourceRef, applicationGVR) + f.objs = append(f.objs, sbr) + return sbr +} + +// AddMockedServiceBindingRequestWithUnannotatedV1_1 add mocked object from ServiceBindingRequestMock with DetectBindingResources. +// This function returns a ServiceBindingRequest with the newer API +// consisting of "applications" & "services". +func (f *Fake) AddMockedServiceBindingRequestWithUnannotatedV1_1( + name string, + backingServiceResourceRef string, + applicationResourceRef string, + applicationGVR schema.GroupVersionResource, +) *v1alpha1.ServiceBindingRequest { + f.S.AddKnownTypes(v1alpha1.SchemeGroupVersion, &v1alpha1.ServiceBindingRequest{}) + sbr := ServiceBindingRequestMockAlphaV1_1(f.ns, name, backingServiceResourceRef, applicationResourceRef, applicationGVR) + f.objs = append(f.objs, sbr) + return sbr +} + +// AddMockedUnstructuredServiceBindingRequestV1_1 creates a mock ServiceBindingRequest object +// This function returns a ServiceBindingRequest with the newer API +// consisting of "applications" & "services". +func (f *Fake) AddMockedUnstructuredServiceBindingRequestV1_1( + name string, + backingServiceResourceRef string, + applicationResourceRef string, + applicationGVR schema.GroupVersionResource, +) *unstructured.Unstructured { + f.S.AddKnownTypes(v1alpha1.SchemeGroupVersion, &v1alpha1.ServiceBindingRequest{}) + sbr, err := UnstructuredServiceBindingRequestMockAlphaV1_1(f.ns, name, backingServiceResourceRef, applicationResourceRef, applicationGVR) + require.NoError(f.t, err) + f.objs = append(f.objs, sbr) + return sbr +} + // AddMockedUnstructuredCSV add mocked unstructured CSV. func (f *Fake) AddMockedUnstructuredCSV(name string) { require.NoError(f.t, olmv1alpha1.AddToScheme(f.S)) diff --git a/test/mocks/mocks.go b/test/mocks/mocks.go index 0cfe6ce135..c6eba9bd5e 100644 --- a/test/mocks/mocks.go +++ b/test/mocks/mocks.go @@ -410,7 +410,7 @@ func MultiNamespaceServiceBindingRequestMock( Value: "spec.imagePath", }, }, - ApplicationSelector: v1alpha1.ApplicationSelector{ + ApplicationSelector: &v1alpha1.ApplicationSelector{ GroupVersionResource: metav1.GroupVersionResource{Group: applicationGVR.Group, Version: applicationGVR.Version, Resource: applicationGVR.Resource}, ResourceRef: applicationResourceRef, LabelSelector: &metav1.LabelSelector{MatchLabels: matchLabels}, @@ -449,7 +449,7 @@ func ServiceBindingRequestMock( Value: "spec.imagePath", }, }, - ApplicationSelector: v1alpha1.ApplicationSelector{ + ApplicationSelector: &v1alpha1.ApplicationSelector{ GroupVersionResource: metav1.GroupVersionResource{Group: applicationGVR.Group, Version: applicationGVR.Version, Resource: applicationGVR.Resource}, ResourceRef: applicationResourceRef, LabelSelector: &metav1.LabelSelector{MatchLabels: matchLabels}, @@ -478,6 +478,63 @@ func UnstructuredServiceBindingRequestMock( return converter.ToUnstructuredAsGVK(&sbr, v1alpha1.SchemeGroupVersion.WithKind(OperatorKind)) } +// ServiceBindingRequestMockAlphaV1_1 return a binding-request mock of informed name and match labels. +// This function returns a ServiceBindingRequest with the newer API +// consisting of "applications" & "services". +func ServiceBindingRequestMockAlphaV1_1( + ns string, + name string, + backingServiceResourceRef string, + applicationResourceRef string, + applicationGVR schema.GroupVersionResource, +) *v1alpha1.ServiceBindingRequest { + sbr := &v1alpha1.ServiceBindingRequest{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: name, + }, + Spec: v1alpha1.ServiceBindingRequestSpec{ + MountPathPrefix: "/var/redhat", + CustomEnvVar: []corev1.EnvVar{ + { + Name: "IMAGE_PATH", + Value: "spec.imagePath", + }, + }, + Applications: &[]v1alpha1.ApplicationSelector{ + { + GroupVersionResource: metav1.GroupVersionResource{Group: applicationGVR.Group, Version: applicationGVR.Version, Resource: applicationGVR.Resource}, + ResourceRef: applicationResourceRef, + }, + }, + DetectBindingResources: false, + + Services: &[]v1alpha1.BackingServiceSelector{ + { + GroupVersionKind: metav1.GroupVersionKind{Group: CRDName, Version: CRDVersion, Kind: CRDKind}, + ResourceRef: backingServiceResourceRef, + Namespace: &ns, + }, + }, + }, + } + return sbr +} + +// UnstructuredServiceBindingRequestMockAlphaV1_1 returns a unstructured version of SBR. +// This function returns a ServiceBindingRequest with the newer API +// consisting of "applications" & "services". +func UnstructuredServiceBindingRequestMockAlphaV1_1( + ns string, + name string, + backingServiceResourceRef string, + applicationResourceRef string, + applicationGVR schema.GroupVersionResource, +) (*unstructured.Unstructured, error) { + sbr := ServiceBindingRequestMockAlphaV1_1(ns, name, backingServiceResourceRef, applicationResourceRef, applicationGVR) + return converter.ToUnstructuredAsGVK(&sbr, v1alpha1.SchemeGroupVersion.WithKind(OperatorKind)) +} + // DeploymentConfigListMock returns a list of DeploymentMock. func DeploymentConfigListMock(ns, name string, matchLabels map[string]string) ocav1.DeploymentConfigList { return ocav1.DeploymentConfigList{