From 66b0330ffcc3bf0fae899c02b22c49c801c70f02 Mon Sep 17 00:00:00 2001 From: Jayant Kumar <75439864+Jayk5@users.noreply.github.com> Date: Fri, 7 Jul 2023 19:03:23 +0530 Subject: [PATCH] feat: add env-configs:configure command (#183) Co-authored-by: Shashwat Khanna --- package.json | 3 +- src/commands/env-configs/configure.ts | 107 ++++++++++++++++ src/mix/api/env-configs-types.ts | 8 ++ src/mix/api/env-configs.ts | 27 +++- src/utils/flags.ts | 12 ++ test/commands/env-configs/configure.test.ts | 131 ++++++++++++++++++++ 6 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/commands/env-configs/configure.ts create mode 100644 test/commands/env-configs/configure.test.ts diff --git a/package.json b/package.json index c9084ef..46230ee 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "Oliver Daniel", "Vishal Sangave", "Pallavi Pawar", - "Krutika Patel" + "Krutika Patel", + "Jayant Kumar" ], "homepage": "https://github.com/nuance-communications/mix-cli", "keywords": [ diff --git a/src/commands/env-configs/configure.ts b/src/commands/env-configs/configure.ts new file mode 100644 index 0000000..f678768 --- /dev/null +++ b/src/commands/env-configs/configure.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2022, Nuance, Inc. and its contributors. + * All rights reserved. + * + * This source code is licensed under the Apache-2.0 license found in + * the LICENSE file in the root directory of this source tree. + */ + +import {FlagOutput} from '@oclif/core/lib/interfaces' +import makeDebug from 'debug' +import chalk from 'chalk' + +import * as MixFlags from '../../utils/flags' +import MixCommand from '../../utils/base/mix-command' +import {MixClient, MixResponse} from '../../mix/types' +import {EnvConfigsConfigureParams} from '../../mix/api/env-configs-types' +import {configureEnvConfigWithGeo, configureEnvConfigWithoutGeo} from '../../mix/api/env-configs' + +const debug = makeDebug('mix:commands:env-configs:configure') + +export default class EnvConfigsList extends MixCommand { + static description = `Configure an environment configuration + + Environment configurations provide default values either for the project as a whole + or for a specific environment geography. If an environment geography doest not have + a default for a specific configuration, then the default for the project is used. + + Using this command with only the 'project' flag configures the project-level default value + for the given configuration label. Using this command with the 'env' and 'env-geo' flags + in addition to the 'project' flag configures the default value for the given configuration + label targeting the specified environment geography. + ` + + static examples = [ + 'Configure an environment configuration project default', + 'mix env-configs:configure -P 1922 --label=GRAMMAR_BASE_PATH --value=https://www.example.com/grammars', + 'Configure an environment configuration for a specific environment geography', + 'mix env-configs:configure -P 1922 --env=1923 --env-geo=9 --label=GRAMMAR_BASE_PATH --value=https://www.example.com/grammars', + ] + + static flags = { + project: MixFlags.projectFlag, + env: { + ...MixFlags.envIDFlag, + dependsOn: ['env-geo'], + }, + 'env-geo': { + ...MixFlags.envGeoIDFlag, + dependsOn: ['env'], + }, + label: MixFlags.labelFlag, + value: MixFlags.valueFlag, + } + + async buildRequestParameters(options: Partial): Promise { + debug('buildRequestParameters()') + const { + env, + 'env-geo': envGeoId, + label, + project, + value, + } = options + + return { + envId: env, + envGeoId, + label, + projectId: project, + value, + } + } + + doRequest(client: MixClient, params: EnvConfigsConfigureParams): Promise { + debug('doRequest()') + const {envId} = params + + if (envId) { + return configureEnvConfigWithGeo(client, params) + } + + return configureEnvConfigWithoutGeo(client, params) + } + + outputHumanReadable() { + debug('outputHumanReadable()') + + this.log() + if (this.options.env) { + this.log(`Environment configuration ${this.options.label} configured successfully for project ${chalk.cyan(this.options.project)}`) + this.log(`In environment geography ${chalk.cyan(this.options['env-geo'])} of environment ${chalk.cyan(this.options.env)}`) + } else { + this.log(`Environment configuration ${this.options.label} default configured successfully for project ${chalk.cyan(this.options.project)}`) + } + + this.log() + } + + setRequestActionMessage(_options: any) { + debug('setRequestActionMessage()') + + this.requestActionMessage = `Configuring environment configuration ${this.options.label} for project ${this.options.project}` + if (this.options.env) { + this.requestActionMessage += ` in environment geography ${this.options['env-geo']} of environment ${this.options.env}` + } + } +} diff --git a/src/mix/api/env-configs-types.ts b/src/mix/api/env-configs-types.ts index 030fda6..ab67585 100644 --- a/src/mix/api/env-configs-types.ts +++ b/src/mix/api/env-configs-types.ts @@ -2,6 +2,14 @@ export type EnvConfigsListParams = { projectId: string } +export type EnvConfigsConfigureParams = { + projectId: string, + envId: string | undefined, + envGeoId: string | undefined, + label: string, + value: string, +} + export type EnvConfigListResponse = { projectDefaults: { label: string, diff --git a/src/mix/api/env-configs.ts b/src/mix/api/env-configs.ts index c98757c..0c360e6 100644 --- a/src/mix/api/env-configs.ts +++ b/src/mix/api/env-configs.ts @@ -10,7 +10,7 @@ import makeDebug from 'debug' import buildURL from './utils/build-url' import {MixClient, MixResponse} from '../types' -import {EnvConfigsListParams} from './env-configs-types' +import {EnvConfigsListParams, EnvConfigsConfigureParams} from './env-configs-types' const debug = makeDebug('mix:api:env-configs') @@ -26,3 +26,28 @@ export async function listEnvConfigs(client: MixClient, params: EnvConfigsListPa url: buildURL(client.getServer(), `/v4/projects/${projectId}/env-configs`), }) } + +export async function configureEnvConfigWithGeo(client: MixClient, params: EnvConfigsConfigureParams): Promise { + debug('configureEnvConfigWithGeo()') + const {projectId, envId, envGeoId, label, value} = params + return client.request({ + method: 'put', + url: buildURL(client.getServer(), `/v4/environments/${envId}/geographies/${envGeoId}/configs/${label}`), + options: { + params: { + projectId, + }, + }, + data: `"${value}"`, + }) +} + +export async function configureEnvConfigWithoutGeo(client: MixClient, params: EnvConfigsConfigureParams): Promise { + debug('configureEnvConfigWithoutGeo()') + const {projectId, label, value} = params + return client.request({ + method: 'put', + url: buildURL(client.getServer(), `/v4/projects/${projectId}/env-configs/${label}`), + data: `"${value}"`, + }) +} diff --git a/src/utils/flags.ts b/src/utils/flags.ts index da58340..fda630f 100644 --- a/src/utils/flags.ts +++ b/src/utils/flags.ts @@ -158,6 +158,10 @@ export const entityTypeFlag = Flags.string({ required: true, }) +export const envIDFlag = Flags.integer({ + description: 'environment ID', +}) + export const envGeoIDFlag = Flags.integer({ description: envGeoIDDesc, }) @@ -235,6 +239,10 @@ export const jsonFlag = Flags.boolean({ exclusive: ['csv', 'yaml'], }) +export const labelFlag = Flags.string({ + description: 'environment configuration name', +}) + export const limitFlag = Flags.integer({ description: 'limit maximum results returned (defaults to Mix API behavior)', }) @@ -489,6 +497,10 @@ export const toEntityTypeFlag = Flags.string({ required: true, }) +export const valueFlag = Flags.string({ + description: 'environment configuration value', +}) + export const watchFlag = Flags.boolean({ default: false, description: 'poll status of job every minute', diff --git a/test/commands/env-configs/configure.test.ts b/test/commands/env-configs/configure.test.ts new file mode 100644 index 0000000..1be9368 --- /dev/null +++ b/test/commands/env-configs/configure.test.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2022, Nuance, Inc. and its contributors. + * All rights reserved. + * + * This source code is licensed under the Apache-2.0 license found in + * the LICENSE file in the root directory of this source tree. + */ + +import { expect, test } from "@oclif/test"; + +const testData = require("../../test-data"); +const serverURL = `https://${testData.server}`; + +describe("env-configs:configure command", () => { + let project = 1; + let envId = 1001; + let envGeoId = 9; + let label = "GRAMMAR_BASE_PATH"; + let value = "http://www.test.com/"; + + test + .env(testData.env) + .nock(serverURL, (api) => + api + .put(`/v4/projects/${project}/env-configs/${label}`, `"${value}"`) + .reply(200) + ) + .stdout() + .command([ + "env-configs:configure", + `--project=${project}`, + `--label=${label}`, + `--value=${value}`, + ]) + .it("configures default environment configuration", (ctx) => { + const [firstLine, secondLine] = ctx.stdout.split("\n"); + expect(secondLine).to.contain(`configured successfully`); + }); + + test + .env(testData.env) + .nock(serverURL, (api) => + api + .put( + `/v4/environments/${envId}/geographies/${envGeoId}/configs/${label}`, + `"${value}"` + ) + .query({ + projectId: project, + }) + .reply(200) + ) + .stdout() + .command([ + "env-configs:configure", + `--project=${project}`, + `--env=${envId}`, + `--env-geo=${envGeoId}`, + `--label=${label}`, + `--value=${value}`, + ]) + .it("configures environment configuration with env and env-geo", (ctx) => { + const [firstLine, secondLine] = ctx.stdout.split("\n"); + expect(secondLine).to.contain(`configured successfully`); + }); + + test + .env(testData.env) + .stderr() + .command([ + "env-configs:configure", + `--project=${project}`, + `--env=${envId}`, + `--label=${label}`, + `--value=${value}`, + ]) + .catch((ctx) => { + expect(ctx.message).to.contain( + `All of the following must be provided when using --env: --env-geo` + ); + }) + .it("fails to configure environment configuration with env but no env-geo"); + + test + .env(testData.env) + .stderr() + .command([ + "env-configs:configure", + `--project=${project}`, + `--env-geo=${envGeoId}`, + `--label=${label}`, + `--value=${value}`, + ]) + .catch((ctx) => { + expect(ctx.message).to.contain( + `All of the following must be provided when using --env-geo: --env` + ); + }) + .it("fails to configure environment configuration with env-geo but no env"); + + test + .env(testData.env) + .stderr() + .command([ + "env-configs:configure", + `--label=${label}`, + `--value=${value}`, + ]) + .catch((ctx) => { + expect(ctx.message).to.contain( + `Missing required flag project` + ); + }) + .it("fails to configure environment configuration with no project ID"); + + test + .env(testData.env) + .stderr() + .command([ + "env-configs:configure", + `--project=ok123`, + `--label=${label}`, + `--value=${value}`, + ]) + .catch((ctx) => { + expect(ctx.message).to.contain( + 'Expected an integer' + ); + }) + .it("fails to configure environment configuration with invalid project ID"); +});