diff --git a/controllers/controllers/services/osbapi/client.go b/controllers/controllers/services/osbapi/client.go index bfa020df8..5bbc9d89b 100644 --- a/controllers/controllers/services/osbapi/client.go +++ b/controllers/controllers/services/osbapi/client.go @@ -298,6 +298,36 @@ func (c *Client) Unbind(ctx context.Context, payload UnbindPayload) (UnbindRespo return response, nil } +func (c *Client) GetServiceBinding(ctx context.Context, payload BindPayload) (BindingResponse, error) { + statusCode, respBytes, err := c.newBrokerRequester(). + forBroker(c.broker). + sendRequest( + ctx, + "/v2/service_instances/"+payload.InstanceID+"/service_bindings/"+payload.BindingID, + http.MethodGet, + map[string]string{ + "service_id": payload.ServiceId, + "plan_id": payload.PlanID, + }, + nil, + ) + if err != nil { + return BindingResponse{}, fmt.Errorf("fetching service binding failed: %w", err) + } + + if statusCode == http.StatusNotFound { + return BindingResponse{}, UnrecoverableError{Status: statusCode} + } + + response := BindingResponse{} + err = json.Unmarshal(respBytes, &response) + if err != nil { + return BindingResponse{}, fmt.Errorf("failed to unmarshal response: %w", err) + } + + return response, nil +} + func payloadToReader(payload any) (io.Reader, error) { if payload == nil { return nil, nil diff --git a/controllers/controllers/services/osbapi/client_test.go b/controllers/controllers/services/osbapi/client_test.go index a72ce1c24..76003bc4e 100644 --- a/controllers/controllers/services/osbapi/client_test.go +++ b/controllers/controllers/services/osbapi/client_test.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + "fmt" "io" "net/http" "strconv" @@ -619,6 +620,67 @@ var _ = Describe("OSBAPI Client", func() { }) }) + Describe("GetServiceBinding", func() { + var ( + bindingResp osbapi.BindingResponse + getBindErr error + ) + BeforeEach(func() { + brokerServer.WithResponse( + "/v2/service_instances/{instance_id}/service_bindings/{binding_id}", + map[string]any{ + "parameters": map[string]string{ + "billing-account": "abcde12345", + }, + }, + http.StatusOK, + ) + }) + JustBeforeEach(func() { + bindingResp, getBindErr = brokerClient.GetServiceBinding(ctx, osbapi.BindPayload{ + InstanceID: "my-service-instance", + BindingID: "my-binding-id", + BindRequest: osbapi.BindRequest{ + ServiceId: "my-service-offering-id", + PlanID: "my-plan-id", + }, + }) + }) + + It("gets the service binding", func() { + Expect(getBindErr).NotTo(HaveOccurred()) + + requests := brokerServer.ServedRequests() + Expect(requests).To(HaveLen(1)) + Expect(requests[0].Method).To(Equal(http.MethodGet)) + Expect(requests[0].URL.Path).To(Equal("/v2/service_instances/my-service-instance/service_bindings/my-binding-id")) + Expect(requests[0].URL.Query()).To(BeEquivalentTo(map[string][]string{ + "service_id": {"my-service-offering-id"}, + "plan_id": {"my-plan-id"}, + })) + + Expect(bindingResp).To(Equal(osbapi.BindingResponse{ + Parameters: map[string]any{ + "billing-account": "abcde12345", + }, + })) + }) + + When("the service binding does not exist", func() { + BeforeEach(func() { + brokerServer = brokerServer.WithResponse( + "/v2/service_instances/{instance_id}/service_bindings/{binding_id}", + nil, + http.StatusNotFound, + ) + }) + + It("returns an error", func() { + Expect(getBindErr).To(MatchError(ContainSubstring(fmt.Sprintf("The server responded with status: %d", http.StatusNotFound)))) + }) + }) + }) + Describe("GetServiceBindingLastOperation", func() { var ( lastOpResp osbapi.LastOperationResponse diff --git a/controllers/controllers/services/osbapi/clientfactory.go b/controllers/controllers/services/osbapi/clientfactory.go index 085a08c25..1dca44793 100644 --- a/controllers/controllers/services/osbapi/clientfactory.go +++ b/controllers/controllers/services/osbapi/clientfactory.go @@ -24,6 +24,7 @@ type BrokerClient interface { Bind(context.Context, BindPayload) (BindResponse, error) Unbind(context.Context, UnbindPayload) (UnbindResponse, error) GetServiceBindingLastOperation(context.Context, GetBindingLastOperationRequest) (LastOperationResponse, error) + GetServiceBinding(ctx context.Context, payload BindPayload) (BindingResponse, error) } //counterfeiter:generate -o fake -fake-name BrokerClientFactory code.cloudfoundry.org/korifi/controllers/controllers/services/osbapi.BrokerClientFactory diff --git a/controllers/controllers/services/osbapi/fake/broker_client.go b/controllers/controllers/services/osbapi/fake/broker_client.go index a273dda20..5d57200e7 100644 --- a/controllers/controllers/services/osbapi/fake/broker_client.go +++ b/controllers/controllers/services/osbapi/fake/broker_client.go @@ -50,18 +50,18 @@ type BrokerClient struct { result1 osbapi.Catalog result2 error } - GetServiceBindingStub func(context.Context, osbapi.GetBindingRequest) (osbapi.GetBindingResponse, error) + GetServiceBindingStub func(context.Context, osbapi.BindPayload) (osbapi.BindingResponse, error) getServiceBindingMutex sync.RWMutex getServiceBindingArgsForCall []struct { arg1 context.Context - arg2 osbapi.GetBindingRequest + arg2 osbapi.BindPayload } getServiceBindingReturns struct { - result1 osbapi.GetBindingResponse + result1 osbapi.BindingResponse result2 error } getServiceBindingReturnsOnCall map[int]struct { - result1 osbapi.GetBindingResponse + result1 osbapi.BindingResponse result2 error } GetServiceBindingLastOperationStub func(context.Context, osbapi.GetBindingLastOperationRequest) (osbapi.LastOperationResponse, error) @@ -318,12 +318,12 @@ func (fake *BrokerClient) GetCatalogReturnsOnCall(i int, result1 osbapi.Catalog, }{result1, result2} } -func (fake *BrokerClient) GetServiceBinding(arg1 context.Context, arg2 osbapi.GetBindingRequest) (osbapi.GetBindingResponse, error) { +func (fake *BrokerClient) GetServiceBinding(arg1 context.Context, arg2 osbapi.BindPayload) (osbapi.BindingResponse, error) { fake.getServiceBindingMutex.Lock() ret, specificReturn := fake.getServiceBindingReturnsOnCall[len(fake.getServiceBindingArgsForCall)] fake.getServiceBindingArgsForCall = append(fake.getServiceBindingArgsForCall, struct { arg1 context.Context - arg2 osbapi.GetBindingRequest + arg2 osbapi.BindPayload }{arg1, arg2}) stub := fake.GetServiceBindingStub fakeReturns := fake.getServiceBindingReturns @@ -344,41 +344,41 @@ func (fake *BrokerClient) GetServiceBindingCallCount() int { return len(fake.getServiceBindingArgsForCall) } -func (fake *BrokerClient) GetServiceBindingCalls(stub func(context.Context, osbapi.GetBindingRequest) (osbapi.GetBindingResponse, error)) { +func (fake *BrokerClient) GetServiceBindingCalls(stub func(context.Context, osbapi.BindPayload) (osbapi.BindingResponse, error)) { fake.getServiceBindingMutex.Lock() defer fake.getServiceBindingMutex.Unlock() fake.GetServiceBindingStub = stub } -func (fake *BrokerClient) GetServiceBindingArgsForCall(i int) (context.Context, osbapi.GetBindingRequest) { +func (fake *BrokerClient) GetServiceBindingArgsForCall(i int) (context.Context, osbapi.BindPayload) { fake.getServiceBindingMutex.RLock() defer fake.getServiceBindingMutex.RUnlock() argsForCall := fake.getServiceBindingArgsForCall[i] return argsForCall.arg1, argsForCall.arg2 } -func (fake *BrokerClient) GetServiceBindingReturns(result1 osbapi.GetBindingResponse, result2 error) { +func (fake *BrokerClient) GetServiceBindingReturns(result1 osbapi.BindingResponse, result2 error) { fake.getServiceBindingMutex.Lock() defer fake.getServiceBindingMutex.Unlock() fake.GetServiceBindingStub = nil fake.getServiceBindingReturns = struct { - result1 osbapi.GetBindingResponse + result1 osbapi.BindingResponse result2 error }{result1, result2} } -func (fake *BrokerClient) GetServiceBindingReturnsOnCall(i int, result1 osbapi.GetBindingResponse, result2 error) { +func (fake *BrokerClient) GetServiceBindingReturnsOnCall(i int, result1 osbapi.BindingResponse, result2 error) { fake.getServiceBindingMutex.Lock() defer fake.getServiceBindingMutex.Unlock() fake.GetServiceBindingStub = nil if fake.getServiceBindingReturnsOnCall == nil { fake.getServiceBindingReturnsOnCall = make(map[int]struct { - result1 osbapi.GetBindingResponse + result1 osbapi.BindingResponse result2 error }) } fake.getServiceBindingReturnsOnCall[i] = struct { - result1 osbapi.GetBindingResponse + result1 osbapi.BindingResponse result2 error }{result1, result2} } diff --git a/controllers/controllers/services/osbapi/types.go b/controllers/controllers/services/osbapi/types.go index 641a991dc..255c416e3 100644 --- a/controllers/controllers/services/osbapi/types.go +++ b/controllers/controllers/services/osbapi/types.go @@ -118,6 +118,10 @@ type BindResponse struct { IsAsync bool } +type BindingResponse struct { + Parameters map[string]any `json:"parameters"` +} + type BindResource struct { AppGUID string `json:"app_guid"` } diff --git a/controllers/controllers/workloads/build/fake/delegate_reconciler.go b/controllers/controllers/workloads/build/fake/delegate_reconciler.go index 292c33dd2..a83940580 100644 --- a/controllers/controllers/workloads/build/fake/delegate_reconciler.go +++ b/controllers/controllers/workloads/build/fake/delegate_reconciler.go @@ -7,13 +7,12 @@ import ( "code.cloudfoundry.org/korifi/controllers/api/v1alpha1" "code.cloudfoundry.org/korifi/controllers/controllers/workloads/build" + controllerruntime "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" ) type DelegateReconciler struct { - ReconcileBuildStub func(context.Context, *v1alpha1.CFBuild, *v1alpha1.CFApp, *v1alpha1.CFPackage) (reconcile.Result, error) + ReconcileBuildStub func(context.Context, *v1alpha1.CFBuild, *v1alpha1.CFApp, *v1alpha1.CFPackage) (controllerruntime.Result, error) reconcileBuildMutex sync.RWMutex reconcileBuildArgsForCall []struct { arg1 context.Context @@ -22,17 +21,17 @@ type DelegateReconciler struct { arg4 *v1alpha1.CFPackage } reconcileBuildReturns struct { - result1 reconcile.Result + result1 controllerruntime.Result result2 error } reconcileBuildReturnsOnCall map[int]struct { - result1 reconcile.Result + result1 controllerruntime.Result result2 error } - SetupWithManagerStub func(manager.Manager) *builder.Builder + SetupWithManagerStub func(controllerruntime.Manager) *builder.Builder setupWithManagerMutex sync.RWMutex setupWithManagerArgsForCall []struct { - arg1 manager.Manager + arg1 controllerruntime.Manager } setupWithManagerReturns struct { result1 *builder.Builder @@ -44,7 +43,7 @@ type DelegateReconciler struct { invocationsMutex sync.RWMutex } -func (fake *DelegateReconciler) ReconcileBuild(arg1 context.Context, arg2 *v1alpha1.CFBuild, arg3 *v1alpha1.CFApp, arg4 *v1alpha1.CFPackage) (reconcile.Result, error) { +func (fake *DelegateReconciler) ReconcileBuild(arg1 context.Context, arg2 *v1alpha1.CFBuild, arg3 *v1alpha1.CFApp, arg4 *v1alpha1.CFPackage) (controllerruntime.Result, error) { fake.reconcileBuildMutex.Lock() ret, specificReturn := fake.reconcileBuildReturnsOnCall[len(fake.reconcileBuildArgsForCall)] fake.reconcileBuildArgsForCall = append(fake.reconcileBuildArgsForCall, struct { @@ -72,7 +71,7 @@ func (fake *DelegateReconciler) ReconcileBuildCallCount() int { return len(fake.reconcileBuildArgsForCall) } -func (fake *DelegateReconciler) ReconcileBuildCalls(stub func(context.Context, *v1alpha1.CFBuild, *v1alpha1.CFApp, *v1alpha1.CFPackage) (reconcile.Result, error)) { +func (fake *DelegateReconciler) ReconcileBuildCalls(stub func(context.Context, *v1alpha1.CFBuild, *v1alpha1.CFApp, *v1alpha1.CFPackage) (controllerruntime.Result, error)) { fake.reconcileBuildMutex.Lock() defer fake.reconcileBuildMutex.Unlock() fake.ReconcileBuildStub = stub @@ -85,37 +84,37 @@ func (fake *DelegateReconciler) ReconcileBuildArgsForCall(i int) (context.Contex return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 } -func (fake *DelegateReconciler) ReconcileBuildReturns(result1 reconcile.Result, result2 error) { +func (fake *DelegateReconciler) ReconcileBuildReturns(result1 controllerruntime.Result, result2 error) { fake.reconcileBuildMutex.Lock() defer fake.reconcileBuildMutex.Unlock() fake.ReconcileBuildStub = nil fake.reconcileBuildReturns = struct { - result1 reconcile.Result + result1 controllerruntime.Result result2 error }{result1, result2} } -func (fake *DelegateReconciler) ReconcileBuildReturnsOnCall(i int, result1 reconcile.Result, result2 error) { +func (fake *DelegateReconciler) ReconcileBuildReturnsOnCall(i int, result1 controllerruntime.Result, result2 error) { fake.reconcileBuildMutex.Lock() defer fake.reconcileBuildMutex.Unlock() fake.ReconcileBuildStub = nil if fake.reconcileBuildReturnsOnCall == nil { fake.reconcileBuildReturnsOnCall = make(map[int]struct { - result1 reconcile.Result + result1 controllerruntime.Result result2 error }) } fake.reconcileBuildReturnsOnCall[i] = struct { - result1 reconcile.Result + result1 controllerruntime.Result result2 error }{result1, result2} } -func (fake *DelegateReconciler) SetupWithManager(arg1 manager.Manager) *builder.Builder { +func (fake *DelegateReconciler) SetupWithManager(arg1 controllerruntime.Manager) *builder.Builder { fake.setupWithManagerMutex.Lock() ret, specificReturn := fake.setupWithManagerReturnsOnCall[len(fake.setupWithManagerArgsForCall)] fake.setupWithManagerArgsForCall = append(fake.setupWithManagerArgsForCall, struct { - arg1 manager.Manager + arg1 controllerruntime.Manager }{arg1}) stub := fake.SetupWithManagerStub fakeReturns := fake.setupWithManagerReturns @@ -136,13 +135,13 @@ func (fake *DelegateReconciler) SetupWithManagerCallCount() int { return len(fake.setupWithManagerArgsForCall) } -func (fake *DelegateReconciler) SetupWithManagerCalls(stub func(manager.Manager) *builder.Builder) { +func (fake *DelegateReconciler) SetupWithManagerCalls(stub func(controllerruntime.Manager) *builder.Builder) { fake.setupWithManagerMutex.Lock() defer fake.setupWithManagerMutex.Unlock() fake.SetupWithManagerStub = stub } -func (fake *DelegateReconciler) SetupWithManagerArgsForCall(i int) manager.Manager { +func (fake *DelegateReconciler) SetupWithManagerArgsForCall(i int) controllerruntime.Manager { fake.setupWithManagerMutex.RLock() defer fake.setupWithManagerMutex.RUnlock() argsForCall := fake.setupWithManagerArgsForCall[i] diff --git a/tests/assets/sample-broker-golang/main.go b/tests/assets/sample-broker-golang/main.go index c523c7b9a..1a28185fb 100644 --- a/tests/assets/sample-broker-golang/main.go +++ b/tests/assets/sample-broker-golang/main.go @@ -31,6 +31,7 @@ func main() { http.HandleFunc("GET /v2/service_instances/{id}/last_operation", getLastOperationHandler) http.HandleFunc("PUT /v2/service_instances/{instance_id}/service_bindings/{binding_id}", bindHandler) + http.HandleFunc("GET /v2/service_instances/{instance_id}/service_bindings/{binding_id}", getBindingHandler) http.HandleFunc("DELETE /v2/service_instances/{instance_id}/service_bindings/{binding_id}", unbindHandler) http.HandleFunc("GET /v2/service_instances/{instance_id}/service_bindings/{binding_id}/last_operation", getLastOperationHandler) @@ -107,6 +108,21 @@ func deprovisionServiceInstanceHandler(w http.ResponseWriter, r *http.Request) { asyncOperation(w, fmt.Sprintf("deprovision-%s", r.PathValue("id")), "{}") } +func getBindingHandler(w http.ResponseWriter, r *http.Request) { + logRequest(r) + + if status, err := checkCredentials(w, r); err != nil { + respond(w, status, fmt.Sprintf("Credentials check failed: %v", err)) + return + } + + respond(w, http.StatusOK, `{ + "parameters": { + "billing-account": "abcde12345" + } + }`) +} + func bindHandler(w http.ResponseWriter, r *http.Request) { logRequest(r)