Skip to content

Commit

Permalink
Implement unbind from managed service instances
Browse files Browse the repository at this point in the history
fixes #3296

Co-authored-by: Danail Branekov <[email protected]>
  • Loading branch information
zabanov-lab and danail-branekov committed Nov 1, 2024
1 parent 3e88807 commit 56e6701
Show file tree
Hide file tree
Showing 37 changed files with 1,209 additions and 368 deletions.
1 change: 1 addition & 0 deletions api/handlers/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
ManagedServiceInstanceDeleteJobType = "managed_service_instance.delete"
ManagedServiceInstanceCreateJobType = "managed_service_instance.create"
ManagedServiceBindingCreateJobType = "managed_service_binding.create"
ManagedServiceBindingDeleteJobType = "managed_service_binding.delete"
JobTimeoutDuration = 120.0
)

Expand Down
52 changes: 24 additions & 28 deletions api/handlers/service_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,53 +78,49 @@ func (h *ServiceBinding) create(r *http.Request) (*routing.Response, error) {

ctx := logr.NewContext(r.Context(), logger.WithValues("app", app.GUID, "service-instance", serviceInstance.GUID))

if serviceInstance.Type == korifiv1alpha1.ManagedType {
return h.createManagedServiceBinding(ctx, authInfo, payload, app)
}

return h.createUserProvidedServiceBinding(ctx, authInfo, payload, app)
}

func (h *ServiceBinding) createUserProvidedServiceBinding(
ctx context.Context,
authInfo authorization.Info,
payload payloads.ServiceBindingCreate,
app repositories.AppRecord,
) (*routing.Response, error) {
serviceBinding, err := h.serviceBindingRepo.CreateServiceBinding(ctx, authInfo, payload.ToMessage(app.SpaceGUID))
if err != nil {
return nil, apierrors.LogAndReturn(logr.FromContextOrDiscard(ctx), err, "failed to create ServiceBinding")
}

return routing.NewResponse(http.StatusCreated).WithBody(presenter.ForServiceBinding(serviceBinding, h.serverURL)), nil
}

func (h *ServiceBinding) createManagedServiceBinding(
ctx context.Context,
authInfo authorization.Info,
payload payloads.ServiceBindingCreate,
app repositories.AppRecord,
) (*routing.Response, error) {
serviceBinding, err := h.serviceBindingRepo.CreateServiceBinding(ctx, authInfo, payload.ToMessage(app.SpaceGUID))
if err != nil {
return nil, apierrors.LogAndReturn(logr.FromContextOrDiscard(ctx), err, "failed to create ServiceBinding")
if serviceInstance.Type == korifiv1alpha1.ManagedType {
return routing.NewResponse(http.StatusAccepted).
WithHeader("Location", presenter.JobURLForRedirects(serviceBinding.GUID, presenter.ManagedServiceBindingCreateOperation, h.serverURL)), nil
}

return routing.NewResponse(http.StatusAccepted).
WithHeader("Location", presenter.JobURLForRedirects(serviceBinding.GUID, presenter.ManagedServiceBindingCreateOperation, h.serverURL)), nil
return routing.NewResponse(http.StatusCreated).WithBody(presenter.ForServiceBinding(serviceBinding, h.serverURL)), nil
}

func (h *ServiceBinding) delete(r *http.Request) (*routing.Response, error) {
authInfo, _ := authorization.InfoFromContext(r.Context())
logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.service-binding.delete")

serviceBindingGUID := routing.URLParam(r, "guid")
serviceBinding, err := h.serviceBindingRepo.GetServiceBinding(r.Context(), authInfo, serviceBindingGUID)
if err != nil {
return nil, apierrors.LogAndReturn(logger, apierrors.ForbiddenAsNotFound(err), "failed to get "+repositories.ServiceBindingResourceType)
}

serviceInstance, err := h.serviceInstanceRepo.GetServiceInstance(r.Context(), authInfo, serviceBinding.ServiceInstanceGUID)
if err != nil {
return nil, apierrors.LogAndReturn(
logger,
apierrors.NewUnprocessableEntityError(err, "failed to get service instance"),
"failed to get "+repositories.ServiceInstanceResourceType,
"instance-guid", serviceBinding.ServiceInstanceGUID,
)
}

err := h.serviceBindingRepo.DeleteServiceBinding(r.Context(), authInfo, serviceBindingGUID)
err = h.serviceBindingRepo.DeleteServiceBinding(r.Context(), authInfo, serviceBindingGUID)
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "error when deleting service binding", "guid", serviceBindingGUID)
}

if serviceInstance.Type == korifiv1alpha1.ManagedType {
return routing.NewResponse(http.StatusAccepted).
WithHeader("Location", presenter.JobURLForRedirects(serviceBinding.GUID, presenter.ManagedServiceBindingDeleteOperation, h.serverURL)), nil
}

return routing.NewResponse(http.StatusNoContent), nil
}

Expand Down
78 changes: 77 additions & 1 deletion api/handlers/service_binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ var _ = Describe("ServiceBinding", func() {
BeforeEach(func() {
serviceBindingRepo = new(fake.CFServiceBindingRepository)
serviceBindingRepo.GetServiceBindingReturns(repositories.ServiceBindingRecord{
GUID: "service-binding-guid",
GUID: "service-binding-guid",
ServiceInstanceGUID: "service-instance-guid",
}, nil)

appRepo = new(fake.CFAppRepository)
Expand All @@ -46,6 +47,7 @@ var _ = Describe("ServiceBinding", func() {
serviceInstanceRepo.GetServiceInstanceReturns(repositories.ServiceInstanceRecord{
GUID: "service-instance-guid",
SpaceGUID: "space-guid",
Type: korifiv1alpha1.UserProvidedType,
}, nil)

requestValidator = new(fake.RequestValidator)
Expand Down Expand Up @@ -376,6 +378,50 @@ var _ = Describe("ServiceBinding", func() {
requestPath = "/v3/service_credential_bindings/service-binding-guid"
})

It("gets the service binding", func() {
Expect(serviceBindingRepo.GetServiceBindingCallCount()).To(Equal(1))
_, actualAuthInfo, actualBindingGUID := serviceBindingRepo.GetServiceBindingArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))
Expect(actualBindingGUID).To(Equal("service-binding-guid"))
})

When("getting the service binding is forbidden", func() {
BeforeEach(func() {
serviceBindingRepo.GetServiceBindingReturns(repositories.ServiceBindingRecord{}, apierrors.NewForbiddenError(nil, repositories.ServiceBindingResourceType))
})

It("returns a not found error", func() {
expectNotFoundError(repositories.ServiceBindingResourceType)
})
})

When("getting the service binding fails", func() {
BeforeEach(func() {
serviceBindingRepo.GetServiceBindingReturns(repositories.ServiceBindingRecord{}, errors.New("getting-binding-failed"))
})

It("returns unknown error", func() {
expectUnknownError()
})
})

It("gets the service instance", func() {
Expect(serviceInstanceRepo.GetServiceInstanceCallCount()).To(Equal(1))
_, actualAuthInfo, actualInstanceGUID := serviceInstanceRepo.GetServiceInstanceArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))
Expect(actualInstanceGUID).To(Equal("service-instance-guid"))
})

When("getting the service instance fails", func() {
BeforeEach(func() {
serviceInstanceRepo.GetServiceInstanceReturns(repositories.ServiceInstanceRecord{}, errors.New("getting-instance-failed"))
})

It("returns error", func() {
expectUnprocessableEntityError("failed to get service instance")
})
})

It("deletes the service binding", func() {
Expect(rr).To(HaveHTTPStatus(http.StatusNoContent))
Expect(rr).To(HaveHTTPBody(BeEmpty()))
Expand All @@ -384,6 +430,36 @@ var _ = Describe("ServiceBinding", func() {
_, _, guid := serviceBindingRepo.DeleteServiceBindingArgsForCall(0)
Expect(guid).To(Equal("service-binding-guid"))
})

When("the service instance is managed", func() {
BeforeEach(func() {
serviceInstanceRepo.GetServiceInstanceReturns(repositories.ServiceInstanceRecord{
GUID: "service-instance-guid",
SpaceGUID: "space-guid",
Type: korifiv1alpha1.ManagedType,
}, nil)
})

It("deletes the binding in a job", func() {
Expect(serviceBindingRepo.DeleteServiceBindingCallCount()).To(Equal(1))
_, _, guid := serviceBindingRepo.DeleteServiceBindingArgsForCall(0)
Expect(guid).To(Equal("service-binding-guid"))

Expect(rr).To(HaveHTTPStatus(http.StatusAccepted))
Expect(rr).To(HaveHTTPHeaderWithValue("Location",
ContainSubstring("/v3/jobs/managed_service_binding.delete~service-binding-guid")))
})
})

When("deleting the service binding fails", func() {
BeforeEach(func() {
serviceBindingRepo.DeleteServiceBindingReturns(errors.New("delete-binding-failed"))
})

It("returns unknown error", func() {
expectUnknownError()
})
})
})

Describe("PATCH /v3/service_credential_bindings/:guid", func() {
Expand Down
1 change: 1 addition & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ func main() {
handlers.RoleDeleteJobType: roleRepo,
handlers.ServiceBrokerDeleteJobType: serviceBrokerRepo,
handlers.ManagedServiceInstanceDeleteJobType: serviceInstanceRepo,
handlers.ManagedServiceBindingDeleteJobType: serviceBindingRepo,
},
map[string]handlers.StateRepository{
handlers.ServiceBrokerCreateJobType: serviceBrokerRepo,
Expand Down
24 changes: 13 additions & 11 deletions api/presenter/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,21 @@ const (
StateFailed = "FAILED"
StateProcessing = "PROCESSING"

AppDeleteOperation = "app.delete"
OrgDeleteOperation = "org.delete"
RouteDeleteOperation = "route.delete"
SpaceApplyManifestOperation = "space.apply_manifest"
SpaceDeleteOperation = "space.delete"
DomainDeleteOperation = "domain.delete"
RoleDeleteOperation = "role.delete"
ServiceBrokerCreateOperation = "service_broker.create"
ServiceBrokerDeleteOperation = "service_broker.delete"
ServiceBrokerUpdateOperation = "service_broker.update"
AppDeleteOperation = "app.delete"
OrgDeleteOperation = "org.delete"
RouteDeleteOperation = "route.delete"
SpaceApplyManifestOperation = "space.apply_manifest"
SpaceDeleteOperation = "space.delete"
DomainDeleteOperation = "domain.delete"
RoleDeleteOperation = "role.delete"
ServiceBrokerCreateOperation = "service_broker.create"
ServiceBrokerDeleteOperation = "service_broker.delete"
ServiceBrokerUpdateOperation = "service_broker.update"

ManagedServiceInstanceCreateOperation = "managed_service_instance.create"
ManagedServiceBindingCreateOperation = "managed_service_binding.create"
ManagedServiceInstanceDeleteOperation = "managed_service_instance.delete"
ManagedServiceBindingCreateOperation = "managed_service_binding.create"
ManagedServiceBindingDeleteOperation = "managed_service_binding.delete"
)

var (
Expand Down
10 changes: 10 additions & 0 deletions api/repositories/service_binding_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type ServiceBindingRecord struct {
Annotations map[string]string
CreatedAt time.Time
UpdatedAt *time.Time
DeletedAt *time.Time
LastOperation ServiceBindingLastOperation
Ready bool
}
Expand Down Expand Up @@ -238,6 +239,7 @@ func serviceBindingToRecord(binding korifiv1alpha1.CFServiceBinding) ServiceBind
Annotations: binding.Annotations,
CreatedAt: binding.CreationTimestamp.Time,
UpdatedAt: getLastUpdatedTime(&binding),
DeletedAt: golangTime(binding.DeletionTimestamp),
LastOperation: serviceBindingRecordLastOperation(binding),
Ready: isBindingReady(binding),
}
Expand Down Expand Up @@ -343,6 +345,14 @@ func (r *ServiceBindingRepo) GetState(ctx context.Context, authInfo authorizatio
return model.CFResourceStateUnknown, nil
}

func (r *ServiceBindingRepo) GetDeletedAt(ctx context.Context, authInfo authorization.Info, bindingGUID string) (*time.Time, error) {
serviceBinding, err := r.GetServiceBinding(ctx, authInfo, bindingGUID)
if err != nil {
return nil, err
}
return serviceBinding.DeletedAt, nil
}

// nolint:dupl
func (r *ServiceBindingRepo) ListServiceBindings(ctx context.Context, authInfo authorization.Info, message ListServiceBindingsMessage) ([]ServiceBindingRecord, error) {
userClient, err := r.userClientFactory.BuildClient(authInfo)
Expand Down
Loading

0 comments on commit 56e6701

Please sign in to comment.