Skip to content

Commit

Permalink
Merge branch 'main' into get_offering
Browse files Browse the repository at this point in the history
  • Loading branch information
klapkov authored Nov 6, 2024
2 parents b363360 + 88295c3 commit a40ae8b
Show file tree
Hide file tree
Showing 17 changed files with 793 additions and 188 deletions.
19 changes: 19 additions & 0 deletions api/handlers/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,24 @@ func getDomainsForRoutes(ctx context.Context, domainRepo CFDomainRepository, aut
return routeRecords, nil
}

func (h *App) getEnvVars(r *http.Request) (*routing.Response, error) {
authInfo, _ := authorization.InfoFromContext(r.Context())
logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.app.get-env-vars")
appGUID := routing.URLParam(r, "guid")

appEnvRecord, err := h.appRepo.GetAppEnv(r.Context(), authInfo, appGUID)
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "Failed to fetch app environment variables", "AppGUID", appGUID)
}

appEnvVarsRecord := repositories.AppEnvVarsRecord{
AppGUID: appEnvRecord.AppGUID,
EnvironmentVariables: appEnvRecord.EnvironmentVariables,
}

return routing.NewResponse(http.StatusOK).WithBody(presenter.ForAppEnvVars(appEnvVarsRecord, h.serverURL)), nil
}

func (h *App) updateEnvVars(r *http.Request) (*routing.Response, error) {
authInfo, _ := authorization.InfoFromContext(r.Context())
logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.app.update-env-vars")
Expand Down Expand Up @@ -717,6 +735,7 @@ func (h *App) AuthenticatedRoutes() []routing.Route {
{Method: "GET", Pattern: AppProcessStatsByTypePath, Handler: h.getProcessStats},
{Method: "GET", Pattern: AppRoutesPath, Handler: h.getRoutes},
{Method: "DELETE", Pattern: AppPath, Handler: h.delete},
{Method: "GET", Pattern: AppEnvVarsPath, Handler: h.getEnvVars},
{Method: "PATCH", Pattern: AppEnvVarsPath, Handler: h.updateEnvVars},
{Method: "GET", Pattern: AppEnvPath, Handler: h.getEnvironment},
{Method: "GET", Pattern: AppPackagesPath, Handler: h.getPackages},
Expand Down
31 changes: 31 additions & 0 deletions api/handlers/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,37 @@ var _ = Describe("App", func() {
})
})

Describe("GET /v3/apps/:guid/environment_variables", func() {
BeforeEach(func() {
appRepo.GetAppEnvReturns(repositories.AppEnvRecord{
AppGUID: appGUID,
EnvironmentVariables: map[string]string{"VAR": "VAL"},
}, nil)

req = createHttpRequest("GET", "/v3/apps/"+appGUID+"/environment_variables", nil)
})

It("returns the app environment variables", func() {
Expect(appRepo.GetAppEnvCallCount()).To(Equal(1))
_, actualAuthInfo, _ := appRepo.GetAppEnvArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))

Expect(rr).To(HaveHTTPStatus(http.StatusOK))
Expect(rr).To(HaveHTTPHeaderWithValue("Content-Type", "application/json"))
Expect(rr).To(HaveHTTPBody(MatchJSONPath("$.var.VAR", "VAL")))
})

When("there is an error fetching the app env", func() {
BeforeEach(func() {
appRepo.GetAppEnvReturns(repositories.AppEnvRecord{}, errors.New("unknown!"))
})

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

Describe("PATCH /v3/apps/:guid/environment_variables", func() {
var payload *payloads.AppPatchEnvVars

Expand Down
120 changes: 120 additions & 0 deletions api/handlers/fake/stack_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package fake

import (
"context"
"sync"

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

type StackRepository struct {
ListStacksStub func(context.Context, authorization.Info) ([]repositories.StackRecord, error)
listStacksMutex sync.RWMutex
listStacksArgsForCall []struct {
arg1 context.Context
arg2 authorization.Info
}
listStacksReturns struct {
result1 []repositories.StackRecord
result2 error
}
listStacksReturnsOnCall map[int]struct {
result1 []repositories.StackRecord
result2 error
}
invocations map[string][][]interface{}
invocationsMutex sync.RWMutex
}

func (fake *StackRepository) ListStacks(arg1 context.Context, arg2 authorization.Info) ([]repositories.StackRecord, error) {
fake.listStacksMutex.Lock()
ret, specificReturn := fake.listStacksReturnsOnCall[len(fake.listStacksArgsForCall)]
fake.listStacksArgsForCall = append(fake.listStacksArgsForCall, struct {
arg1 context.Context
arg2 authorization.Info
}{arg1, arg2})
stub := fake.ListStacksStub
fakeReturns := fake.listStacksReturns
fake.recordInvocation("ListStacks", []interface{}{arg1, arg2})
fake.listStacksMutex.Unlock()
if stub != nil {
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1, ret.result2
}
return fakeReturns.result1, fakeReturns.result2
}

func (fake *StackRepository) ListStacksCallCount() int {
fake.listStacksMutex.RLock()
defer fake.listStacksMutex.RUnlock()
return len(fake.listStacksArgsForCall)
}

func (fake *StackRepository) ListStacksCalls(stub func(context.Context, authorization.Info) ([]repositories.StackRecord, error)) {
fake.listStacksMutex.Lock()
defer fake.listStacksMutex.Unlock()
fake.ListStacksStub = stub
}

func (fake *StackRepository) ListStacksArgsForCall(i int) (context.Context, authorization.Info) {
fake.listStacksMutex.RLock()
defer fake.listStacksMutex.RUnlock()
argsForCall := fake.listStacksArgsForCall[i]
return argsForCall.arg1, argsForCall.arg2
}

func (fake *StackRepository) ListStacksReturns(result1 []repositories.StackRecord, result2 error) {
fake.listStacksMutex.Lock()
defer fake.listStacksMutex.Unlock()
fake.ListStacksStub = nil
fake.listStacksReturns = struct {
result1 []repositories.StackRecord
result2 error
}{result1, result2}
}

func (fake *StackRepository) ListStacksReturnsOnCall(i int, result1 []repositories.StackRecord, result2 error) {
fake.listStacksMutex.Lock()
defer fake.listStacksMutex.Unlock()
fake.ListStacksStub = nil
if fake.listStacksReturnsOnCall == nil {
fake.listStacksReturnsOnCall = make(map[int]struct {
result1 []repositories.StackRecord
result2 error
})
}
fake.listStacksReturnsOnCall[i] = struct {
result1 []repositories.StackRecord
result2 error
}{result1, result2}
}

func (fake *StackRepository) Invocations() map[string][][]interface{} {
fake.invocationsMutex.RLock()
defer fake.invocationsMutex.RUnlock()
fake.listStacksMutex.RLock()
defer fake.listStacksMutex.RUnlock()
copiedInvocations := map[string][][]interface{}{}
for key, value := range fake.invocations {
copiedInvocations[key] = value
}
return copiedInvocations
}

func (fake *StackRepository) recordInvocation(key string, args []interface{}) {
fake.invocationsMutex.Lock()
defer fake.invocationsMutex.Unlock()
if fake.invocations == nil {
fake.invocations = map[string][][]interface{}{}
}
if fake.invocations[key] == nil {
fake.invocations[key] = [][]interface{}{}
}
fake.invocations[key] = append(fake.invocations[key], args)
}

var _ handlers.StackRepository = new(StackRepository)
60 changes: 60 additions & 0 deletions api/handlers/stack.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 (
StacksPath = "/v3/stacks"
)

type StackRepository interface {
ListStacks(ctx context.Context, authInfo authorization.Info) ([]repositories.StackRecord, error)
}

type Stack struct {
serverURL url.URL
stackRepo StackRepository
}

func NewStack(
serverURL url.URL,
stackRepo StackRepository,
) *Stack {
return &Stack{
serverURL: serverURL,
stackRepo: stackRepo,
}
}

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

stacks, err := h.stackRepo.ListStacks(r.Context(), authInfo)
if err != nil {
return nil, apierrors.LogAndReturn(logger, err, "Failed to fetch buildpacks from Kubernetes")
}

return routing.NewResponse(http.StatusOK).WithBody(presenter.ForList(presenter.ForStack, stacks, h.serverURL, *r.URL)), nil

Check failure on line 49 in api/handlers/stack.go

View workflow job for this annotation

GitHub Actions / linter

in call to presenter.ForList, type func(stackRecord repositories.StackRecord, baseURL url.URL) presenter.StackResponse of presenter.ForStack does not match presenter.itemPresenter[T, S] (cannot infer T and S)
}

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

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

import (
"errors"
"net/http"
"time"

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

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

var _ = Describe("Stack", func() {
var (
stackRepo *fake.StackRepository
req *http.Request
)

BeforeEach(func() {
stackRepo = new(fake.StackRepository)

apiHandler := NewStack(*serverURL, stackRepo)
routerBuilder.LoadRoutes(apiHandler)
})

JustBeforeEach(func() {
routerBuilder.Build().ServeHTTP(rr, req)
})

Describe("the GET /v3/stacks endpoint", func() {
BeforeEach(func() {
stackRepo.ListStacksReturns([]repositories.StackRecord{
{
Name: "io.buildpacks.stacks.jammy",
Description: "Jammy Stack",
CreatedAt: time.UnixMilli(1000),
UpdatedAt: tools.PtrTo(time.UnixMilli(2000)),
},
}, nil)

var err error
req, err = http.NewRequestWithContext(ctx, "GET", "/v3/stacks", nil)
Expect(err).NotTo(HaveOccurred())
})

It("returns the stacks for the default builder", func() {
Expect(stackRepo.ListStacksCallCount()).To(Equal(1))
_, actualAuthInfo := stackRepo.ListStacksArgsForCall(0)
Expect(actualAuthInfo).To(Equal(authInfo))

Expect(rr).To(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/stacks"),
MatchJSONPath("$.resources", HaveLen(1)),
MatchJSONPath("$.resources[0].name", "io.buildpacks.stacks.jammy"),
)))
})
When("there is some other error fetching the stacks", func() {
BeforeEach(func() {
stackRepo.ListStacksReturns([]repositories.StackRecord{}, errors.New("unknown!"))
})

It("returns an error", func() {
expectUnknownError()
})
})
})
})
8 changes: 8 additions & 0 deletions api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ func main() {
nsPermissions,
conditions.NewConditionAwaiter[*korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBinding, korifiv1alpha1.CFServiceBindingList](conditionTimeout),
)
stackRepo := repositories.NewStackRepository(cfg.BuilderName,
userClientFactory,
cfg.RootNamespace,
)
buildpackRepo := repositories.NewBuildpackRepository(cfg.BuilderName,
userClientFactory,
cfg.RootNamespace,
Expand Down Expand Up @@ -358,6 +362,10 @@ func main() {
runnerInfoRepo,
cfg.RunnerName,
),
handlers.NewStack(
*serverURL,
stackRepo,
),
handlers.NewJob(
*serverURL,
map[string]handlers.DeletionRepository{
Expand Down
39 changes: 39 additions & 0 deletions api/presenter/stack.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package presenter

import (
"net/url"

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

const (
stacksBase = "/v3/stacks"
)

type StackResponse struct {
GUID string `json:"guid"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
Name string `json:"name"`
Description string `json:"description"`
Metadata Metadata `json:"metadata"`
Links StackLinks `json:"links"`
}

type StackLinks struct {
Self Link `json:"self"`
}

func ForStack(stackRecord repositories.StackRecord, baseURL url.URL) StackResponse {
return StackResponse{
GUID: stackRecord.GUID,
CreatedAt: formatTimestamp(&stackRecord.CreatedAt),
UpdatedAt: formatTimestamp(stackRecord.UpdatedAt),
Name: stackRecord.Name,
Links: StackLinks{
Self: Link{
HRef: buildURL(baseURL).appendPath(stacksBase, stackRecord.GUID).build(),
},
},
}
}
Loading

0 comments on commit a40ae8b

Please sign in to comment.