cloudfoundry/cf-k8s-controllers

View on GitHub
api/handlers/service_binding.go

Summary

Maintainability
A
0 mins
Test Coverage
package handlers

import (
    "context"
    "net/http"
    "net/url"

    "github.com/go-logr/logr"

    "code.cloudfoundry.org/korifi/api/authorization"
    apierrors "code.cloudfoundry.org/korifi/api/errors"
    "code.cloudfoundry.org/korifi/api/payloads"
    "code.cloudfoundry.org/korifi/api/presenter"
    "code.cloudfoundry.org/korifi/api/repositories"
    "code.cloudfoundry.org/korifi/api/routing"
)

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

type ServiceBinding struct {
    appRepo             CFAppRepository
    serviceBindingRepo  CFServiceBindingRepository
    serviceInstanceRepo CFServiceInstanceRepository
    serverURL           url.URL
    requestValidator    RequestValidator
}

//counterfeiter:generate -o fake -fake-name CFServiceBindingRepository . CFServiceBindingRepository
type CFServiceBindingRepository interface {
    CreateServiceBinding(context.Context, authorization.Info, repositories.CreateServiceBindingMessage) (repositories.ServiceBindingRecord, error)
    DeleteServiceBinding(context.Context, authorization.Info, string) error
    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)
}

func NewServiceBinding(serverURL url.URL, serviceBindingRepo CFServiceBindingRepository, appRepo CFAppRepository, serviceInstanceRepo CFServiceInstanceRepository, requestValidator RequestValidator) *ServiceBinding {
    return &ServiceBinding{
        appRepo:             appRepo,
        serviceInstanceRepo: serviceInstanceRepo,
        serviceBindingRepo:  serviceBindingRepo,
        serverURL:           serverURL,
        requestValidator:    requestValidator,
    }
}

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

    var payload payloads.ServiceBindingCreate
    if err := h.requestValidator.DecodeAndValidateJSONPayload(r, &payload); err != nil {
        return nil, apierrors.LogAndReturn(logger, err, "failed to decode payload")
    }

    app, err := h.appRepo.GetApp(r.Context(), authInfo, payload.Relationships.App.Data.GUID)
    if err != nil {
        return nil, apierrors.LogAndReturn(logger, apierrors.ForbiddenAsNotFound(err), "failed to get "+repositories.AppResourceType)
    }

    serviceInstance, err := h.serviceInstanceRepo.GetServiceInstance(r.Context(), authInfo, payload.Relationships.ServiceInstance.Data.GUID)
    if err != nil {
        return nil, apierrors.LogAndReturn(logger, apierrors.ForbiddenAsNotFound(err), "failed to get "+repositories.ServiceInstanceResourceType)
    }

    if app.SpaceGUID != serviceInstance.SpaceGUID {
        return nil, apierrors.LogAndReturn(
            logger,
            apierrors.NewUnprocessableEntityError(nil, "The service instance and the app are in different spaces"),
            "App and ServiceInstance in different spaces", "App GUID", app.GUID,
            "ServiceInstance GUID", serviceInstance.GUID,
        )
    }

    serviceBinding, err := h.serviceBindingRepo.CreateServiceBinding(r.Context(), authInfo, payload.ToMessage(app.SpaceGUID))
    if err != nil {
        return nil, apierrors.LogAndReturn(logger, err, "failed to create ServiceBinding", "App GUID", app.GUID, "ServiceInstance GUID", serviceInstance.GUID)
    }

    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")

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

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

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

    listFilter := new(payloads.ServiceBindingList)
    if err := h.requestValidator.DecodeAndValidateURLValues(r, listFilter); err != nil {
        return nil, apierrors.LogAndReturn(logger, err, "Unable to decode request query parameters")
    }

    serviceBindingList, err := h.serviceBindingRepo.ListServiceBindings(r.Context(), authInfo, listFilter.ToMessage())
    if err != nil {
        return nil, apierrors.LogAndReturn(logger, err, "failed to list "+repositories.ServiceBindingResourceType)
    }

    var appRecords []repositories.AppRecord
    if listFilter.Include != "" && len(serviceBindingList) > 0 {
        listAppsMessage := repositories.ListAppsMessage{}

        for _, serviceBinding := range serviceBindingList {
            listAppsMessage.Guids = append(listAppsMessage.Guids, serviceBinding.AppGUID)
        }

        appRecords, err = h.appRepo.ListApps(r.Context(), authInfo, listAppsMessage)
        if err != nil {
            return nil, apierrors.LogAndReturn(logger, err, "failed to list "+repositories.AppResourceType)
        }
    }

    return routing.NewResponse(http.StatusOK).WithBody(presenter.ForServiceBindingList(serviceBindingList, appRecords, h.serverURL, *r.URL)), nil
}

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

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

    var payload payloads.ServiceBindingUpdate
    if err := h.requestValidator.DecodeAndValidateJSONPayload(r, &payload); err != nil {
        return nil, apierrors.LogAndReturn(logger, err, "failed to decode payload")
    }

    _, err := h.serviceBindingRepo.GetServiceBinding(r.Context(), authInfo, serviceBindingGUID)
    if err != nil {
        return nil, apierrors.LogAndReturn(logger, apierrors.ForbiddenAsNotFound(err), "Error getting service binding in repository")
    }

    serviceBinding, err := h.serviceBindingRepo.UpdateServiceBinding(r.Context(), authInfo, payload.ToMessage(serviceBindingGUID))
    if err != nil {
        return nil, apierrors.LogAndReturn(logger, err, "Error updating service binding in repository")
    }

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

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

    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), "Error getting service binding in repository")
    }
    return routing.NewResponse(http.StatusOK).WithBody(presenter.ForServiceBinding(serviceBinding, h.serverURL)), nil
}

func (h *ServiceBinding) UnauthenticatedRoutes() []routing.Route {
    return nil
}

func (h *ServiceBinding) AuthenticatedRoutes() []routing.Route {
    return []routing.Route{
        {Method: "POST", Pattern: ServiceBindingsPath, Handler: h.create},
        {Method: "GET", Pattern: ServiceBindingsPath, Handler: h.list},
        {Method: "DELETE", Pattern: ServiceBindingPath, Handler: h.delete},
        {Method: "PATCH", Pattern: ServiceBindingPath, Handler: h.update},
        {Method: "GET", Pattern: ServiceBindingPath, Handler: h.get},
    }
}