Skip to content

Commit

Permalink
Add GET /v3/apps/:guid/environment_variables endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
gogolok authored and danail-branekov committed Nov 4, 2024
1 parent 00500a2 commit 594fe58
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 0 deletions.
15 changes: 15 additions & 0 deletions api/handlers/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
type CFAppRepository interface {
GetApp(context.Context, authorization.Info, string) (repositories.AppRecord, error)
ListApps(context.Context, authorization.Info, repositories.ListAppsMessage) ([]repositories.AppRecord, error)
GetAppEnvVars(context.Context, authorization.Info, string) (repositories.AppEnvVarsRecord, error)
PatchAppEnvVars(context.Context, authorization.Info, repositories.PatchAppEnvVarsMessage) (repositories.AppEnvVarsRecord, error)
CreateApp(context.Context, authorization.Info, repositories.CreateAppMessage) (repositories.AppRecord, error)
SetCurrentDroplet(context.Context, authorization.Info, repositories.SetCurrentDropletMessage) (repositories.CurrentDropletRecord, error)
Expand Down Expand Up @@ -480,6 +481,19 @@ 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")

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

return routing.NewResponse(http.StatusOK).WithBody(presenter.ForAppEnvVars(envVarsRecord, 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 +731,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
30 changes: 30 additions & 0 deletions api/handlers/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,36 @@ var _ = Describe("App", func() {
})
})

Describe("GET /v3/apps/:guid/environment_variables", func() {
BeforeEach(func() {
appRepo.GetAppEnvVarsReturns(repositories.AppEnvVarsRecord{
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.GetAppEnvVarsCallCount()).To(Equal(1))
_, actualAuthInfo, _ := appRepo.GetAppEnvVarsArgsForCall(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.GetAppEnvVarsReturns(repositories.AppEnvVarsRecord{}, errors.New("unknown!"))
})

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

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

Expand Down
83 changes: 83 additions & 0 deletions api/handlers/fake/cfapp_repository.go

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

30 changes: 30 additions & 0 deletions api/repositories/app_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,36 @@ func (f *AppRepo) ListApps(ctx context.Context, authInfo authorization.Info, mes
return f.sorter.Sort(slices.Collect(appRecords), message.OrderBy), nil
}

func (f *AppRepo) GetAppEnvVars(ctx context.Context, authInfo authorization.Info, appGUID string) (AppEnvVarsRecord, error) {
app, err := f.GetApp(ctx, authInfo, appGUID)
if err != nil {
return AppEnvVarsRecord{}, err
}

userClient, err := f.userClientFactory.BuildClient(authInfo)
if err != nil {
return AppEnvVarsRecord{}, fmt.Errorf("failed to build user client: %w", err)
}

appEnvVarMap := map[string]string{}
if app.envSecretName != "" {
appEnvVarSecret := new(corev1.Secret)
err = userClient.Get(ctx, types.NamespacedName{Name: app.envSecretName, Namespace: app.SpaceGUID}, appEnvVarSecret)
if err != nil {
return AppEnvVarsRecord{}, fmt.Errorf("error finding environment variable Secret %q for App %q: %w",
app.envSecretName,
app.GUID,
apierrors.FromK8sError(err, AppEnvResourceType))
}
appEnvVarMap = convertByteSliceValuesToStrings(appEnvVarSecret.Data)
}

return AppEnvVarsRecord{
AppGUID: app.GUID,
EnvironmentVariables: appEnvVarMap,
}, nil
}

func (f *AppRepo) PatchAppEnvVars(ctx context.Context, authInfo authorization.Info, message PatchAppEnvVarsMessage) (AppEnvVarsRecord, error) {
secretObj := corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Expand Down
91 changes: 91 additions & 0 deletions api/repositories/app_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,97 @@ var _ = Describe("AppRepository", func() {
})
})

Describe("GetAppEnvVars", func() {
var (
envVars map[string]string
secretName string
appGUID string
appEnvVarsRecord repositories.AppEnvVarsRecord
getAppEnvVarsErr error
)

BeforeEach(func() {
appGUID = cfApp.Name
secretName = "the-env-secret"

envVars = map[string]string{
"RAILS_ENV": "production",
"LUNCHTIME": "12:00",
}

secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: cfSpace.Name,
},
StringData: envVars,
}

Expect(k8sClient.Create(ctx, secret)).To(Succeed())
})

JustBeforeEach(func() {
appEnvVarsRecord, getAppEnvVarsErr = appRepo.GetAppEnvVars(ctx, authInfo, appGUID)
})

When("the user can read secrets in the space", func() {
BeforeEach(func() {
Expect(k8s.PatchResource(ctx, k8sClient, cfApp, func() {
cfApp.Spec.EnvSecretName = secretName
})).To(Succeed())

createRoleBinding(ctx, userName, spaceDeveloperRole.Name, cfSpace.Name)
})

It("returns the environment variables stored on the secret", func() {
Expect(getAppEnvVarsErr).NotTo(HaveOccurred())
Expect(appEnvVarsRecord.EnvironmentVariables).To(Equal(envVars))
})

When("the EnvSecret doesn't exist", func() {
BeforeEach(func() {
secretName = "doIReallyExist"
Expect(k8s.PatchResource(ctx, k8sClient, cfApp, func() {
cfApp.Spec.EnvSecretName = secretName
})).To(Succeed())
})

It("errors", func() {
Expect(getAppEnvVarsErr).To(MatchError(ContainSubstring("Secret")))
})
})
})

When("EnvSecretName is blank", func() {
BeforeEach(func() {
secretName = ""
Expect(k8s.PatchResource(ctx, k8sClient, cfApp, func() {
cfApp.Spec.EnvSecretName = secretName
})).To(Succeed())
})

It("returns an empty map", func() {
Expect(appEnvVarsRecord.EnvironmentVariables).To(BeEmpty())
})
})

When("the user doesn't have permission to get secrets in the space", func() {
It("errors", func() {
Expect(getAppEnvVarsErr).To(matchers.WrapErrorAssignableToTypeOf(apierrors.ForbiddenError{}))
})
})

When("the app does not exist", func() {
BeforeEach(func() {
appGUID = "i don't exist"
})
It("returns an error", func() {
Expect(getAppEnvVarsErr).To(HaveOccurred())
Expect(getAppEnvVarsErr).To(matchers.WrapErrorAssignableToTypeOf(apierrors.NotFoundError{}))
})
})
})

Describe("PatchAppEnvVars", func() {
const (
key0 = "KEY0"
Expand Down
22 changes: 22 additions & 0 deletions tests/e2e/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,28 @@ var _ = Describe("Apps", func() {
})

Describe("Fetching app environment variables", func() {
var result map[string]interface{}

BeforeEach(func() {
appGUID, _ = pushTestApp(space1GUID, defaultAppBitsFile)
setEnv(appGUID, map[string]interface{}{
"foo": "var",
})
})

It("succeeds", func() {
var err error
resp, err = adminClient.R().
SetResult(&result).
Get("/v3/apps/" + appGUID + "/environment_variables")

Expect(err).NotTo(HaveOccurred())
Expect(resp).To(HaveRestyStatusCode(http.StatusOK))
Expect(result).To(HaveKeyWithValue("var", HaveKeyWithValue("foo", "var")))
})
})

Describe("Fetching app environment", func() {
var (
result map[string]interface{}
instanceGUID, instanceGUID2 string
Expand Down

0 comments on commit 594fe58

Please sign in to comment.