in addons/addon-base-raas/packages/base-raas-workflow-steps/lib/steps/provision-environment/provision-environment.js [31:193]
async start() {
// start workflow that starts the CFN Template, updates status to PROCESSING, waits for CFN to finish, updates env status to COMPLETED/FAILED
// Get services
const [
userService,
environmentService,
cfnTemplateService,
environmentKeypairService,
environmentMountService,
] = await this.mustFindServices([
'userService',
'environmentService',
'cfnTemplateService',
'environmentKeypairService',
'environmentMountService',
]);
// Get common payload params and pull environment info
const [type, environmentId, requestContext, vpcId, vpcSubnet, encryptionKeyArn] = await Promise.all([
this.payload.string('type'),
this.payload.string('environmentId'),
this.payload.object('requestContext'),
this.payload.string('vpcId'),
this.payload.string('subnetId'),
this.payload.string('encryptionKeyArn'),
]);
const environment = await environmentService.mustFind(requestContext, { id: environmentId });
const by = _.get(requestContext, 'principalIdentifier.uid');
const user = await userService.mustFindUser({ uid: by });
// Stack naming combines datetime & randomString to avoid collisions when two workspaces are created at the same time
const stackName = `analysis-${new Date().getTime()}-${randomString(10)}`;
// Set initial state
this.state.setKey('STATE_ENVIRONMENT_ID', environmentId);
this.state.setKey('STATE_REQUEST_CONTEXT', requestContext);
// Define array for collecting CloudFormation functions
const cfnParams = [];
const addParam = (key, value) => cfnParams.push({ ParameterKey: key, ParameterValue: value });
// Add parameters unique to each environment type
let template;
switch (type) {
case 'ec2-rstudio':
template = await cfnTemplateService.getTemplate('ec2-rstudio-instance');
break;
case 'ec2-linux':
template = await cfnTemplateService.getTemplate('ec2-linux-instance');
break;
case 'ec2-windows':
template = await cfnTemplateService.getTemplate('ec2-windows-instance');
break;
case 'sagemaker':
template = await cfnTemplateService.getTemplate('sagemaker-notebook-instance');
break;
case 'emr': {
template = await cfnTemplateService.getTemplate('emr-cluster');
addParam('DiskSizeGB', environment.instanceInfo.config.diskSizeGb.toString());
addParam('MasterInstanceType', environment.instanceInfo.size);
addParam('WorkerInstanceType', environment.instanceInfo.config.workerInstanceSize);
addParam('CoreNodeCount', environment.instanceInfo.config.workerInstanceCount.toString());
// Add parameters to support spot instance pricing if specified
// TODO this needs to be parameterized
const isOnDemand = !environment.instanceInfo.config.spotBidPrice;
// The spot bid price can only have 3 decimal places maximum
const spotBidPrice = isOnDemand ? '0' : environment.instanceInfo.config.spotBidPrice.toFixed(3);
addParam('Market', isOnDemand ? 'ON_DEMAND' : 'SPOT');
addParam('WorkerBidPrice', spotBidPrice);
this.print(
isOnDemand
? 'Launching on demand core nodes'
: `Launching spot core nodes with a bid price of ${spotBidPrice}`,
);
break;
}
default:
throw new Error(`Unknown environment type requested: ${type}`);
}
// Handle CFN parameters that need to be excluded from certain environment types
if (type !== 'ec2-windows') {
const {
s3Mounts,
iamPolicyDocument,
environmentInstanceFiles,
s3Prefixes,
} = await environmentMountService.getCfnStudyAccessParameters(requestContext, environment);
addParam('S3Mounts', s3Mounts);
addParam('IamPolicyDocument', iamPolicyDocument);
addParam('EnvironmentInstanceFiles', environmentInstanceFiles);
// Only save the prefixes for the local resources, otherwise we add list and get access for
// potentially the whole study bucket
this.state.setKey('ENV_S3_STUDY_PREFIXES', s3Prefixes);
}
if (type !== 'sagemaker') {
const credential = await this.getCredentials();
const [amiImage, keyName] = await Promise.all([
this.payload.string('amiImage'),
environmentKeypairService.create(requestContext, environmentId, credential),
]);
addParam('AmiId', amiImage);
addParam('KeyName', keyName);
}
const cidr = await this.payload.string('cidr');
addParam('AccessFromCIDRBlock', cidr);
if (type !== 'emr') {
addParam('InstanceType', environment.instanceInfo.size);
}
// Add rest of parameters
addParam('Namespace', stackName);
addParam('VPC', vpcId);
addParam('Subnet', vpcSubnet);
addParam('EncryptionKeyArn', encryptionKeyArn);
const input = {
StackName: stackName,
Parameters: cfnParams,
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
TemplateBody: template,
Tags: [
{
Key: 'Description',
Value: `Created by ${user.username}`,
},
{
Key: 'Env',
Value: environmentId,
},
{
Key: 'Proj',
Value: environment.indexId,
},
{
Key: 'CreatedBy',
Value: user.username,
},
],
};
// Create stack
const cfn = await this.getCloudFormationService();
const response = await cfn.createStack(input).promise();
// Update workflow state and poll for stack creation completion
this.state.setKey('STATE_STACK_ID', response.StackId);
await this.updateEnvironment({ stackId: response.StackId });
return this.wait(fuzz(80))
.maxAttempts(120)
.until('checkCfnCompleted');
}