Skip to content

Commit

Permalink
fix: limit skill infrastructure deployment to one task per aws region (
Browse files Browse the repository at this point in the history
  • Loading branch information
jsetton authored Aug 10, 2022
1 parent 1c42ba1 commit 6143b02
Show file tree
Hide file tree
Showing 8 changed files with 536 additions and 261 deletions.
63 changes: 41 additions & 22 deletions lib/builtins/deploy-delegates/cfn-deployer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,13 @@ const fs = require('fs');
const R = require('ramda');

const awsUtil = require('@src/clients/aws-client/aws-util');
const stringUtils = require('@src/utils/string-utils');
const CliCFNDeployerError = require('@src/exceptions/cli-cfn-deployer-error');
const Helper = require('./helper');

const SKILL_STACK_PUBLIC_FILE_NAME = 'skill-stack.yaml';
const SKILL_STACK_ASSET_FILE_NAME = 'basic-lambda.yaml';

const alexaAwsRegionMap = {
default: 'us-east-1',
NA: 'us-east-1',
EU: 'eu-west-1',
FE: 'us-west-2'
};

module.exports = {
bootstrap,
invoke
Expand Down Expand Up @@ -58,16 +52,17 @@ function bootstrap(options, callback) {
*/
async function invoke(reporter, options, callback) {
const { alexaRegion, deployState = {} } = options;
deployState[alexaRegion] = deployState[alexaRegion] || {};
const deployProgress = {
isAllStepSuccess: false,
isCodeDeployed: false,
deployState: deployState[alexaRegion]
deployState: deployState[alexaRegion] || {}
};

try {
await _deploy(reporter, options, deployProgress);
deployProgress.resultMessage = _makeSuccessMessage(deployProgress.endpoint.uri, alexaRegion);
deployProgress.resultMessage = deployProgress.isDeploySkipped
? _makeSkippedMessage(deployProgress.deployRegion, alexaRegion)
: _makeSuccessMessage(deployProgress.endpoint.uri, alexaRegion);
callback(null, deployProgress);
} catch (err) {
deployProgress.resultMessage = _makeErrorMessage(err, alexaRegion);
Expand All @@ -76,16 +71,23 @@ async function invoke(reporter, options, callback) {
}

async function _deploy(reporter, options, deployProgress) {
const { profile, doDebug, alexaRegion, skillId, skillName, code, userConfig } = options;
const { profile, doDebug, alexaRegion, skillId, skillName, code, userConfig, deployState = {}, deployRegions } = options;

let { stackId } = deployProgress.deployState;
const awsProfile = _getAwsProfile(profile);
const awsRegion = _getAwsRegion(alexaRegion, userConfig);
const awsRegion = _getAwsRegion(alexaRegion, deployRegions);
const templateBody = _getTemplateBody(alexaRegion, userConfig);
const userDefinedParameters = _getUserDefinedParameters(alexaRegion, userConfig);
const bucketName = _getS3BucketName(alexaRegion, userConfig, deployProgress.deployState, awsProfile, awsRegion);
const bucketKey = _getS3BucketKey(alexaRegion, userConfig, code.codeBuild);
const stackName = `ask-${skillName}-${alexaRegion}-skillStack-${Date.now()}`;
const stackName = _getStackName(skillName, alexaRegion);
const deployRegion = R.keys(deployRegions).find((region) => deployRegions[region] === awsRegion);

if (deployRegion !== alexaRegion && R.equals(deployState[deployRegion], deployState[alexaRegion])) {
deployProgress.isDeploySkipped = true;
deployProgress.deployRegion = deployRegion;
return;
}

const helper = new Helper(profile, doDebug, awsProfile, awsRegion, reporter);

Expand Down Expand Up @@ -132,11 +134,8 @@ function _getAwsProfile(profile) {
return awsProfile;
}

function _getAwsRegion(alexaRegion, userConfig) {
let awsRegion = alexaRegion === 'default' ? userConfig.awsRegion
: R.path(['regionalOverrides', alexaRegion, 'awsRegion'], userConfig);
awsRegion = awsRegion || alexaAwsRegionMap[alexaRegion];

function _getAwsRegion(alexaRegion, deployRegions) {
const awsRegion = deployRegions[alexaRegion];
if (!awsRegion) {
throw new CliCFNDeployerError(`Unsupported Alexa region: ${alexaRegion}. `
+ 'Please check your region name or use "regionalOverrides" to specify AWS region.');
Expand All @@ -150,13 +149,16 @@ function _getS3BucketName(alexaRegion, userConfig, currentRegionDeployState, aws

if (customValue) return customValue;

function generateBucketName() {
// Generates a valid S3 bucket name.
// a bucket name should follow the pattern: ask-projectName-profileName-awsRegion-timeStamp
// a valid bucket name cannot longer than 63 characters, so cli fixes the project name no longer than 22 characters
const generateBucketName = () => {
const projectName = path.basename(process.cwd());
const validProjectName = projectName.toLowerCase().replace(/[^a-z0-9-.]+/g, '').substring(0, 22);
const validProfile = awsProfile.toLowerCase().replace(/[^a-z0-9-.]+/g, '').substring(0, 9);
const validProjectName = stringUtils.filterNonAlphanumeric(projectName.toLowerCase()).substring(0, 22);
const validProfile = stringUtils.filterNonAlphanumeric(awsProfile.toLowerCase()).substring(0, 9);
const shortRegionName = awsRegion.replace(/-/g, '');
return `ask-${validProjectName}-${validProfile}-${shortRegionName}-${Date.now()}`;
}
};

return R.path(['s3', 'bucket'], currentRegionDeployState) || generateBucketName();
}
Expand All @@ -170,6 +172,19 @@ function _getS3BucketKey(alexaRegion, userConfig, codeBuild) {
return `endpoint/${path.basename(codeBuild)}`;
}

function _getStackName(skillName, alexaRegion) {
// Generates a valid CloudFormation stack name.
// a stack name should follow the pattern: ask-skillName-alexaRegion-skillStack-timeStamp
// a valid stack name cannot longer than 128 characters, so cli fixes the skill name no longer than 64 characters
const generateStackName = () => {
const validSkillName = stringUtils.filterNonAlphanumeric(skillName).substring(0, 64);
const shortRegionName = alexaRegion.replace(/-/g, '');
return `ask-${validSkillName}-${shortRegionName}-skillStack-${Date.now()}`;
};

return generateStackName();
}

function _getCapabilities(alexaRegion, userConfig) {
let capabilities = R.path(['regionalOverrides', alexaRegion, 'cfn', 'capabilities'], userConfig)
|| R.path(['cfn', 'capabilities'], userConfig);
Expand Down Expand Up @@ -216,6 +231,10 @@ function _getTemplateBody(alexaRegion, userConfig) {
return fs.readFileSync(templatePath, 'utf-8');
}

function _makeSkippedMessage(deployRegion, alexaRegion) {
return `The CloudFormation deploy for Alexa region "${alexaRegion}" is same as "${deployRegion}".`;
}

function _makeSuccessMessage(endpointUri, alexaRegion) {
return `The CloudFormation deploy succeeded for Alexa region "${alexaRegion}" with output Lambda ARN: ${endpointUri}.`;
}
Expand Down
51 changes: 28 additions & 23 deletions lib/builtins/deploy-delegates/lambda-deployer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ const awsUtil = require('@src/clients/aws-client/aws-util');
const stringUtils = require('@src/utils/string-utils');
const helper = require('./helper');

const alexaAwsRegionMap = {
default: 'us-east-1',
NA: 'us-east-1',
EU: 'eu-west-1',
FE: 'us-west-2'
};

module.exports = {
bootstrap,
invoke
Expand All @@ -36,28 +29,35 @@ function bootstrap(options, callback) {
* @param {Function} callback
*/
function invoke(reporter, options, callback) {
const { profile, ignoreHash, alexaRegion, skillId, skillName, code, userConfig, deployState = {} } = options;
const { profile, ignoreHash, alexaRegion, skillId, skillName, code, userConfig, deployState = {}, deployRegions } = options;
const currentRegionDeployState = deployState[alexaRegion] || {};
const awsProfile = awsUtil.getAWSProfile(profile);
if (!stringUtils.isNonBlankString(awsProfile)) {
return callback(`Profile [${profile}] doesn't have AWS profile linked to it. Please run "ask configure" to re-configure your porfile.`);
}
let currentRegionDeployState = deployState[alexaRegion];
if (!currentRegionDeployState) {
currentRegionDeployState = {};
deployState[alexaRegion] = currentRegionDeployState;
}
// parse AWS region to use
let awsRegion = alexaRegion === 'default' ? userConfig.awsRegion : R.path(['regionalOverrides', alexaRegion, 'awsRegion'], userConfig);
awsRegion = awsRegion || alexaAwsRegionMap[alexaRegion];
const awsRegion = deployRegions[alexaRegion];
if (!stringUtils.isNonBlankString(awsRegion)) {
return callback(`Unsupported Alexa region: ${alexaRegion}. Please check your region name or use "regionalOverrides" to specify AWS region.`);
}
const deployRegion = R.keys(deployRegions).find((region) => deployRegions[region] === awsRegion);
if (deployRegion !== alexaRegion && R.equals(deployState[deployRegion], deployState[alexaRegion])) {
return callback(null, {
isDeploySkipped: true,
deployRegion,
resultMessage: `The lambda deploy for Alexa region "${alexaRegion}" is same as "${deployRegion}"`
});
}

// load Lambda info from either existing deployState or userConfig's sourceLambda
const loadLambdaConfig = { awsProfile, awsRegion, alexaRegion, ignoreHash, deployState: currentRegionDeployState, userConfig };
helper.loadLambdaInformation(reporter, loadLambdaConfig, (loadLambdaErr, lambdaData) => {
if (loadLambdaErr) {
return callback(loadLambdaErr);
return callback(null, {
isAllStepSuccess: false,
isCodeDeployed: false,
deployState: currentRegionDeployState,
resultMessage: `The lambda deploy failed for Alexa region "${alexaRegion}": ${loadLambdaErr}`
});
}
currentRegionDeployState.lambda = lambdaData.lambda;
currentRegionDeployState.iamRole = lambdaData.iamRole;
Expand All @@ -71,9 +71,14 @@ function invoke(reporter, options, callback) {
};
helper.deployIAMRole(reporter, deployIAMConfig, (iamErr, iamRoleArn) => {
if (iamErr) {
return callback(iamErr);
return callback(null, {
isAllStepSuccess: false,
isCodeDeployed: false,
deployState: currentRegionDeployState,
resultMessage: `The lambda deploy failed for Alexa region "${alexaRegion}": ${iamErr}`
});
}
deployState[alexaRegion].iamRole = iamRoleArn;
currentRegionDeployState.iamRole = iamRoleArn;
// create/update deploy for Lambda
const deployLambdaConfig = {
profile,
Expand All @@ -93,19 +98,19 @@ function invoke(reporter, options, callback) {
return callback(null, {
isAllStepSuccess: false,
isCodeDeployed: false,
deployState: deployState[alexaRegion],
deployState: currentRegionDeployState,
resultMessage: `The lambda deploy failed for Alexa region "${alexaRegion}": ${lambdaErr}`
});
}
const { isAllStepSuccess, isCodeDeployed, lambdaResponse = {} } = lambdaResult;
deployState[alexaRegion].lambda = lambdaResponse;
currentRegionDeployState.lambda = lambdaResponse;
const { arn } = lambdaResponse;
// 2.full successs in Lambda deploy
if (isAllStepSuccess) {
return callback(null, {
isAllStepSuccess,
isCodeDeployed,
deployState: deployState[alexaRegion],
deployState: currentRegionDeployState,
endpoint: { uri: arn },
resultMessage: `The lambda deploy succeeded for Alexa region "${alexaRegion}" with output Lambda ARN: ${arn}.`
});
Expand All @@ -114,7 +119,7 @@ function invoke(reporter, options, callback) {
return callback(null, {
isAllStepSuccess,
isCodeDeployed,
deployState: deployState[alexaRegion],
deployState: currentRegionDeployState,
resultMessage: `The lambda deploy failed for Alexa region "${alexaRegion}": ${lambdaResult.resultMessage}`
});
});
Expand Down
Loading

0 comments on commit 6143b02

Please sign in to comment.