From c5a4436e98a90bf0bbea9a70f76c983647fa7ccd Mon Sep 17 00:00:00 2001 From: Yaron Yarimi Date: Wed, 15 May 2024 11:01:50 +0300 Subject: [PATCH] Feat: Environment ID argument (#131) * add support for environment id argument * fix env id flag * pass options obj to get environment func * refine get environment func * fix tests after changing param * add test for calling get environment with env id --- node/README.md | 10 ++- node/src/commands/deploy.js | 4 +- node/src/commands/destroy.js | 4 +- node/src/config/arguments.js | 8 +++ node/src/config/constants.js | 1 + node/src/lib/config-manager.js | 5 +- node/src/lib/deploy-utils.js | 25 ++++---- .../src/lib/set-deployment-approval-status.js | 2 +- node/tests/commands/deploy.spec.js | 14 ++-- node/tests/lib/deploy-utils.spec.js | 64 ++++++++++++------- 10 files changed, 87 insertions(+), 50 deletions(-) diff --git a/node/README.md b/node/README.md index d4fd824..ede339c 100644 --- a/node/README.md +++ b/node/README.md @@ -77,15 +77,20 @@ The order of precedence of the arguments is: - Alias: `-p` ### Blueprint ID -> The Blueprint ID you would like to deploy with (Required for new environments) +> The Blueprint ID you wish to deploy with (Required for new environments) - Usage: `--blueprintId` - Alias: `-b` ### Environment Name -> The environment name you would like to create, or deploy to an existing one (Required for existing environments) +> The environment name you wish to create, or deploy to an existing one (Required for existing environments) - Usage: `--environmentName` - Alias: `-e` +### Environment ID +> The environment id you wish to re-deploy. If both the environment name and ID are specified, the environment ID will take precedence +- Usage: `--environmentId` +- Alias: N/A + ### Workspace Name > (Optional) - A name for Terraform Workspace created for your new environment. This cannot be changed after an environment was created - Usage: `--workspaceName` @@ -138,6 +143,7 @@ This file holds your last action's required parameters and will spare you from r - `ENV0_PROJECT_ID` - `ENV0_BLUEPRINT_ID` - `ENV0_ENVIRONMENT_NAME` +- `ENV0_ENVIRONMENT_ID` ## API Reference diff --git a/node/src/commands/deploy.js b/node/src/commands/deploy.js index dc1e172..342fc46 100644 --- a/node/src/commands/deploy.js +++ b/node/src/commands/deploy.js @@ -2,7 +2,7 @@ const DeployUtils = require('../lib/deploy-utils'); const logger = require('../lib/logger'); const { options } = require('../config/constants'); -const { BLUEPRINT_ID, ENVIRONMENT_NAME, PROJECT_ID, WORKSPACE_NAME } = options; +const { BLUEPRINT_ID, WORKSPACE_NAME } = options; const assertBlueprintExistsOnInitialDeployment = options => { if (!options[BLUEPRINT_ID]) throw new Error('Missing blueprint ID on initial deployment'); @@ -26,7 +26,7 @@ const deploy = async (options, variables) => { const configurationChanges = getConfigurationChanges(variables); let deployment; - let environment = await deployUtils.getEnvironment(options[ENVIRONMENT_NAME], options[PROJECT_ID]); + let environment = await deployUtils.getEnvironment(options); if (!environment) { logger.info('Initial deployment detected!'); diff --git a/node/src/commands/destroy.js b/node/src/commands/destroy.js index 1d5a70a..4d8c2f1 100644 --- a/node/src/commands/destroy.js +++ b/node/src/commands/destroy.js @@ -3,7 +3,7 @@ const { options } = require('../config/constants'); const _ = require('lodash'); const { convertStringToBoolean, removeEmptyValuesFromObj } = require('../lib/general-utils'); -const { PROJECT_ID, ENVIRONMENT_NAME, REQUIRES_APPROVAL, SKIP_STATE_REFRESH } = options; +const { ENVIRONMENT_NAME, REQUIRES_APPROVAL, SKIP_STATE_REFRESH } = options; const assertEnvironmentExists = environment => { if (!environment) { @@ -14,7 +14,7 @@ const assertEnvironmentExists = environment => { const destroy = async options => { const deployUtils = new DeployUtils(); - const environment = await deployUtils.getEnvironment(options[ENVIRONMENT_NAME], options[PROJECT_ID]); + const environment = await deployUtils.getEnvironment(options); let status; assertEnvironmentExists(environment); diff --git a/node/src/config/arguments.js b/node/src/config/arguments.js index 705a990..f076565 100644 --- a/node/src/config/arguments.js +++ b/node/src/config/arguments.js @@ -8,6 +8,7 @@ const { BLUEPRINT_ID, WORKSPACE_NAME, ENVIRONMENT_NAME, + ENVIRONMENT_ID, ENVIRONMENT_VARIABLES, TERRAFORM_VARIABLES, SENSITIVE_ENVIRONMENT_VARIABLES, @@ -67,6 +68,13 @@ const argumentsMap = { prompt: 'Environment Name', group: ['deploy', 'destroy', 'approve', 'cancel', 'configure'] }, + [ENVIRONMENT_ID]: { + name: ENVIRONMENT_ID, + type: String, + description: 'The environment id you want to perform the action on', + prompt: 'Environment ID', + group: ['deploy', 'destroy', 'approve', 'cancel', 'configure'] + }, [BLUEPRINT_ID]: { name: BLUEPRINT_ID, alias: 'b', diff --git a/node/src/config/constants.js b/node/src/config/constants.js index 61e8d45..0c7c466 100644 --- a/node/src/config/constants.js +++ b/node/src/config/constants.js @@ -5,6 +5,7 @@ const options = { PROJECT_ID: 'projectId', BLUEPRINT_ID: 'blueprintId', ENVIRONMENT_NAME: 'environmentName', + ENVIRONMENT_ID: 'environmentId', WORKSPACE_NAME: 'workspaceName', ENVIRONMENT_VARIABLES: 'environmentVariables', TERRAFORM_VARIABLES: 'terraformVariables', diff --git a/node/src/lib/config-manager.js b/node/src/lib/config-manager.js index 5fcf306..67f45ab 100644 --- a/node/src/lib/config-manager.js +++ b/node/src/lib/config-manager.js @@ -7,7 +7,7 @@ const logger = require('./logger'); const CONFIG_FILE = path.join(os.homedir(), '.env0', 'config.json'); -const { API_KEY, API_SECRET, ORGANIZATION_ID, PROJECT_ID, BLUEPRINT_ID, ENVIRONMENT_NAME } = options; +const { API_KEY, API_SECRET, ORGANIZATION_ID, PROJECT_ID, BLUEPRINT_ID, ENVIRONMENT_NAME, ENVIRONMENT_ID } = options; const INCLUDED_OPTIONS = [API_KEY, API_SECRET, ORGANIZATION_ID, PROJECT_ID, BLUEPRINT_ID, ENVIRONMENT_NAME]; @@ -17,7 +17,8 @@ const envVarToOptionMapper = { ENV0_ORGANIZATION_ID: ORGANIZATION_ID, ENV0_PROJECT_ID: PROJECT_ID, ENV0_BLUEPRINT_ID: BLUEPRINT_ID, - ENV0_ENVIRONMENT_NAME: ENVIRONMENT_NAME + ENV0_ENVIRONMENT_NAME: ENVIRONMENT_NAME, + ENV0_ENVIRONMENT_ID: ENVIRONMENT_ID }; const getEnvVars = () => { diff --git a/node/src/lib/deploy-utils.js b/node/src/lib/deploy-utils.js index ce544a4..bce5f99 100644 --- a/node/src/lib/deploy-utils.js +++ b/node/src/lib/deploy-utils.js @@ -2,7 +2,7 @@ const Env0ApiClient = require('./api-client'); const logger = require('./logger'); const { options } = require('../config/constants'); const { convertStringToBoolean, removeEmptyValuesFromObj, withRetry } = require('./general-utils'); -const { isEmpty } = require('lodash'); +const _ = require('lodash'); const { API_KEY, @@ -23,10 +23,13 @@ class DeployUtils { await apiClient.init(options[API_KEY], options[API_SECRET]); } - async getEnvironment(environmentName, projectId) { - const environments = await apiClient.callApi('get', `environments?projectId=${projectId}&name=${environmentName}`); + async getEnvironment(options) { + const { environmentName, environmentId, projectId } = options; + const response = environmentId + ? await apiClient.callApi('get', `environments/${environmentId}`) + : await apiClient.callApi('get', `environments?projectId=${projectId}&name=${environmentName}`); - return isEmpty(environments) ? undefined : environments[0]; + return _(response).castArray().first(); } async getDeployment(deploymentLogId) { @@ -34,15 +37,13 @@ class DeployUtils { } async getDeploymentSteps(deploymentLogId) { - return await apiClient.callApi('get', `deployments/${deploymentLogId}/steps`) + return await apiClient.callApi('get', `deployments/${deploymentLogId}/steps`); } async getDeploymentStepLog(deploymentLogId, stepName, startTime) { - return await apiClient.callApi( - 'get', - `deployments/${deploymentLogId}/steps/${stepName}/log`, - { params: { startTime } } - ) + return await apiClient.callApi('get', `deployments/${deploymentLogId}/steps/${stepName}/log`, { + params: { startTime } + }); } async updateEnvironment(environment, data) { @@ -106,8 +107,8 @@ class DeployUtils { const { status } = steps.find(step => step.name === stepName); const stepInProgress = status === 'IN_PROGRESS'; - const { events, nextStartTime, hasMoreLogs } = await withRetry( - () => this.getDeploymentStepLog(deploymentLogId, stepName, startTime) + const { events, nextStartTime, hasMoreLogs } = await withRetry(() => + this.getDeploymentStepLog(deploymentLogId, stepName, startTime) ); events.forEach(event => logger.info(event.message)); diff --git a/node/src/lib/set-deployment-approval-status.js b/node/src/lib/set-deployment-approval-status.js index 468d3b4..8396c33 100644 --- a/node/src/lib/set-deployment-approval-status.js +++ b/node/src/lib/set-deployment-approval-status.js @@ -4,7 +4,7 @@ const logger = require('../lib/logger'); const setDeploymentApprovalStatus = (command, shouldProcessDeploymentSteps) => async options => { const deployUtils = new DeployUtils(); - const environment = await deployUtils.getEnvironment(options.environmentName, options.projectId); + const environment = await deployUtils.getEnvironment(options); if (!environment) { throw new Error(`Could not find an environment with the name ${options.environmentName}`); diff --git a/node/tests/commands/deploy.spec.js b/node/tests/commands/deploy.spec.js index c63768e..f123825 100644 --- a/node/tests/commands/deploy.spec.js +++ b/node/tests/commands/deploy.spec.js @@ -47,7 +47,7 @@ describe('deploy', () => { it('should get environment', async () => { await deploy(mockOptionsWithRequired); - expect(mockGetEnvironment).toBeCalledWith(mockOptions[ENVIRONMENT_NAME], mockOptions[PROJECT_ID]); + expect(mockGetEnvironment).toBeCalledWith(mockOptionsWithRequired); }); it("should create environment when it doesn't exist", async () => { @@ -115,12 +115,16 @@ describe('deploy', () => { let existingEnvironmentWithWorkspace = { ...mockEnvironment, [WORKSPACE_NAME]: 'workspace0' }; mockGetEnvironment.mockResolvedValue(existingEnvironmentWithWorkspace); - await deploy(mockOptionsWithRequired, variables) + await deploy(mockOptionsWithRequired, variables); - const expectedOptions = { ...mockOptionsWithRequired } - delete expectedOptions[WORKSPACE_NAME] + const expectedOptions = { ...mockOptionsWithRequired }; + delete expectedOptions[WORKSPACE_NAME]; - expect(mockDeployEnvironment).toBeCalledWith(existingEnvironmentWithWorkspace, expectedOptions, expectedConfigurationChanges); + expect(mockDeployEnvironment).toBeCalledWith( + existingEnvironmentWithWorkspace, + expectedOptions, + expectedConfigurationChanges + ); }); }); diff --git a/node/tests/lib/deploy-utils.spec.js b/node/tests/lib/deploy-utils.spec.js index 7b9f42a..c0af30f 100644 --- a/node/tests/lib/deploy-utils.spec.js +++ b/node/tests/lib/deploy-utils.spec.js @@ -173,8 +173,8 @@ describe('deploy utils', () => { await deployUtils.writeDeploymentStepLog(mockDeploymentId, mockStep.name); - expect(mockCallApi).toHaveBeenNthCalledWith(1, 'get', `deployments/${mockDeploymentId}/steps`) - expect(mockCallApi).toHaveBeenNthCalledWith(2, 'get', `deployments/${mockDeploymentId}/steps`) + expect(mockCallApi).toHaveBeenNthCalledWith(1, 'get', `deployments/${mockDeploymentId}/steps`); + expect(mockCallApi).toHaveBeenNthCalledWith(2, 'get', `deployments/${mockDeploymentId}/steps`); }); it('should retry when polling on deployment step log fails', async () => { @@ -187,16 +187,24 @@ describe('deploy utils', () => { await deployUtils.writeDeploymentStepLog(mockDeploymentId, mockStep.name); - expect(mockCallApi).toHaveBeenNthCalledWith(2, 'get', `deployments/${mockDeploymentId}/steps/${mockStep.name}/log`, { - params: { startTime: undefined } - }) - expect(mockCallApi).toHaveBeenNthCalledWith(3, 'get', `deployments/${mockDeploymentId}/steps/${mockStep.name}/log`, { - params: { startTime: undefined } - }) + expect(mockCallApi).toHaveBeenNthCalledWith( + 2, + 'get', + `deployments/${mockDeploymentId}/steps/${mockStep.name}/log`, + { + params: { startTime: undefined } + } + ); + expect(mockCallApi).toHaveBeenNthCalledWith( + 3, + 'get', + `deployments/${mockDeploymentId}/steps/${mockStep.name}/log`, + { + params: { startTime: undefined } + } + ); }); - }) - - + }); }); describe('create and deploy environment', () => { @@ -284,26 +292,34 @@ describe('deploy utils', () => { describe('get environments', () => { const environmentName = 'env0'; + const environmentId = 'environmentId'; const projectId = 'projectX'; + const environments = ['id1', 'id2', 'id3'].map(id => ({ id })); let response; describe.each` - when | apiResponse | expectedReturnValue - ${''} | ${environments} | ${environments[0]} - ${'NOT'} | ${[]} | ${undefined} - `('when environment was $when found', ({ apiResponse, expectedReturnValue }) => { - beforeEach(async () => { - mockCallApi.mockReturnValue(apiResponse); - response = await deployUtils.getEnvironment(environmentName, projectId); - }); + when | options | expectedApiPath + ${''} | ${{ environmentId, environmentName, projectId }} | ${`environments/${environmentId}`} + ${'NOT'} | ${{ environmentName, projectId }} | ${`environments?projectId=${projectId}&name=${environmentName}`} + `('when environment id is $when specified', ({ options, expectedApiPath }) => { + describe.each` + when | apiResponse | expectedReturnValue + ${''} | ${environments} | ${environments[0]} + ${'NOT'} | ${[]} | ${undefined} + `('when environment was $when found', ({ apiResponse, expectedReturnValue }) => { + beforeEach(async () => { + mockCallApi.mockReturnValue(apiResponse); + response = await deployUtils.getEnvironment(options); + }); - it('should call api', async () => { - expect(mockCallApi).toBeCalledWith('get', `environments?projectId=${projectId}&name=${environmentName}`); - }); + it('should call api', async () => { + expect(mockCallApi).toBeCalledWith('get', expectedApiPath); + }); - it(`should return ${expectedReturnValue}`, () => { - expect(response).toEqual(expectedReturnValue); + it(`should return ${expectedReturnValue}`, () => { + expect(response).toEqual(expectedReturnValue); + }); }); }); });