Skip to content

Commit

Permalink
Added the ability to work with secrets in the CLI. set, delete and ge…
Browse files Browse the repository at this point in the history
…t list of all secrets keys, per region. (#100)

* Added the ability to work with secrets in the CLI. set, delete and get list of all secrets keys, per region.

* When setting or deleting value in specific region, list-keys for this region and not the default region.
  • Loading branch information
Shaharshaki2 authored Sep 10, 2024
1 parent 08d710e commit 6f3b3cf
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 35 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mondaycom/apps-cli",
"version": "4.0.1",
"version": "4.1.0",
"description": "A cli tool to manage apps (and monday-code projects) in monday.com",
"author": "monday.com Apps Team",
"type": "module",
Expand Down
41 changes: 23 additions & 18 deletions src/commands/code/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,43 @@ import { Flags } from '@oclif/core';
import { Relationship } from '@oclif/core/lib/interfaces/parser';

import { AuthenticatedCommand } from 'commands-base/authenticated-command';
import { APP_ENV_MANAGEMENT_MODES } from 'consts/manage-app-env';
import { APP_VARIABLE_MANAGEMENT_MODES } from 'consts/manage-app-variables';
import { DynamicChoicesService } from 'services/dynamic-choices-service';
import { handleEnvironmentRequest, listAppEnvKeys } from 'services/manage-app-env-service';
import { PromptService } from 'services/prompt-service';
import { ManageAppEnvFlags } from 'types/commands/manage-app-env';
import { ManageAppVariableFlags } from 'types/commands/manage-app-variable';
import { AppId } from 'types/general';
import { Region } from 'types/general/region';
import logger from 'utils/logger';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MODES_WITH_KEYS: Array<APP_ENV_MANAGEMENT_MODES> = [
APP_ENV_MANAGEMENT_MODES.SET,
APP_ENV_MANAGEMENT_MODES.DELETE,
const MODES_WITH_KEYS: Array<APP_VARIABLE_MANAGEMENT_MODES> = [
APP_VARIABLE_MANAGEMENT_MODES.SET,
APP_VARIABLE_MANAGEMENT_MODES.DELETE,
];

const isKeyRequired = (mode: APP_ENV_MANAGEMENT_MODES) => MODES_WITH_KEYS.includes(mode);
const isValueRequired = (mode: APP_ENV_MANAGEMENT_MODES) => mode === APP_ENV_MANAGEMENT_MODES.SET;
const isKeyRequired = (mode: APP_VARIABLE_MANAGEMENT_MODES) => MODES_WITH_KEYS.includes(mode);
const isValueRequired = (mode: APP_VARIABLE_MANAGEMENT_MODES) => mode === APP_VARIABLE_MANAGEMENT_MODES.SET;

const promptForModeIfNotProvided = async (mode?: APP_ENV_MANAGEMENT_MODES) => {
const promptForModeIfNotProvided = async (mode?: APP_VARIABLE_MANAGEMENT_MODES) => {
if (!mode) {
mode = await PromptService.promptSelectionWithAutoComplete<APP_ENV_MANAGEMENT_MODES>(
mode = await PromptService.promptSelectionWithAutoComplete<APP_VARIABLE_MANAGEMENT_MODES>(
'Select app environment variables management mode',
Object.values(APP_ENV_MANAGEMENT_MODES),
Object.values(APP_VARIABLE_MANAGEMENT_MODES),
);
}

return mode;
};

const promptForKeyIfNotProvided = async (mode: APP_ENV_MANAGEMENT_MODES, appId: AppId, key?: string) => {
const promptForKeyIfNotProvided = async (
mode: APP_VARIABLE_MANAGEMENT_MODES,
appId: AppId,
key?: string,
region?: Region,
) => {
if (!key && isKeyRequired(mode)) {
const existingKeys = await listAppEnvKeys(appId);
const existingKeys = await listAppEnvKeys(appId, region);
key = await PromptService.promptSelectionWithAutoComplete('Enter key for environment variable', existingKeys, {
includeInputInSelection: true,
});
Expand All @@ -41,12 +47,12 @@ const promptForKeyIfNotProvided = async (mode: APP_ENV_MANAGEMENT_MODES, appId:
return key;
};

const promptForValueIfNotProvided = async (mode: APP_ENV_MANAGEMENT_MODES, value?: string) => {
const promptForValueIfNotProvided = async (mode: APP_VARIABLE_MANAGEMENT_MODES, value?: string) => {
if (!value && isValueRequired(mode)) {
value = await PromptService.promptForHiddenInput(
'value',
'Enter value for environment variable',
'You must enter a value value',
'You must enter a value',
);
}

Expand All @@ -58,7 +64,6 @@ const flagsWithModeRelationships: Relationship = {
flags: [
{
name: 'mode',

when: async (flags: Record<string, unknown>) => isValueRequired(flags.mode as (typeof MODES_WITH_KEYS)[number]),
},
],
Expand All @@ -79,7 +84,7 @@ export default class Env extends AuthenticatedCommand {
mode: Flags.string({
char: 'm',
description: 'management mode',
options: Object.values(APP_ENV_MANAGEMENT_MODES),
options: Object.values(APP_VARIABLE_MANAGEMENT_MODES),
}),
key: Flags.string({
char: 'k',
Expand All @@ -101,7 +106,7 @@ export default class Env extends AuthenticatedCommand {
const { flags } = await this.parse(Env);
const { region: strRegion } = flags;
const region = getRegionFromString(strRegion);
let { mode, key, value, appId } = flags as ManageAppEnvFlags;
let { mode, key, value, appId } = flags as ManageAppVariableFlags;

if (!appId) {
appId = Number(await DynamicChoicesService.chooseApp());
Expand All @@ -110,7 +115,7 @@ export default class Env extends AuthenticatedCommand {
const selectedRegion = await chooseRegionIfNeeded(region, { appId });

mode = await promptForModeIfNotProvided(mode);
key = await promptForKeyIfNotProvided(mode, appId, key);
key = await promptForKeyIfNotProvided(mode, appId, key, selectedRegion);
value = await promptForValueIfNotProvided(mode, value);
this.preparePrintCommand(this, { appId, mode, key, value, region: selectedRegion });

Expand Down
128 changes: 128 additions & 0 deletions src/commands/code/secret.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Flags } from '@oclif/core';
import { Relationship } from '@oclif/core/lib/interfaces/parser';

import { AuthenticatedCommand } from 'commands-base/authenticated-command';
import { APP_VARIABLE_MANAGEMENT_MODES } from 'consts/manage-app-variables';
import { DynamicChoicesService } from 'services/dynamic-choices-service';
import { handleSecretRequest, listAppSecretKeys } from 'services/manage-app-secret-service';
import { PromptService } from 'services/prompt-service';
import { ManageAppVariableFlags } from 'types/commands/manage-app-variable';
import { AppId } from 'types/general';
import { Region } from 'types/general/region';
import logger from 'utils/logger';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MODES_WITH_KEYS: Array<APP_VARIABLE_MANAGEMENT_MODES> = [
APP_VARIABLE_MANAGEMENT_MODES.SET,
APP_VARIABLE_MANAGEMENT_MODES.DELETE,
];

const isKeyRequired = (mode: APP_VARIABLE_MANAGEMENT_MODES) => MODES_WITH_KEYS.includes(mode);
const isValueRequired = (mode: APP_VARIABLE_MANAGEMENT_MODES) => mode === APP_VARIABLE_MANAGEMENT_MODES.SET;

const promptForModeIfNotProvided = async (mode?: APP_VARIABLE_MANAGEMENT_MODES) => {
if (!mode) {
mode = await PromptService.promptSelectionWithAutoComplete<APP_VARIABLE_MANAGEMENT_MODES>(
'Select app secret variables management mode',
Object.values(APP_VARIABLE_MANAGEMENT_MODES),
);
}

return mode;
};

const promptForKeyIfNotProvided = async (
mode: APP_VARIABLE_MANAGEMENT_MODES,
appId: AppId,
key?: string,
region?: Region,
) => {
if (!key && isKeyRequired(mode)) {
const existingKeys = await listAppSecretKeys(appId, region);
key = await PromptService.promptSelectionWithAutoComplete('Enter key for secret variable', existingKeys, {
includeInputInSelection: true,
});
}

return key;
};

const promptForValueIfNotProvided = async (mode: APP_VARIABLE_MANAGEMENT_MODES, value?: string) => {
if (!value && isValueRequired(mode)) {
value = await PromptService.promptForHiddenInput(
'value',
'Enter value for secret variable',
'You must enter a value',
);
}

return value;
};

const flagsWithModeRelationships: Relationship = {
type: 'all',
flags: [
{
name: 'mode',
when: async (flags: Record<string, unknown>) => isValueRequired(flags.mode as (typeof MODES_WITH_KEYS)[number]),
},
],
};

export default class Secret extends AuthenticatedCommand {
static description = 'Manage secret variables for your app hosted on monday-code.';

static examples = ['<%= config.bin %> <%= command.id %>'];

static flags = Secret.serializeFlags(
addRegionToFlags({
appId: Flags.integer({
char: 'i',
aliases: ['a'],
description: 'The id of the app to manage secret variables for',
}),
mode: Flags.string({
char: 'm',
description: 'management mode',
options: Object.values(APP_VARIABLE_MANAGEMENT_MODES),
}),
key: Flags.string({
char: 'k',
description: 'variable key [required for set and delete]]',
relationships: [flagsWithModeRelationships],
}),
value: Flags.string({
char: 'v',
description: 'variable value [required for set]',
relationships: [flagsWithModeRelationships],
}),
}),
);

static args = {};
DEBUG_TAG = 'secret';
public async run(): Promise<void> {
try {
const { flags } = await this.parse(Secret);
const { region: strRegion } = flags;
const region = getRegionFromString(strRegion);
let { mode, key, value, appId } = flags as ManageAppVariableFlags;

if (!appId) {
appId = Number(await DynamicChoicesService.chooseApp());
}

const selectedRegion = await chooseRegionIfNeeded(region, { appId });
mode = await promptForModeIfNotProvided(mode);
key = await promptForKeyIfNotProvided(mode, appId, key, selectedRegion);
value = await promptForValueIfNotProvided(mode, value);
this.preparePrintCommand(this, { appId, mode, key, value, region: selectedRegion });
await handleSecretRequest(appId, mode, key, value, selectedRegion);
} catch (error: any) {
logger.debug(error, this.DEBUG_TAG);

// need to signal to the parent process that the command failed
process.exit(1);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export enum APP_ENV_MANAGEMENT_MODES {
export enum APP_VARIABLE_MANAGEMENT_MODES {
LIST_KEYS = 'list-keys',
SET = 'set',
DELETE = 'delete',
Expand Down
8 changes: 8 additions & 0 deletions src/consts/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ export const appEnvironmentKeysUrl = (appId: AppId): string => {
return `/api/code/${appId}/env-keys`;
};

export const appSecretUrl = (appId: AppId, key: string): string => {
return `/api/code/${appId}/secrets/${key}`;
};

export const appSecretKeysUrl = (appId: AppId): string => {
return `/api/code/${appId}/secret-keys`;
};

export const appReleasesUrl = (appVersionId: AppId): string => {
return `/apps_ms/app-versions/${appVersionId}/releases`;
};
Expand Down
12 changes: 6 additions & 6 deletions src/services/manage-app-env-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { StatusCodes } from 'http-status-codes';

import { APP_ENV_MANAGEMENT_MODES } from 'consts/manage-app-env';
import { APP_VARIABLE_MANAGEMENT_MODES } from 'consts/manage-app-variables';
import { appEnvironmentKeysUrl, appEnvironmentUrl } from 'consts/urls';
import { execute } from 'services/api-service';
import { listAppEnvironmentKeysResponseSchema } from 'services/schemas/manage-app-env-service-schemas';
Expand Down Expand Up @@ -130,17 +130,17 @@ const handleEnvironmentListKeys = async (appId: AppId, region: Region | undefine
};

const MAP_MODE_TO_HANDLER: Record<
APP_ENV_MANAGEMENT_MODES,
APP_VARIABLE_MANAGEMENT_MODES,
(appId: AppId, region: Region | undefined, key: string, value: string) => Promise<void>
> = {
[APP_ENV_MANAGEMENT_MODES.SET]: handleEnvironmentSet,
[APP_ENV_MANAGEMENT_MODES.DELETE]: handleEnvironmentDelete,
[APP_ENV_MANAGEMENT_MODES.LIST_KEYS]: handleEnvironmentListKeys,
[APP_VARIABLE_MANAGEMENT_MODES.SET]: handleEnvironmentSet,
[APP_VARIABLE_MANAGEMENT_MODES.DELETE]: handleEnvironmentDelete,
[APP_VARIABLE_MANAGEMENT_MODES.LIST_KEYS]: handleEnvironmentListKeys,
};

export const handleEnvironmentRequest = async (
appId: AppId,
mode: APP_ENV_MANAGEMENT_MODES,
mode: APP_VARIABLE_MANAGEMENT_MODES,
key?: string,
value?: string,
region?: Region,
Expand Down
Loading

0 comments on commit 6f3b3cf

Please sign in to comment.