From 8cf4077a05681eb5a21c7266d71baf71e73acaf6 Mon Sep 17 00:00:00 2001 From: Maxi Date: Wed, 28 Aug 2024 20:50:20 +0100 Subject: [PATCH 1/4] feat: add environment secrets support [sc-21028] --- packages/cli/src/commands/env/add.ts | 10 ++++++++-- packages/cli/src/commands/env/pull.ts | 17 ++++++++++++++--- packages/cli/src/commands/env/update.ts | 10 ++++++++-- packages/cli/src/constructs/key-value-pair.ts | 1 + packages/cli/src/rest/environment-variables.ts | 9 +++++---- 5 files changed, 36 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/commands/env/add.ts b/packages/cli/src/commands/env/add.ts index 6c3c5a19..382b6cda 100644 --- a/packages/cli/src/commands/env/add.ts +++ b/packages/cli/src/commands/env/add.ts @@ -12,6 +12,12 @@ export default class EnvAdd extends AuthCommand { description: 'Indicate that the environment variable will be locked.', default: false, }), + secret: Flags.boolean({ + char: 's', + description: 'Indicate that the environment variable will be secret, the value will not be revealed anywhere.', + default: false, + exclusive: ['locked'], + }), } static args = { @@ -29,7 +35,7 @@ export default class EnvAdd extends AuthCommand { async run (): Promise { const { flags, args } = await this.parse(EnvAdd) - const { locked } = flags + const { locked, secret } = flags const envVariableName = args.key let envValue = '' @@ -40,7 +46,7 @@ export default class EnvAdd extends AuthCommand { envValue = await ux.prompt(`What is the value of ${envVariableName}?`, { type: 'mask' }) } try { - await api.environmentVariables.add(envVariableName, envValue, locked) + await api.environmentVariables.add(envVariableName, envValue, locked, secret) this.log(`Environment variable ${envVariableName} added.`) } catch (err: any) { if (err?.response?.status === 409) { diff --git a/packages/cli/src/commands/env/pull.ts b/packages/cli/src/commands/env/pull.ts index 9249dffd..1a276be6 100644 --- a/packages/cli/src/commands/env/pull.ts +++ b/packages/cli/src/commands/env/pull.ts @@ -7,6 +7,8 @@ import { escapeValue } from '../../services/util' import * as fs from 'fs/promises' const CONTENTS_PREFIX = '# Created by Checkly CLI\n' +const CONTENTS_ENV_VARS = '# Environment variables\n' +const CONTENTS_SECRET_VARS = '# Secret variables\n' export default class EnvPull extends AuthCommand { static hidden = false @@ -36,14 +38,23 @@ export default class EnvPull extends AuthCommand { const filepath = path.resolve(args.filename) const filename = path.basename(filepath) const { data: environmentVariables } = await api.environmentVariables.getAll() + // create an file in current directory and save the env vars there - const env = CONTENTS_PREFIX + environmentVariables.map(({ key, value }) => `${key}=${escapeValue(value)}`).join('\n') + '\n' + let fileContent = CONTENTS_PREFIX + fileContent += CONTENTS_ENV_VARS + fileContent += environmentVariables + .filter(({ secret }) => !secret) + .map(({ key, value }) => `${key}=${escapeValue(value)}`).join('\n') + '\n' + fileContent += CONTENTS_SECRET_VARS + fileContent += environmentVariables + .filter(({ secret }) => secret) + .map(({ key, value }) => `${key}=${escapeValue(value)}`).join('\n') + '\n' // wx will cause the write to fail if the file already exists // https://nodejs.org/api/fs.html#file-system-flags const flag = force ? 'w' : 'wx' try { - await fs.writeFile(filepath, env, { flag }) + await fs.writeFile(filepath, fileContent, { flag }) } catch (err: any) { // By catching EEXIST rather than checking fs.existsSync, // we avoid a race condition when a file is created between writing and checking @@ -57,7 +68,7 @@ export default class EnvPull extends AuthCommand { this.log('Cancelled. No changes made.') return } - await fs.writeFile(filepath, env) + await fs.writeFile(filepath, fileContent) } } this.log(`Success! Environment variables written to ${filename}.`) diff --git a/packages/cli/src/commands/env/update.ts b/packages/cli/src/commands/env/update.ts index dbf4814e..150b69b9 100644 --- a/packages/cli/src/commands/env/update.ts +++ b/packages/cli/src/commands/env/update.ts @@ -12,6 +12,12 @@ export default class EnvUpdate extends AuthCommand { description: 'Indicate if environment variable is locked.', default: false, }), + secret: Flags.boolean({ + char: 's', + description: 'Indicate that the environment variable will be secret, the value will not be revealed anywhere.', + default: false, + exclusive: ['locked'], + }), } static args = { @@ -29,7 +35,7 @@ export default class EnvUpdate extends AuthCommand { async run (): Promise { const { flags, args } = await this.parse(EnvUpdate) - const { locked } = flags + const { locked, secret } = flags const envVariableName = args.key let envValue = '' @@ -40,7 +46,7 @@ export default class EnvUpdate extends AuthCommand { envValue = await ux.prompt(`What is the value of ${envVariableName}?`, { type: 'mask' }) } try { - await api.environmentVariables.update(envVariableName, envValue, locked) + await api.environmentVariables.update(envVariableName, envValue, locked, secret) this.log(`Environment variable ${envVariableName} updated.`) } catch (err: any) { if (err?.response?.status === 404) { diff --git a/packages/cli/src/constructs/key-value-pair.ts b/packages/cli/src/constructs/key-value-pair.ts index 0438f671..0ee36238 100644 --- a/packages/cli/src/constructs/key-value-pair.ts +++ b/packages/cli/src/constructs/key-value-pair.ts @@ -2,4 +2,5 @@ export default interface KeyValuePair { key: string value: string locked?: boolean + secret?: boolean } diff --git a/packages/cli/src/rest/environment-variables.ts b/packages/cli/src/rest/environment-variables.ts index cdbc4176..b4c13ed5 100644 --- a/packages/cli/src/rest/environment-variables.ts +++ b/packages/cli/src/rest/environment-variables.ts @@ -4,6 +4,7 @@ export interface EnvironmentVariable { key: string value: string locked: boolean + secret?: boolean } class EnvironmentVariables { @@ -20,8 +21,8 @@ class EnvironmentVariables { return this.api.delete(`/v1/variables/${environmentVariableKey}`) } - add (environmentVariableKey: string, environmentVariableValue: string, locked: boolean) { - return this.api.post('/v1/variables', { key: environmentVariableKey, value: environmentVariableValue, locked }) + add (environmentVariableKey: string, environmentVariableValue: string, locked = false, secret = false) { + return this.api.post('/v1/variables', { key: environmentVariableKey, value: environmentVariableValue, locked, secret }) } get (environmentVariableKey: string) { @@ -29,8 +30,8 @@ class EnvironmentVariables { } // update environment variable with default locked value false - update (environmentVariableKey: string, environmentVariableValue: string, locked = false) { - return this.api.put(`/v1/variables/${environmentVariableKey}`, { value: environmentVariableValue, locked }) + update (environmentVariableKey: string, environmentVariableValue: string, locked = false, secret = false) { + return this.api.put(`/v1/variables/${environmentVariableKey}`, { value: environmentVariableValue, locked, secret }) } } From 1aa6287ef8a23d9a8b6df226f9083226e71c351c Mon Sep 17 00:00:00 2001 From: Maxi Date: Thu, 29 Aug 2024 11:58:51 +0100 Subject: [PATCH 2/4] feat: update copy --- packages/cli/src/commands/env/add.ts | 2 +- packages/cli/src/commands/env/update.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/env/add.ts b/packages/cli/src/commands/env/add.ts index 382b6cda..e63f1098 100644 --- a/packages/cli/src/commands/env/add.ts +++ b/packages/cli/src/commands/env/add.ts @@ -14,7 +14,7 @@ export default class EnvAdd extends AuthCommand { }), secret: Flags.boolean({ char: 's', - description: 'Indicate that the environment variable will be secret, the value will not be revealed anywhere.', + description: 'Indicate that the environment variable will be secret.', default: false, exclusive: ['locked'], }), diff --git a/packages/cli/src/commands/env/update.ts b/packages/cli/src/commands/env/update.ts index 150b69b9..243ac3fc 100644 --- a/packages/cli/src/commands/env/update.ts +++ b/packages/cli/src/commands/env/update.ts @@ -14,7 +14,7 @@ export default class EnvUpdate extends AuthCommand { }), secret: Flags.boolean({ char: 's', - description: 'Indicate that the environment variable will be secret, the value will not be revealed anywhere.', + description: 'Indicate if environment variable is secret.', default: false, exclusive: ['locked'], }), From 4b25caa131f2b466a78be1829c26e6a9ae24b367 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Thu, 29 Aug 2024 15:51:06 +0100 Subject: [PATCH 3/4] feat: use error message from the API [sc-21028] --- packages/cli/src/commands/env/update.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/cli/src/commands/env/update.ts b/packages/cli/src/commands/env/update.ts index 243ac3fc..7889cc02 100644 --- a/packages/cli/src/commands/env/update.ts +++ b/packages/cli/src/commands/env/update.ts @@ -49,9 +49,14 @@ export default class EnvUpdate extends AuthCommand { await api.environmentVariables.update(envVariableName, envValue, locked, secret) this.log(`Environment variable ${envVariableName} updated.`) } catch (err: any) { + if (err?.response?.status === 400 && err?.response?.data?.message) { + throw new Error(err.response.data.message) + } + if (err?.response?.status === 404) { throw new Error(`Environment variable ${envVariableName} not found.`) } + throw err } } From e0f21fe63d1429287c19a8413fc4666f85f70c42 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Fri, 30 Aug 2024 10:10:01 +0100 Subject: [PATCH 4/4] feat: tweak confirm messages for better DX --- packages/cli/src/commands/env/add.ts | 5 ++++- packages/cli/src/commands/env/update.ts | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/env/add.ts b/packages/cli/src/commands/env/add.ts index e63f1098..c5a7447e 100644 --- a/packages/cli/src/commands/env/add.ts +++ b/packages/cli/src/commands/env/add.ts @@ -47,7 +47,10 @@ export default class EnvAdd extends AuthCommand { } try { await api.environmentVariables.add(envVariableName, envValue, locked, secret) - this.log(`Environment variable ${envVariableName} added.`) + this.log(secret + ? `Secret environment variable ${envVariableName} added.` + : `Environment variable ${envVariableName} added.`, + ) } catch (err: any) { if (err?.response?.status === 409) { throw new Error(`Environment variable ${envVariableName} already exists.`) diff --git a/packages/cli/src/commands/env/update.ts b/packages/cli/src/commands/env/update.ts index 7889cc02..94a46b44 100644 --- a/packages/cli/src/commands/env/update.ts +++ b/packages/cli/src/commands/env/update.ts @@ -47,7 +47,10 @@ export default class EnvUpdate extends AuthCommand { } try { await api.environmentVariables.update(envVariableName, envValue, locked, secret) - this.log(`Environment variable ${envVariableName} updated.`) + this.log(secret + ? `Secret environment variable ${envVariableName} updated.` + : `Environment variable ${envVariableName} updated.`, + ) } catch (err: any) { if (err?.response?.status === 400 && err?.response?.data?.message) { throw new Error(err.response.data.message)