Skip to content

Commit

Permalink
refactor(cloud): move secrets helpers to CloudApi (#6070)
Browse files Browse the repository at this point in the history
* refactor: split api call and result formatting

* refactor: move secret helpers to Cloud API

Co-authored-by: Anna Mager <[email protected]>

---------

Co-authored-by: Anna Mager <[email protected]>
  • Loading branch information
vvagaytsev and twelvemo authored May 24, 2024
1 parent 76fa956 commit 2f713f7
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 145 deletions.
104 changes: 102 additions & 2 deletions core/src/cloud/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ import type { IncomingHttpHeaders } from "http"

import type { GotHeaders, GotJsonOptions, GotResponse } from "../util/http.js"
import { got, GotHttpError } from "../util/http.js"
import { CloudApiError, InternalError } from "../exceptions.js"
import { CloudApiError, GardenError, InternalError } from "../exceptions.js"
import type { Log } from "../logger/log-entry.js"
import { DEFAULT_GARDEN_CLOUD_DOMAIN, gardenEnv } from "../constants.js"
import { Cookie } from "tough-cookie"
import { cloneDeep, isObject } from "lodash-es"
import { cloneDeep, isObject, omit } from "lodash-es"
import { dedent, deline } from "../util/string.js"
import type {
BaseResponse,
Expand All @@ -27,6 +27,9 @@ import type {
GetProfileResponse,
GetProjectResponse,
ListProjectsResponse,
ListSecretsResponse,
SecretResult as CloudApiSecretResult,
SecretResult,
UpdateSecretRequest,
UpdateSecretResponse,
} from "@garden-io/platform-api-types"
Expand All @@ -41,6 +44,9 @@ import type { StringMap } from "../config/common.js"
import { styles } from "../logger/styles.js"
import { RequestError } from "got"
import type { Garden } from "../garden.js"
import type { ApiCommandError } from "../commands/cloud/helpers.js"
import { enumerate } from "../util/enumerate.js"
import queryString from "query-string"

const gardenClientName = "garden-core"
const gardenClientVersion = getPackageVersion()
Expand Down Expand Up @@ -68,6 +74,8 @@ function isGotResponseOk(response: GotResponse) {

const refreshThreshold = 10 // Threshold (in seconds) subtracted to jwt validity when checking if a refresh is needed

const secretsPageLimit = 100

export interface ApiFetchParams {
headers: GotHeaders
method: "GET" | "POST" | "PUT" | "PATCH" | "HEAD" | "DELETE"
Expand All @@ -77,6 +85,28 @@ export interface ApiFetchParams {
body?: any
}

interface BulkOperationResult {
results: SecretResult[]
errors: ApiCommandError[]
}

export interface Secret {
name: string
value: string
}

export interface BulkCreateSecretRequest extends Omit<CreateSecretRequest, "name" | "value"> {
secrets: Secret[]
}

export interface SingleUpdateSecretRequest extends UpdateSecretRequest {
id: string
}

export interface BulkUpdateSecretRequest {
secrets: SingleUpdateSecretRequest[]
}

export interface ApiFetchOptions {
headers?: GotHeaders
/**
Expand Down Expand Up @@ -874,14 +904,84 @@ export class CloudApi {
return secrets
}

async fetchAllSecrets(projectId: string, log: Log): Promise<CloudApiSecretResult[]> {
let page = 0
const secrets: CloudApiSecretResult[] = []
let hasMore = true
while (hasMore) {
log.debug(`Fetching page ${page}`)
const q = queryString.stringify({ projectId, offset: page * secretsPageLimit, limit: secretsPageLimit })
const res = await this.get<ListSecretsResponse>(`/secrets?${q}`)
if (res.data.length === 0) {
hasMore = false
} else {
secrets.push(...res.data)
page++
}
}
return secrets
}

async createSecret(request: CreateSecretRequest): Promise<CreateSecretResponse> {
return await this.post<CreateSecretResponse>(`/secrets`, { body: request })
}

async createSecrets({ request, log }: { request: BulkCreateSecretRequest; log: Log }): Promise<BulkOperationResult> {
const { secrets, environmentId, userId, projectId } = request

const errors: ApiCommandError[] = []
const results: SecretResult[] = []

for (const [counter, { name, value }] of enumerate(secrets, 1)) {
log.info({ msg: `Creating secrets... → ${counter}/${secrets.length}` })
try {
const body = { environmentId, userId, projectId, name, value }
const res = await this.createSecret(body)
results.push(res.data)
} catch (err) {
if (!(err instanceof GardenError)) {
throw err
}
errors.push({
identifier: name,
message: err.message,
})
}
}

return { results, errors }
}

async updateSecret(secretId: string, request: UpdateSecretRequest): Promise<UpdateSecretResponse> {
return await this.put<UpdateSecretResponse>(`/secrets/${secretId}`, { body: request })
}

async updateSecrets({ request, log }: { request: BulkUpdateSecretRequest; log: Log }): Promise<BulkOperationResult> {
const { secrets } = request

const errors: ApiCommandError[] = []
const results: SecretResult[] = []

for (const [counter, secret] of enumerate(secrets, 1)) {
log.info({ msg: `Updating secrets... → ${counter}/${secrets.length}` })
try {
const body = omit(secret, "id")
const res = await this.updateSecret(secret.id, body)
results.push(res.data)
} catch (err) {
if (!(err instanceof GardenError)) {
throw err
}
errors.push({
identifier: secret.name,
message: err.message,
})
}
}

return { results, errors }
}

async registerCloudBuilderBuild(body: {
actionName: string
actionUid: string
Expand Down
128 changes: 3 additions & 125 deletions core/src/commands/cloud/secrets/secret-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import type {
CreateSecretRequest,
ListSecretsResponse,
SecretResult as CloudApiSecretResult,
UpdateSecretRequest,
} from "@garden-io/platform-api-types"
import type { CloudApi, CloudEnvironment, CloudProject } from "../../../cloud/api.js"
import type { Log } from "../../../logger/log-entry.js"
import queryString from "query-string"
import { CloudApiError, GardenError } from "../../../exceptions.js"
import type { SecretResult as CloudApiSecretResult } from "@garden-io/platform-api-types"
import type { CloudEnvironment, CloudProject } from "../../../cloud/api.js"
import { CloudApiError } from "../../../exceptions.js"
import { dedent } from "../../../util/string.js"
import type { ApiCommandError } from "../helpers.js"
import { enumerate } from "../../../util/enumerate.js"
import { omit } from "lodash-es"

export interface SecretResult {
id: string
Expand Down Expand Up @@ -62,98 +52,6 @@ export function getEnvironmentByNameOrThrow({
})
}

// TODO: consider moving bulk ops to CloudApi

interface BulkOperationResult {
results: SecretResult[]
errors: ApiCommandError[]
}

export interface Secret {
name: string
value: string
}

export interface BulkCreateSecretRequest extends Omit<CreateSecretRequest, "name" | "value"> {
secrets: Secret[]
}

export async function createSecrets({
request,
api,
log,
}: {
request: BulkCreateSecretRequest
api: CloudApi
log: Log
}): Promise<BulkOperationResult> {
const { secrets, environmentId, userId, projectId } = request

const errors: ApiCommandError[] = []
const results: SecretResult[] = []

for (const [counter, { name, value }] of enumerate(secrets, 1)) {
log.info({ msg: `Creating secrets... → ${counter}/${secrets.length}` })
try {
const body = { environmentId, userId, projectId, name, value }
const res = await api.createSecret(body)
results.push(makeSecretFromResponse(res.data))
} catch (err) {
if (!(err instanceof GardenError)) {
throw err
}
errors.push({
identifier: name,
message: err.message,
})
}
}

return { results, errors }
}

export interface SingleUpdateSecretRequest extends UpdateSecretRequest {
id: string
}

export interface BulkUpdateSecretRequest {
secrets: SingleUpdateSecretRequest[]
}

export async function updateSecrets({
request,
api,
log,
}: {
request: BulkUpdateSecretRequest
api: CloudApi
log: Log
}): Promise<BulkOperationResult> {
const { secrets } = request

const errors: ApiCommandError[] = []
const results: SecretResult[] = []

for (const [counter, secret] of enumerate(secrets, 1)) {
log.info({ msg: `Updating secrets... → ${counter}/${secrets.length}` })
try {
const body = omit(secret, "id")
const res = await api.updateSecret(secret.id, body)
results.push(makeSecretFromResponse(res.data))
} catch (err) {
if (!(err instanceof GardenError)) {
throw err
}
errors.push({
identifier: secret.name,
message: err.message,
})
}
}

return { results, errors }
}

export function makeSecretFromResponse(res: CloudApiSecretResult): SecretResult {
const secret = {
name: res.name,
Expand All @@ -176,23 +74,3 @@ export function makeSecretFromResponse(res: CloudApiSecretResult): SecretResult
}
return secret
}

const secretsPageLimit = 100

export async function fetchAllSecrets(api: CloudApi, projectId: string, log: Log): Promise<CloudApiSecretResult[]> {
let page = 0
const secrets: CloudApiSecretResult[] = []
let hasMore = true
while (hasMore) {
log.debug(`Fetching page ${page}`)
const q = queryString.stringify({ projectId, offset: page * secretsPageLimit, limit: secretsPageLimit })
const res = await api.get<ListSecretsResponse>(`/secrets?${q}`)
if (res.data.length === 0) {
hasMore = false
} else {
secrets.push(...res.data)
page++
}
}
return secrets
}
7 changes: 3 additions & 4 deletions core/src/commands/cloud/secrets/secrets-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { handleBulkOperationResult, noApiMsg, readInputKeyValueResources } from
import { dedent, deline } from "../../../util/string.js"
import { PathParameter, StringParameter, StringsParameter } from "../../../cli/params.js"
import type { SecretResult } from "./secret-helpers.js"
import { createSecrets } from "./secret-helpers.js"
import { makeSecretFromResponse } from "./secret-helpers.js"
import { getEnvironmentByNameOrThrow } from "./secret-helpers.js"

export const secretsCreateArgs = {
Expand Down Expand Up @@ -117,9 +117,8 @@ export class SecretsCreateCommand extends Command<Args, Opts> {
}
}

const { errors, results } = await createSecrets({
const { errors, results } = await api.createSecrets({
request: { secrets, environmentId, userId, projectId: project.id },
api,
log,
})

Expand All @@ -129,7 +128,7 @@ export class SecretsCreateCommand extends Command<Args, Opts> {
action: "create",
resource: "secret",
errors,
results,
results: results.map(makeSecretFromResponse),
})
}
}
3 changes: 1 addition & 2 deletions core/src/commands/cloud/secrets/secrets-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { StringsParameter } from "../../../cli/params.js"
import { styles } from "../../../logger/styles.js"
import type { SecretResult } from "./secret-helpers.js"
import { makeSecretFromResponse } from "./secret-helpers.js"
import { fetchAllSecrets } from "./secret-helpers.js"

export const secretsListOpts = {
"filter-envs": new StringsParameter({
Expand Down Expand Up @@ -67,7 +66,7 @@ export class SecretsListCommand extends Command<{}, Opts> {
projectName: garden.projectName,
})

const secrets = await fetchAllSecrets(api, project.id, log)
const secrets = await api.fetchAllSecrets(project.id, log)
log.info("")

if (secrets.length === 0) {
Expand Down
Loading

0 comments on commit 2f713f7

Please sign in to comment.