Skip to content

Commit

Permalink
Feat: Environment ID argument (#131)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
yaronya authored May 15, 2024
1 parent 63277a5 commit c5a4436
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 50 deletions.
10 changes: 8 additions & 2 deletions node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions node/src/commands/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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!');
Expand Down
4 changes: 2 additions & 2 deletions node/src/commands/destroy.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions node/src/config/arguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
BLUEPRINT_ID,
WORKSPACE_NAME,
ENVIRONMENT_NAME,
ENVIRONMENT_ID,
ENVIRONMENT_VARIABLES,
TERRAFORM_VARIABLES,
SENSITIVE_ENVIRONMENT_VARIABLES,
Expand Down Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions node/src/config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
5 changes: 3 additions & 2 deletions node/src/lib/config-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];

Expand All @@ -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 = () => {
Expand Down
25 changes: 13 additions & 12 deletions node/src/lib/deploy-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,26 +23,27 @@ 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) {
return await apiClient.callApi('get', `environments/deployments/${deploymentLogId}`);
}

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) {
Expand Down Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion node/src/lib/set-deployment-approval-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
14 changes: 9 additions & 5 deletions node/tests/commands/deploy.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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
);
});
});

Expand Down
64 changes: 40 additions & 24 deletions node/tests/lib/deploy-utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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', () => {
Expand Down Expand Up @@ -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);
});
});
});
});
Expand Down

0 comments on commit c5a4436

Please sign in to comment.