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

Feat: Environment ID argument #131

Merged
merged 6 commits into from
May 15, 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
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 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 @@
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 @@
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 Expand Up @@ -153,7 +154,7 @@
logger.info('Note: You can always stop waiting by using Ctrl+C');
}

while (true) {

Check warning on line 157 in node/src/lib/deploy-utils.js

View workflow job for this annotation

GitHub Actions / ci (node)

Unexpected constant condition
const { type, status } = await withRetry(() => this.getDeployment(deployment.id));

if (status === 'QUEUED') logger.info('Queued deployment is still waiting for earlier deployments to finish...');
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
Loading