Skip to content

Commit

Permalink
improvement(cloud): better secrets errors
Browse files Browse the repository at this point in the history
We now log more informative error messages when a missing secret is
referenced, and when trying to fetch secrets for an environment that
doesn't exist on the backend.
  • Loading branch information
thsig committed Jun 8, 2021
1 parent 3766ad2 commit 7d64bdc
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 33 deletions.
12 changes: 10 additions & 2 deletions core/src/config/module-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ export async function resolveModuleTemplate(
...resource,
modules: [],
}
const context = new ProjectConfigContext({ ...garden, branch: garden.vcsBranch })
const branch = garden.vcsBranch
const loggedIn = !!garden.enterpriseApi
const enterpriseDomain = garden.enterpriseApi?.domain
const context = new ProjectConfigContext({ ...garden, branch, loggedIn, enterpriseDomain })
const resolved = resolveTemplateStrings(partial, context)

// Validate the partial config
Expand Down Expand Up @@ -112,7 +115,10 @@ export async function resolveTemplatedModule(
// Resolve template strings for fields. Note that inputs are partially resolved, and will be fully resolved later
// when resolving the resolving the resulting modules. Inputs that are used in module names must however be resolvable
// immediately.
const templateContext = new EnvironmentConfigContext({ ...garden, branch: garden.vcsBranch })
const branch = garden.vcsBranch
const loggedIn = !!garden.enterpriseApi
const enterpriseDomain = garden.enterpriseApi?.domain
const templateContext = new EnvironmentConfigContext({ ...garden, branch, loggedIn, enterpriseDomain })
const resolvedWithoutInputs = resolveTemplateStrings(
{ ...config, spec: omit(config.spec, "inputs") },
templateContext
Expand Down Expand Up @@ -160,6 +166,8 @@ export async function resolveTemplatedModule(
const context = new ModuleTemplateConfigContext({
...garden,
branch: garden.vcsBranch,
loggedIn: !!garden.enterpriseApi,
enterpriseDomain,
parentName: resolved.name,
templateName: template.name,
inputs: partiallyResolvedInputs,
Expand Down
12 changes: 12 additions & 0 deletions core/src/config/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,8 @@ export function resolveProjectConfig({
artifactsPath,
branch,
username,
loggedIn,
enterpriseDomain,
secrets,
commandInfo,
}: {
Expand All @@ -411,6 +413,8 @@ export function resolveProjectConfig({
artifactsPath: string
branch: string
username: string
loggedIn: boolean
enterpriseDomain: string | undefined
secrets: PrimitiveMap
commandInfo: CommandInfo
}): ProjectConfig {
Expand All @@ -431,6 +435,8 @@ export function resolveProjectConfig({
artifactsPath,
branch,
username,
loggedIn,
enterpriseDomain,
secrets,
commandInfo,
})
Expand Down Expand Up @@ -511,6 +517,8 @@ export async function pickEnvironment({
artifactsPath,
branch,
username,
loggedIn,
enterpriseDomain,
secrets,
commandInfo,
}: {
Expand All @@ -519,6 +527,8 @@ export async function pickEnvironment({
artifactsPath: string
branch: string
username: string
loggedIn: boolean
enterpriseDomain: string | undefined
secrets: PrimitiveMap
commandInfo: CommandInfo
}) {
Expand Down Expand Up @@ -552,6 +562,8 @@ export async function pickEnvironment({
branch,
username,
variables: projectVariables,
loggedIn,
enterpriseDomain,
secrets,
commandInfo,
})
Expand Down
12 changes: 12 additions & 0 deletions core/src/config/template-contexts/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ export abstract class ConfigContext {
return joi.object().keys(schemas).required()
}

/**
* Override this method to add more context to error messages thrown in the `resolve` method when a missing key is
* referenced.
*/
getMissingKeyErrorFooter(_key: ContextKeySegment, _path: ContextKeySegment[]): string {
return ""
}

resolve({ key, nodePath, opts }: ContextResolveParams): ContextResolveOutput {
const path = renderKeyPath(key)
const fullPath = renderKeyPath(nodePath.concat(key))
Expand Down Expand Up @@ -178,6 +186,10 @@ export abstract class ConfigContext {
if (available && available.length) {
message += chalk.red(" Available keys: " + naturalList(available.sort().map((k) => chalk.white(k))) + ".")
}
const messageFooter = this.getMissingKeyErrorFooter(nextKey, nestedNodePath.slice(0, -1))
if (messageFooter) {
message += `\n\n${messageFooter}`
}
}

// If we're allowing partial strings, we throw the error immediately to end the resolution flow. The error
Expand Down
40 changes: 39 additions & 1 deletion core/src/config/template-contexts/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import { last, isEmpty } from "lodash"
import chalk from "chalk"
import { PrimitiveMap, joiIdentifierMap, joiStringMap, joiPrimitive, DeepPrimitiveMap, joiVariables } from "../common"
import { joi } from "../common"
import { deline, dedent } from "../../util/string"
import { schema, ConfigContext } from "./base"
import { schema, ConfigContext, ContextKeySegment } from "./base"
import { CommandInfo } from "../../plugin-context"

class LocalContext extends ConfigContext {
Expand Down Expand Up @@ -200,7 +202,9 @@ export class DefaultEnvironmentContext extends ConfigContext {
}

export interface ProjectConfigContextParams extends DefaultEnvironmentContextParams {
loggedIn: boolean
secrets: PrimitiveMap
enterpriseDomain: string | undefined
}

/**
Expand All @@ -220,10 +224,44 @@ export class ProjectConfigContext extends DefaultEnvironmentContext {
})
)
public secrets: PrimitiveMap
private _enterpriseDomain: string | undefined
private _loggedIn: boolean

getMissingKeyErrorFooter(_key: ContextKeySegment, path: ContextKeySegment[]): string {
if (last(path) !== "secrets") {
return ""
}

if (!this._loggedIn) {
return dedent`
You are not logged in to Garden Enterprise, but one or more secrets are referenced in template strings in your Garden configuration files.
Please log in via the ${chalk.green("garden login")} command to use Garden with secrets.
`
}

if (isEmpty(this.secrets)) {
// TODO: Provide project ID (not UID) to this class so we can render a full link to the secrets section of the
// project. To do this, we'll also need to handle the case where the project doesn't already exist in GE/CLoud.
const suffix = this._enterpriseDomain
? ` To create secrets, please visit ${this._enterpriseDomain} and navigate to the secrets section for this project.`
: ""
return deline`
Looks like no secrets have been created for this project and/or environment in Garden Enterprise.${suffix}
`
} else {
return deline`
Please make sure that all required secrets for this project exist in Garden Enterprise, and are accessible in this
environment.
`
}
}

constructor(params: ProjectConfigContextParams) {
super(params)
this._loggedIn = params.loggedIn
this.secrets = params.secrets
this._enterpriseDomain = params.enterpriseDomain
}
}

Expand Down
2 changes: 2 additions & 0 deletions core/src/config/template-contexts/workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export class WorkflowConfigContext extends EnvironmentConfigContext {
branch: garden.vcsBranch,
username: garden.username,
variables: garden.variables,
loggedIn: !!garden.enterpriseApi,
enterpriseDomain: garden.enterpriseApi?.domain,
secrets: garden.secrets,
commandInfo: garden.commandInfo,
})
Expand Down
6 changes: 5 additions & 1 deletion core/src/enterprise/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ export const authTokenHeader =

export const makeAuthHeader = (clientAuthToken: string) => ({ [authTokenHeader]: clientAuthToken })

export function isGotError(error: any, statusCode: number): error is GotHttpError {
return error instanceof GotHttpError && error.response.statusCode === statusCode
}

function is401Error(error: any): error is GotHttpError {
return error instanceof GotHttpError && error.response.statusCode === 401
return isGotError(error, 401)
}

const refreshThreshold = 10 // Threshold (in seconds) subtracted to jwt validity when checking if a refresh is needed
Expand Down
22 changes: 18 additions & 4 deletions core/src/enterprise/get-secrets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@

import { LogEntry } from "../logger/log-entry"
import { StringMap } from "../config/common"
import { EnterpriseApi } from "./api"
import { EnterpriseApi, isGotError } from "./api"
import { BaseResponse } from "@garden-io/platform-api-types"
import { deline } from "../util/string"

export interface GetSecretsParams {
log: LogEntry
Expand All @@ -26,9 +27,22 @@ export async function getSecrets({ log, environmentName, enterpriseApi }: GetSec
)
secrets = res.data
} catch (err) {
log.error("An error occurred while fetching secrets for the project.")
log.debug(`Error: ${err.message}`)
throw err
if (isGotError(err, 404)) {
log.debug("No secrets were received from Garden Enterprise.")
log.debug("")
log.debug(deline`
Either the environment ${environmentName} does not exist in Garden Enterprise, or no project
with the id in your project configuration exists in Garden Enterprise.
`)
log.debug("")
log.debug(deline`
Please visit ${enterpriseApi.domain} to review the environments and projects currently
in the system.
`)
} else {
log.error("An error occurred while fetching secrets for the project.")
throw err
}
}

const emptyKeys = Object.keys(secrets).filter((key) => !secrets[key])
Expand Down
7 changes: 7 additions & 0 deletions core/src/garden.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1211,12 +1211,17 @@ export async function resolveGardenParams(currentDirectory: string, opts: Garden
}
}

const loggedIn = !!enterpriseApi
const enterpriseDomain = enterpriseApi?.domain

config = resolveProjectConfig({
defaultEnvironment: defaultEnvironmentName,
config,
artifactsPath,
branch: vcsBranch,
username: _username,
loggedIn,
enterpriseDomain,
secrets,
commandInfo,
})
Expand All @@ -1229,6 +1234,8 @@ export async function resolveGardenParams(currentDirectory: string, opts: Garden
artifactsPath,
branch: vcsBranch,
username: _username,
loggedIn,
enterpriseDomain,
secrets,
commandInfo,
})
Expand Down
Loading

0 comments on commit 7d64bdc

Please sign in to comment.