Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 4 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 %>'];
maorb-dev marked this conversation as resolved.
Show resolved Hide resolved

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,21 +130,21 @@
};

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,
) => {

Check warning on line 147 in src/services/manage-app-env-service.ts

View workflow job for this annotation

GitHub Actions / Run validations

Async arrow function has too many parameters (5). Maximum allowed is 4
if (!appId || !mode) {
throw new Error('appId and mode are required');
}
Expand Down
Loading
Loading