Skip to content

Commit

Permalink
fix(enterprise): ensure exit code and --yes flag work
Browse files Browse the repository at this point in the history
  • Loading branch information
eysi09 committed Apr 22, 2021
1 parent 3ad56c8 commit 65303d4
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 145 deletions.
97 changes: 89 additions & 8 deletions core/src/commands/enterprise/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { GetProjectResponse } from "@garden-io/platform-api-types"
import { GetProjectResponse, SecretResponse, UserResponse } from "@garden-io/platform-api-types"
import { EnterpriseApi } from "../../enterprise/api"
import { dedent } from "../../util/string"

Expand All @@ -16,12 +16,47 @@ import minimatch from "minimatch"
import pluralize from "pluralize"
import chalk from "chalk"
import inquirer from "inquirer"
import { CommandError } from "../../exceptions"
import { CommandResult } from "../base"

export interface DeleteResult {
id: number
status: string
}

export interface ApiCommandError {
identifier: string | number
message?: string
}

export interface SecretResult {
id: number
createdAt: string
updatedAt: string
name: string
environment?: {
name: string
id: number
}
user?: {
name: string
id: number
vcsUsername: string
}
}

export interface UserResult {
id: number
createdAt: string
updatedAt: string
name: string
vcsUsername: string
groups: {
id: number
name: string
}[]
}

export const noApiMsg = (action: string, resource: string) => dedent`
Unable to ${action} ${resource}. Make sure the project is configured for Garden Enterprise and that you're logged in.
`
Expand All @@ -31,21 +66,61 @@ export async function getProject(api: EnterpriseApi, projectUid: string) {
return res.data
}

export function handleBulkOperationResult({
export function makeUserFromResponse(user: UserResponse): UserResult {
return {
id: user.id,
name: user.name,
vcsUsername: user.vcsUsername,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
groups: user.groups.map((g) => ({ id: g.id, name: g.name })),
}
}

export function makeSecretFromResponse(res: SecretResponse): SecretResult {
const secret = {
name: res.name,
id: res.id,
updatedAt: res.updatedAt,
createdAt: res.createdAt,
}
if (res.environment) {
secret["environment"] = {
name: res.environment.name,
id: res.environment.id,
}
}
if (res.user) {
secret["user"] = {
name: res.user.name,
id: res.user.id,
vcsUsername: res.user.vcsUsername,
}
}
return secret
}

/**
* Helper function for consistenly logging outputs for enterprise bulk operation commands.
*
* Throws if any errors exist after logging the relavant output.
*/
export function handleBulkOperationResult<T>({
log,
results,
errors,
action,
cmdLog,
resource,
successCount,
}: {
log: LogEntry
cmdLog: LogEntry
results: T[]
errors: ApiCommandError[]
action: "create" | "delete"
successCount: number
resource: "secret" | "user"
}) {
}): CommandResult<T[]> {
const successCount = results.length
const totalCount = errors.length + successCount

log.info("")
Expand All @@ -66,9 +141,7 @@ export function handleBulkOperationResult({
})
.join("\n")
log.error(dedent`
Failed ${actionVerb} ${errors.length}/${totalCount} ${pluralize(resource)}.
See errors below:
Failed ${actionVerb} ${errors.length}/${totalCount} ${pluralize(resource)}. See errors below:
${errorMsgs}\n
`)
Expand All @@ -81,7 +154,15 @@ export function handleBulkOperationResult({
log.info({
msg: `Successfully ${action === "create" ? "created" : "deleted"} ${successCount} ${resourceStr}!`,
})
log.info("")
}

// Ensure command exits with code 1.
if (errors.length > 0) {
throw new CommandError("Command failed.", { errors })
}

return { result: results }
}

export function applyFilter(filter: string[], val?: string | string[]) {
Expand Down
31 changes: 13 additions & 18 deletions core/src/commands/enterprise/secrets/secrets-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,18 @@ import { readFile } from "fs-extra"

import { printHeader } from "../../../logger/util"
import { Command, CommandParams, CommandResult } from "../../base"
import { ApiCommandError, getProject, handleBulkOperationResult, noApiMsg } from "../helpers"
import {
ApiCommandError,
getProject,
handleBulkOperationResult,
makeSecretFromResponse,
noApiMsg,
SecretResult,
} from "../helpers"
import { dedent, deline } from "../../../util/string"
import { StringsParameter, PathParameter, IntegerParameter, StringParameter } from "../../../cli/params"
import { StringMap } from "../../../config/common"

export interface SecretsCreateCommandResult {
errors: ApiCommandError[]
results: CreateSecretResponse[]
}

export const secretsCreateArgs = {
secrets: new StringsParameter({
help: deline`The names and values of the secrets to create, separated by '='.
Expand Down Expand Up @@ -76,12 +78,7 @@ export class SecretsCreateCommand extends Command<Args, Opts> {
printHeader(headerLog, "Create secrets", "lock")
}

async action({
garden,
log,
opts,
args,
}: CommandParams<Args, Opts>): Promise<CommandResult<SecretsCreateCommandResult>> {
async action({ garden, log, opts, args }: CommandParams<Args, Opts>): Promise<CommandResult<SecretResult[]>> {
// Apparently TS thinks that optional params are always defined so we need to cast them to their
// true type here.
const envName = opts["scope-to-env"] as string | undefined
Expand Down Expand Up @@ -157,14 +154,14 @@ export class SecretsCreateCommand extends Command<Args, Opts> {

let count = 1
const errors: ApiCommandError[] = []
const results: CreateSecretResponse[] = []
const results: SecretResult[] = []
for (const [name, value] of secretsToCreate) {
cmdLog.setState({ msg: `Creating secrets... → ${count}/${secretsToCreate.length}` })
count++
try {
const body = { environmentId, userId, projectId: project.id, name, value }
const res = await api.post<CreateSecretResponse>(`/secrets`, { body })
results.push(res)
results.push(makeSecretFromResponse(res.data))
} catch (err) {
errors.push({
identifier: name,
Expand All @@ -173,15 +170,13 @@ export class SecretsCreateCommand extends Command<Args, Opts> {
}
}

handleBulkOperationResult({
return handleBulkOperationResult({
log,
cmdLog,
action: "create",
resource: "secret",
errors,
successCount: results.length,
results,
})

return { result: { errors, results } }
}
}
21 changes: 7 additions & 14 deletions core/src/commands/enterprise/secrets/secrets-delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ import { CommandError, ConfigurationError } from "../../../exceptions"
import { printHeader } from "../../../logger/util"
import { dedent, deline } from "../../../util/string"
import { Command, CommandParams, CommandResult } from "../../base"
import { ApiCommandError, confirmDelete, handleBulkOperationResult, noApiMsg } from "../helpers"

export interface SecretsDeleteCommandResult {
errors: ApiCommandError[]
results: BaseResponse[]
}
import { ApiCommandError, confirmDelete, DeleteResult, handleBulkOperationResult, noApiMsg } from "../helpers"

export const secretsDeleteArgs = {
ids: new StringsParameter({
Expand All @@ -44,15 +39,15 @@ export class SecretsDeleteCommand extends Command<Args> {
printHeader(headerLog, "Delete secrets", "lock")
}

async action({ garden, args, log }: CommandParams<Args>): Promise<CommandResult<SecretsDeleteCommandResult>> {
async action({ garden, args, log, opts }: CommandParams<Args>): Promise<CommandResult<DeleteResult[]>> {
const secretsToDelete = (args.ids || []).map((id) => parseInt(id, 10))
if (secretsToDelete.length === 0) {
throw new CommandError(`No secret IDs provided.`, {
args,
})
}

if (!(await confirmDelete("secret", secretsToDelete.length))) {
if (!opts.yes && !(await confirmDelete("secret", secretsToDelete.length))) {
return {}
}

Expand All @@ -65,13 +60,13 @@ export class SecretsDeleteCommand extends Command<Args> {

let count = 1
const errors: ApiCommandError[] = []
const results: BaseResponse[] = []
const results: DeleteResult[] = []
for (const id of secretsToDelete) {
cmdLog.setState({ msg: `Deleting secrets... → ${count}/${secretsToDelete.length}` })
count++
try {
const res = await api.delete<BaseResponse>(`/secrets/${id}`)
results.push(res)
results.push({ id, status: res.status })
} catch (err) {
errors.push({
identifier: id,
Expand All @@ -80,15 +75,13 @@ export class SecretsDeleteCommand extends Command<Args> {
}
}

handleBulkOperationResult({
return handleBulkOperationResult({
log,
cmdLog,
errors,
action: "delete",
resource: "secret",
successCount: results.length,
results,
})

return { result: { errors, results } }
}
}
43 changes: 3 additions & 40 deletions core/src/commands/enterprise/secrets/secrets-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,27 +13,11 @@ import { ListSecretsResponse } from "@garden-io/platform-api-types"
import { printHeader } from "../../../logger/util"
import { dedent, deline, renderTable } from "../../../util/string"
import { Command, CommandParams, CommandResult } from "../../base"
import { applyFilter, getProject, noApiMsg } from "../helpers"
import { applyFilter, getProject, makeSecretFromResponse, noApiMsg, SecretResult } from "../helpers"
import chalk from "chalk"
import { sortBy } from "lodash"
import { StringsParameter } from "../../../cli/params"

interface Secret {
id: number
createdAt: string
updatedAt: string
name: string
environment?: {
name: string
id: number
}
user?: {
name: string
id: number
vcsUsername: string
}
}

export const secretsListOpts = {
"filter-envs": new StringsParameter({
help: deline`Filter on environment. Use comma as a separator to filter on multiple environments.
Expand Down Expand Up @@ -67,7 +51,7 @@ export class SecretsListCommand extends Command<{}, Opts> {
printHeader(headerLog, "List secrets", "lock")
}

async action({ garden, log, opts }: CommandParams<{}, Opts>): Promise<CommandResult<Secret[]>> {
async action({ garden, log, opts }: CommandParams<{}, Opts>): Promise<CommandResult<SecretResult[]>> {
const envFilter = opts["filter-envs"] || []
const nameFilter = opts["filter-names"] || []
const userFilter = opts["filter-user-ids"] || []
Expand All @@ -81,28 +65,7 @@ export class SecretsListCommand extends Command<{}, Opts> {

const q = stringify({ projectId: project.id })
const res = await api.get<ListSecretsResponse>(`/secrets?${q}`)
const secrets = res.data.map((secret) => {
const ret: Secret = {
name: secret.name,
id: secret.id,
updatedAt: secret.updatedAt,
createdAt: secret.createdAt,
}
if (secret.environment) {
ret["environment"] = {
name: secret.environment.name,
id: secret.environment.id,
}
}
if (secret.user) {
ret["user"] = {
name: secret.user.name,
id: secret.user.id,
vcsUsername: secret.user.vcsUsername,
}
}
return ret
})
const secrets = res.data.map((secret) => makeSecretFromResponse(secret))

log.info("")

Expand Down
Loading

0 comments on commit 65303d4

Please sign in to comment.