Skip to content

Commit

Permalink
Implement GET /v3/service_offerings
Browse files Browse the repository at this point in the history
fixes #3264

Co-authored-by: Danail Branekov <[email protected]>
  • Loading branch information
georgethebeatle and danail-branekov committed Jul 5, 2024
1 parent d71ca92 commit 5f8f91b
Show file tree
Hide file tree
Showing 52 changed files with 2,150 additions and 121 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
*.test
tests/assets/dorifi/dorifi
tests/assets/multi-process/dorifi
tests/assets/sample-broker/sample-broker
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ test-smoke: build-dorifi


build-dorifi:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -C tests/assets/golang -o ../dorifi/dorifi .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -C tests/assets/golang -o ../multi-process/dorifi .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -C tests/assets/dorifi-golang -o ../dorifi/dorifi .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -C tests/assets/dorifi-golang -o ../multi-process/dorifi .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -C tests/assets/sample-broker-golang -o ../sample-broker/sample-broker .

GOFUMPT = $(shell go env GOPATH)/bin/gofumpt
install-gofumpt:
Expand Down
1 change: 1 addition & 0 deletions README.helm.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Here are all the values that can be set for the chart:
- `experimental`: Experimental features. No guarantees are provided and breaking/backwards incompatible changes should be expected. These features are not recommended for use in production environments.
- `managedServices`:
- `include` (_Boolean_): Enable managed services support
- `trustInsecureBrokers` (_Boolean_): Disable service broker certificate validation. Not recommended to be set to 'true' in production environments
- `generateIngressCertificates` (_Boolean_): Use `cert-manager` to generate self-signed certificates for the API and app endpoints.
- `helm`:
- `hooksImage` (_String_): Image for the helm hooks containing kubectl
Expand Down
121 changes: 121 additions & 0 deletions api/handlers/fake/cfservice_offering_repository.go

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

60 changes: 60 additions & 0 deletions api/handlers/service_offering.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package handlers

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

"code.cloudfoundry.org/korifi/api/authorization"
apierrors "code.cloudfoundry.org/korifi/api/errors"
"code.cloudfoundry.org/korifi/api/presenter"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/api/routing"
"github.com/go-logr/logr"
)

const (
ServiceOfferingsPath = "/v3/service_offerings"
)

//counterfeiter:generate -o fake -fake-name CFServiceOfferingRepository . CFServiceOfferingRepository
type CFServiceOfferingRepository interface {
ListOfferings(context.Context, authorization.Info) ([]repositories.ServiceOfferingResource, error)
}

type ServiceOffering struct {
serverURL url.URL
serviceOfferingRepo CFServiceOfferingRepository
}

func NewServiceOffering(
serverURL url.URL,
serviceOfferingRepo CFServiceOfferingRepository,
) *ServiceOffering {
return &ServiceOffering{
serverURL: serverURL,
serviceOfferingRepo: serviceOfferingRepo,
}
}

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

serviceOfferingList, err := h.serviceOfferingRepo.ListOfferings(r.Context(), authInfo)
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "Failed to list service offerings")
}

return routing.NewResponse(http.StatusOK).WithBody(presenter.ForList(presenter.ForServiceOffering, serviceOfferingList, h.serverURL, *r.URL)), nil
}

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

func (h *ServiceOffering) AuthenticatedRoutes() []routing.Route {
return []routing.Route{
{Method: "GET", Pattern: ServiceOfferingsPath, Handler: h.list},
}
}
71 changes: 71 additions & 0 deletions api/handlers/service_offering_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package handlers_test

import (
"net/http"

. "code.cloudfoundry.org/korifi/api/handlers"
"code.cloudfoundry.org/korifi/api/handlers/fake"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/model"
"code.cloudfoundry.org/korifi/model/services"
. "code.cloudfoundry.org/korifi/tests/matchers"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

var _ = Describe("ServiceOffering", func() {
var serviceOfferingRepo *fake.CFServiceOfferingRepository

BeforeEach(func() {
serviceOfferingRepo = new(fake.CFServiceOfferingRepository)

apiHandler := NewServiceOffering(
*serverURL,
serviceOfferingRepo,
)
routerBuilder.LoadRoutes(apiHandler)
})

Describe("GET /v3/service_offerings", func() {
BeforeEach(func() {
serviceOfferingRepo.ListOfferingsReturns([]repositories.ServiceOfferingResource{{
ServiceOffering: services.ServiceOffering{},
CFResource: model.CFResource{
GUID: "offering-guid",
},
Relationships: repositories.ServiceOfferingRelationships{
ServiceBroker: model.ToOneRelationship{
Data: model.Relationship{
GUID: "broker-guid",
},
},
},
}}, nil)
})

JustBeforeEach(func() {
req, err := http.NewRequestWithContext(ctx, "GET", "/v3/service_offerings", nil)
Expect(err).NotTo(HaveOccurred())

routerBuilder.Build().ServeHTTP(rr, req)
})

It("lists the service offerings", func() {
Expect(serviceOfferingRepo.ListOfferingsCallCount()).To(Equal(1))
_, actualAuthInfo := serviceOfferingRepo.ListOfferingsArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))

Expect(rr).Should(HaveHTTPStatus(http.StatusOK))
Expect(rr).To(HaveHTTPHeaderWithValue("Content-Type", "application/json"))
Expect(rr).To(HaveHTTPBody(SatisfyAll(
MatchJSONPath("$.pagination.total_results", BeEquivalentTo(1)),
MatchJSONPath("$.pagination.first.href", "https://api.example.org/v3/service_offerings"),
MatchJSONPath("$.resources[0].guid", "offering-guid"),
MatchJSONPath("$.resources[0].links.self.href", "https://api.example.org/v3/service_offerings/offering-guid"),
MatchJSONPath("$.resources[0].links.service_plans.href", "https://api.example.org/v3/service_plans?service_offering_guids=offering-guid"),
MatchJSONPath("$.resources[0].links.service_broker.href", "https://api.example.org/v3/service_brokers/broker-guid"),
)))
})
})
})
5 changes: 5 additions & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ func main() {
)
metricsRepo := repositories.NewMetricsRepo(userClientFactory)
serviceBrokerRepo := repositories.NewServiceBrokerRepo(userClientFactory, cfg.RootNamespace)
serviceOfferingRepo := repositories.NewServiceOfferingRepo(userClientFactory, cfg.RootNamespace)

processStats := actions.NewProcessStats(processRepo, appRepo, metricsRepo)
manifest := actions.NewManifest(
Expand Down Expand Up @@ -412,6 +413,10 @@ func main() {
requestValidator,
cfg.ExperimentalManagedServicesEnabled,
),
handlers.NewServiceOffering(
*serverURL,
serviceOfferingRepo,
),
}
for _, handler := range apiHandlers {
routerBuilder.LoadRoutes(handler)
Expand Down
47 changes: 47 additions & 0 deletions api/presenter/service_offering.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package presenter

import (
"net/url"

"code.cloudfoundry.org/korifi/api/repositories"
)

const (
serviceOfferingsBase = "/v3/service_offerings"
servicePlansBase = "/v3/service_plans"
serviceBrokersBase = "/v3/service_brokers"
)

type ServiceOfferingLinks struct {
Self Link `json:"self"`
ServicePlans Link `json:"service_plans"`
ServiceBroker Link `json:"service_broker"`
}

type ServiceOfferingResponse struct {
repositories.ServiceOfferingResource
Links ServiceOfferingLinks `json:"links"`
}

func ForServiceOffering(serviceOfferingResource repositories.ServiceOfferingResource, baseURL url.URL) ServiceOfferingResponse {
return ServiceOfferingResponse{
ServiceOfferingResource: serviceOfferingResource,
Links: ServiceOfferingLinks{
Self: Link{
HRef: buildURL(baseURL).appendPath(serviceOfferingsBase, serviceOfferingResource.GUID).build(),
},
ServicePlans: Link{
HRef: buildURL(baseURL).appendPath(servicePlansBase).setQuery("service_offering_guids=" + serviceOfferingResource.GUID).build(),
},
ServiceBroker: Link{
HRef: buildURL(baseURL).appendPath(serviceBrokersBase, serviceOfferingResource.Relationships.ServiceBroker.Data.GUID).build(),
},
},
}
}

func ForServiceOfferingList(serviceOfferingResourceList []repositories.ServiceOfferingResource, baseURL, requestURL url.URL) ListResponse[ServiceOfferingResponse] {
return ForList(func(serviceOfferingResource repositories.ServiceOfferingResource, baseURL url.URL) ServiceOfferingResponse {
return ForServiceOffering(serviceOfferingResource, baseURL)
}, serviceOfferingResourceList, baseURL, requestURL)
}
Loading

0 comments on commit 5f8f91b

Please sign in to comment.