Skip to content

Commit

Permalink
feat: implement service_instances and service_bindings endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
kanngiesser committed Nov 25, 2024
1 parent 24eb991 commit fdc049c
Show file tree
Hide file tree
Showing 10 changed files with 894 additions and 76 deletions.
7 changes: 0 additions & 7 deletions brokerapi/broker/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package broker

import (
"context"
"errors"
"fmt"
"net/http"

"code.cloudfoundry.org/lager/v3"
"github.com/cloudfoundry/cloud-service-broker/v2/internal/paramparser"
Expand All @@ -18,11 +16,6 @@ import (
"github.com/pivotal-cf/brokerapi/v11/domain/apiresponses"
)

var (
invalidUserInputMsg = "User supplied parameters must be in the form of a valid JSON map."
ErrInvalidUserInput = apiresponses.NewFailureResponse(errors.New(invalidUserInputMsg), http.StatusBadRequest, "parsing-user-request")
)

// Bind creates an account with credentials to access an instance of a service.
// It is bound to the `PUT /v2/service_instances/:instance_id/service_bindings/:binding_id` endpoint and can be called using the `cf bind-service` command.
func (broker *ServiceBroker) Bind(ctx context.Context, instanceID, bindingID string, details domain.BindDetails, _ bool) (domain.Binding, error) {
Expand Down
30 changes: 30 additions & 0 deletions brokerapi/broker/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package broker

import (
"errors"
"net/http"

"github.com/pivotal-cf/brokerapi/v11/domain/apiresponses"
)

// typed errors which are not declared in pivotal-cf/brokerapi

var (
badRequestMsg = "bad request"
invalidUserInputMsg = "User supplied parameters must be in the form of a valid JSON map."
nonUpdateableParameterMsg = "attempt to update parameter that may result in service instance re-creation and data loss"
notFoundMsg = "not found"
concurrencyErrorMsg = "ConcurrencyError"

badRequestKey = "bad-request"
invalidUserInputKey = "parsing-user-request"
nonUpdatableParameterKey = "prohibited"
notFoundKey = "not-found"
concurrencyErrorKey = "concurrency-error"

ErrBadRequest = apiresponses.NewFailureResponse(errors.New(badRequestMsg), http.StatusBadRequest, badRequestKey)
ErrInvalidUserInput = apiresponses.NewFailureResponse(errors.New(invalidUserInputMsg), http.StatusBadRequest, invalidUserInputKey)
ErrNonUpdatableParameter = apiresponses.NewFailureResponse(errors.New(nonUpdateableParameterMsg), http.StatusBadRequest, nonUpdatableParameterKey)
ErrNotFound = apiresponses.NewFailureResponse(errors.New(notFoundMsg), http.StatusNotFound, notFoundKey)
ErrConcurrencyError = apiresponses.NewFailureResponse(errors.New(concurrencyErrorMsg), http.StatusUnprocessableEntity, concurrencyErrorKey)
)
75 changes: 65 additions & 10 deletions brokerapi/broker/get_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,83 @@ package broker

import (
"context"
"errors"
"net/http"
"fmt"

"code.cloudfoundry.org/lager/v3"
"github.com/pivotal-cf/brokerapi/v11/domain"
"github.com/pivotal-cf/brokerapi/v11/domain/apiresponses"

"github.com/cloudfoundry/cloud-service-broker/v2/utils/correlation"
)

// GetBinding fetches an existing service binding.
// GET /v2/service_instances/{instance_id}/service_bindings/{binding_id}
//
// NOTE: This functionality is not implemented.
func (broker *ServiceBroker) GetBinding(ctx context.Context, instanceID, bindingID string, _ domain.FetchBindingDetails) (domain.GetBindingSpec, error) {
func (broker *ServiceBroker) GetBinding(ctx context.Context, instanceID, bindingID string, details domain.FetchBindingDetails) (domain.GetBindingSpec, error) {
broker.Logger.Info("GetBinding", correlation.ID(ctx), lager.Data{
"instance_id": instanceID,
"binding_id": bindingID,
"service_id": details.ServiceID,
"plan_id": details.PlanID,
})

return domain.GetBindingSpec{}, apiresponses.NewFailureResponse(
errors.New("the service_bindings endpoint is unsupported"),
http.StatusBadRequest,
"unsupported")
// check whether instance exists
instanceExists, err := broker.store.ExistsServiceInstanceDetails(instanceID)
if err != nil {
return domain.GetBindingSpec{}, fmt.Errorf("error checking for existing instance: %w", err)
}
if !instanceExists {
return domain.GetBindingSpec{}, ErrNotFound
}

// get instance details
instanceRecord, err := broker.store.GetServiceInstanceDetails(instanceID)
if err != nil {
return domain.GetBindingSpec{}, fmt.Errorf("error retrieving service instance details: %w", err)
}

// check whether request parameters (if not empty) match instance details
if len(details.ServiceID) > 0 && details.ServiceID != instanceRecord.ServiceGUID {
return domain.GetBindingSpec{}, ErrNotFound
}
if len(details.PlanID) > 0 && details.PlanID != instanceRecord.PlanGUID {
return domain.GetBindingSpec{}, ErrNotFound
}

// check whether service plan is bindable
serviceDefinition, _, err := broker.getDefinitionAndProvider(instanceRecord.ServiceGUID)
if err != nil {
return domain.GetBindingSpec{}, fmt.Errorf("error retrieving service definition: %w", err)
}
if !serviceDefinition.Bindable {
return domain.GetBindingSpec{}, ErrBadRequest
}

// check whether binding exists
// with the current implementation, bind is a synchroneous operation which waits for all resources to be created before binding credentials are stored
// therefore, we can assume the binding operation is completed if it exists at the store
bindingExists, err := broker.store.ExistsServiceBindingCredentials(bindingID, instanceID)
if err != nil {
return domain.GetBindingSpec{}, fmt.Errorf("error checking for existing binding: %w", err)
}
if !bindingExists {
return domain.GetBindingSpec{}, ErrNotFound
}

// get binding parameters
params, err := broker.store.GetBindRequestDetails(bindingID, instanceID)
if err != nil {
return domain.GetBindingSpec{}, fmt.Errorf("error retrieving bind request details: %w", err)
}

// broker does not support Log Drain, Route Services, or Volume Mounts
// broker does not support binding metadata
// credentials are returned with synchronous bind request
return domain.GetBindingSpec{
Credentials: nil,
SyslogDrainURL: "",
RouteServiceURL: "",
VolumeMounts: nil,
Parameters: params,
Endpoints: nil,
Metadata: domain.BindingMetadata{},
}, nil
}
Loading

0 comments on commit fdc049c

Please sign in to comment.