Skip to content

Commit

Permalink
Added getDetails handler and repo implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
ddraganovv committed Jan 27, 2025
1 parent fe267ac commit dedb18a
Show file tree
Hide file tree
Showing 7 changed files with 332 additions and 4 deletions.
83 changes: 83 additions & 0 deletions api/handlers/fake/cfservice_binding_repository.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 18 additions & 2 deletions api/handlers/service_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ import (
)

const (
ServiceBindingsPath = "/v3/service_credential_bindings"
ServiceBindingPath = "/v3/service_credential_bindings/{guid}"
ServiceBindingsPath = "/v3/service_credential_bindings"
ServiceBindingPath = "/v3/service_credential_bindings/{guid}"
ServiceBindingDetailsPath = ServiceBindingPath + "/details"
)

type ServiceBinding struct {
Expand All @@ -36,6 +37,7 @@ type CFServiceBindingRepository interface {
ListServiceBindings(context.Context, authorization.Info, repositories.ListServiceBindingsMessage) ([]repositories.ServiceBindingRecord, error)
GetServiceBinding(context.Context, authorization.Info, string) (repositories.ServiceBindingRecord, error)
UpdateServiceBinding(context.Context, authorization.Info, repositories.UpdateServiceBindingMessage) (repositories.ServiceBindingRecord, error)
GetServiceBindingDetails(context.Context, authorization.Info, string) (repositories.ServiceBindingDetailsRecord, error)
}

func NewServiceBinding(serverURL url.URL, serviceBindingRepo CFServiceBindingRepository, appRepo CFAppRepository, serviceInstanceRepo CFServiceInstanceRepository, requestValidator RequestValidator) *ServiceBinding {
Expand Down Expand Up @@ -220,6 +222,19 @@ func (h *ServiceBinding) get(r *http.Request) (*routing.Response, error) {
return routing.NewResponse(http.StatusOK).WithBody(presenter.ForServiceBinding(serviceBinding, h.serverURL)), nil
}

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

serviceBindingGUID := routing.URLParam(r, "guid")

bindingDetails, err := h.serviceBindingRepo.GetServiceBindingDetails(r.Context(), authInfo, serviceBindingGUID)
if err != nil {
return nil, apierrors.LogAndReturn(logger, apierrors.ForbiddenAsNotFound(err), "Error getting service binding details in repository")
}
return routing.NewResponse(http.StatusOK).WithBody(presenter.ForServiceBindingDetails(bindingDetails)), nil
}

func (h *ServiceBinding) UnauthenticatedRoutes() []routing.Route {
return nil
}
Expand All @@ -231,5 +246,6 @@ func (h *ServiceBinding) AuthenticatedRoutes() []routing.Route {
{Method: "DELETE", Pattern: ServiceBindingPath, Handler: h.delete},
{Method: "PATCH", Pattern: ServiceBindingPath, Handler: h.update},
{Method: "GET", Pattern: ServiceBindingPath, Handler: h.get},
{Method: "GET", Pattern: ServiceBindingDetailsPath, Handler: h.getDetails},
}
}
49 changes: 49 additions & 0 deletions api/handlers/service_binding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ var _ = Describe("ServiceBinding", func() {
Type: korifiv1alpha1.UserProvidedType,
}, nil)

sysLogDrainURL := "http://syslog.example.com/drain"
serviceBindingRepo.GetServiceBindingDetailsReturns(repositories.ServiceBindingDetailsRecord{
Credentials: repositories.Credentials{
"connection": "mydb://user@password:example.com",
},
SyslogDrainURL: &sysLogDrainURL,
VolumeMounts: []string{"mount1", "mount2"},
}, nil)

requestValidator = new(fake.RequestValidator)

apiHandler := NewServiceBinding(
Expand Down Expand Up @@ -568,4 +577,44 @@ var _ = Describe("ServiceBinding", func() {
})
})
})

Describe("GET /v3/service_credential_bindings/{guid}/details", func() {
BeforeEach(func() {
requestMethod = http.MethodGet
requestPath = "/v3/service_credential_bindings/service-binding-guid/details"
requestBody = ""
})

It("returns the service binding details", func() {
Expect(rr).To(HaveHTTPStatus(http.StatusOK))
Expect(rr).To(HaveHTTPHeaderWithValue("Content-Type", "application/json"))
Expect(rr).To(HaveHTTPBody(SatisfyAll(
MatchJSONPath("$.credentials.connection", "mydb://user@password:example.com"),
MatchJSONPath("$.syslog_drain_url", "http://syslog.example.com/drain"),
MatchJSONPath("$.volume_mounts[0]", "mount1"),
MatchJSONPath("$.volume_mounts[1]", "mount2"),
)))
Expect(serviceBindingRepo.GetServiceBindingDetailsCallCount()).To(Equal(1))
})

When("the service binding repo returns an error", func() {
BeforeEach(func() {
serviceBindingRepo.GetServiceBindingDetailsReturns(repositories.ServiceBindingDetailsRecord{}, errors.New("get-service-binding-details-error"))
})

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

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

It("returns 404 NotFound when binding not found", func() {
expectNotFoundError(repositories.ServiceBindingResourceType)
})
})
})
})
14 changes: 14 additions & 0 deletions api/presenter/service_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ type ServiceBindingLinks struct {
Details Link `json:"details"`
}

type ServiceBindingDetailsResponse struct {
Credentials map[string]any `json:"credentials"`
SyslogDrainURL *string `json:"syslog_drain_url,omitempty"`
VolumeMounts []string `json:"volume_mounts,omitempty"`
}

func ForServiceBinding(record repositories.ServiceBindingRecord, baseURL url.URL, includes ...model.IncludedResource) ServiceBindingResponse {
return ServiceBindingResponse{
GUID: record.GUID,
Expand Down Expand Up @@ -87,3 +93,11 @@ func ForServiceBindingList(serviceBindingRecords []repositories.ServiceBindingRe

return ForList(ForServiceBinding, serviceBindingRecords, baseURL, requestURL, includedApps...)
}

func ForServiceBindingDetails(serviceBindingDetailsRecord repositories.ServiceBindingDetailsRecord) ServiceBindingDetailsResponse {
return ServiceBindingDetailsResponse{
Credentials: serviceBindingDetailsRecord.Credentials,
SyslogDrainURL: serviceBindingDetailsRecord.SyslogDrainURL,
VolumeMounts: serviceBindingDetailsRecord.VolumeMounts,
}
}
54 changes: 54 additions & 0 deletions api/repositories/service_binding_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package repositories

import (
"context"
"encoding/json"
"errors"
"fmt"
"slices"
"time"
Expand Down Expand Up @@ -75,6 +77,12 @@ func (r ServiceBindingRecord) Relationships() map[string]string {
}
}

type Credentials map[string]any
type ServiceBindingDetailsRecord struct {
Credentials Credentials
SyslogDrainURL *string
VolumeMounts []string
}
type ServiceBindingLastOperation struct {
Type string
State string
Expand Down Expand Up @@ -272,6 +280,52 @@ func (r *ServiceBindingRepo) GetServiceBinding(ctx context.Context, authInfo aut
return serviceBindingToRecord(*serviceBinding), nil
}

func (r *ServiceBindingRepo) GetServiceBindingDetails(ctx context.Context, authInfo authorization.Info, bindingGUID string) (ServiceBindingDetailsRecord, error) {
userClient, err := r.userClientFactory.BuildClient(authInfo)
if err != nil {
return ServiceBindingDetailsRecord{}, fmt.Errorf("failed to build user client: %w", err)
}

namespace, err := r.namespaceRetriever.NamespaceFor(ctx, bindingGUID, ServiceBindingResourceType)
if err != nil {
return ServiceBindingDetailsRecord{}, fmt.Errorf("failed to retrieve namespace: %w", err)
}

serviceBinding := &korifiv1alpha1.CFServiceBinding{
ObjectMeta: metav1.ObjectMeta{
Name: bindingGUID,
Namespace: namespace,
},
}
err = userClient.Get(ctx, client.ObjectKeyFromObject(serviceBinding), serviceBinding)
if err != nil {
return ServiceBindingDetailsRecord{}, fmt.Errorf("failed to get service binding: %w", apierrors.FromK8sError(err, ServiceBindingResourceType))
}

if ok := isBindingReady(*serviceBinding); !ok {
return ServiceBindingDetailsRecord{}, errors.New("binding is not ready yet")
}

credentialsSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: serviceBinding.Status.Credentials.Name,
},
}
err = userClient.Get(ctx, client.ObjectKeyFromObject(credentialsSecret), credentialsSecret)
if err != nil {
return ServiceBindingDetailsRecord{}, fmt.Errorf("failed to get credentials: %w", apierrors.ForbiddenAsNotFound(apierrors.FromK8sError(err, ServiceBindingResourceType))) //
}

credentials := map[string]any{}
err = json.Unmarshal(credentialsSecret.Data[korifiv1alpha1.CredentialsSecretKey], &credentials)
if err != nil {
return ServiceBindingDetailsRecord{}, fmt.Errorf("failed to unmarshal service binding credentials: %w", err)
}

return ServiceBindingDetailsRecord{Credentials: credentials}, nil
}

func serviceBindingToRecord(binding korifiv1alpha1.CFServiceBinding) ServiceBindingRecord {
return ServiceBindingRecord{
GUID: binding.Name,
Expand Down
Loading

0 comments on commit dedb18a

Please sign in to comment.