Skip to content

Commit

Permalink
feat: hosted skill-upgrade project
Browse files Browse the repository at this point in the history
  • Loading branch information
Chih-Ying committed Mar 23, 2020
1 parent 394fe82 commit 72b9ac7
Show file tree
Hide file tree
Showing 15 changed files with 1,479 additions and 504 deletions.
6 changes: 3 additions & 3 deletions docs/Upgrade-Project-From-V1.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Use this guide to upgrade CLI 1.x projects for deployment with the CLI Beta (askx)**
Skills created with CLI 1.x will need to update their project structure in order to deploy with the CLI Beta (askx). To help projects upgrade to the new format, we have provided an `askx util upgrade-to-v2` command that will attempt to perform the necessary changes.
Skills created with CLI 1.x will need to update their project structure in order to deploy with the CLI Beta (askx). To help projects upgrade to the new format, we have provided an `askx util upgrade-project` command that will attempt to perform the necessary changes.

## Upgrade steps

Expand All @@ -11,7 +11,7 @@ Skills created with CLI 1.x will need to update their project structure in order
2. Install ask-cli-x:
`$ npm install -g ask-cli-x`
3. Upgrade your skill project with ask-cli-x. From your project's root, run:
`$ askx util upgrade-to-v2`
`$ askx util upgrade-project`
* The command will make the following changes to the project structure:

| | v1 project | beta (askx) project |
Expand All @@ -30,7 +30,7 @@ Skills created with CLI 1.x will need to update their project structure in order

**NOTE:**
* No changes will be made to existing AWS resources. We will utilize existing Lambda ARN(s) from the Lambda resources list in v1.
* The upgrade-to-v2 command assumes v1 project structure, and focusing on managing existing resources in Alexa or AWS. Should this fail, we will present developers with error, link to GitHub issues to ask for support.
* The upgrade-project command assumes v1 project structure, and focusing on managing existing resources in Alexa or AWS. Should this fail, we will present developers with error, link to GitHub issues to ask for support.
* The ./hooks folder is no longer needed, as ask-cli v2 can infer code build flow.

If you encounter any problems during this upgrade process, please create a [issues](https://github.com/alexa-labs/ask-cli/issues) to us.
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
const R = require('ramda');
const path = require('path');
const fs = require('fs-extra');
const path = require('path');
const R = require('ramda');


const SkillMetadataController = require('@src/controllers/skill-metadata-controller');
const awsUtil = require('@src/clients/aws-client/aws-util');
const CliError = require('@src/exceptions/cli-error');
const ResourcesConfig = require('@src/model/resources-config');
const Messenger = require('@src/view/messenger');
const urlUtils = require('@src/utils/url-utils');
const awsUtil = require('@src/clients/aws-client/aws-util');
const hashUtils = require('@src/utils/hash-utils');
const stringUtils = require('@src/utils/string-utils');
const CONSTANTS = require('@src/utils/constants');
const stringUtils = require('@src/utils/string-utils');
const hashUtils = require('@src/utils/hash-utils');
const urlUtils = require('@src/utils/url-utils');

const ui = require('./ui');

Expand All @@ -23,34 +25,37 @@ module.exports = {
};

/**
*
* @param {String} v1RootPath
* @param {String} profile
* @returns upgradeInfo { skillId, lambdaResources }
* To extract upgrade information from v1 project
* @param {String} v1RootPath the v1 file path
* @param {String} profile the profile
* @returns upgradeInfo { skillId, isHosted, lambdaResources }
* upgradeInfo.lambdaResources { $alexaRegion: { arn, codeUri, v2CodeUri, runtime, handler, revisionId } }
* @throws validationError
*/
function extractUpgradeInformation(v1RootPath, profile) {
// 1.check v1 .ask/config exists
const hiddenConfigPath = path.join(v1RootPath, '.ask', 'config');
if (!fs.existsSync(hiddenConfigPath)) {
throw 'Failed to find ask-cli v1 project. Please make sure this command is called at the root of the skill project.';
throw new CliError('Failed to find ask-cli v1 project. Please make sure this command is called at the root of the skill project.');
}
const hiddenConfig = fs.readJsonSync(hiddenConfigPath, 'utf-8');
const v1ProjData = {
skillId: R.view(R.lensPath(['deploy_settings', profile, 'skill_id']), hiddenConfig),
isHosted: R.view(R.lensPath(['deploy_settings', profile, 'alexaHosted', 'isAlexaHostedSkill']), hiddenConfig) || false,
isHosted: R.view(R.lensPath(['alexaHosted', 'isAlexaHostedSkill']), hiddenConfig) || false,
lambdaList: R.view(R.lensPath(['deploy_settings', profile, 'resources', 'lambda']), hiddenConfig) || []
};
// 2.check if skillId exists
if (!stringUtils.isNonBlankString(v1ProjData.skillId)) {
throw `Failed to find skill_id for profile [${profile}]. If the skill has never been deployed in v1 ask-cli, please start from v2 structure.`;
throw new CliError(`Failed to find skill_id for profile [${profile}]. \
If the skill has never been deployed in v1 ask-cli, please start from v2 structure.`);
}
// 3.exclude hosted-skill case for now
if (v1ProjData.isHosted) {
throw 'Alexa Hosted Skill is currently not supported to upgrade.';
return {
skillId: v1ProjData.skillId,
isHosted: v1ProjData.isHosted
};
}
// 4.resolve Lambda codebase for each region
// 3.resolve Lambda codebase for each region
const lambdaMapByRegion = {};
for (const lambdaResource of v1ProjData.lambdaList) {
_collectLambdaMapFromResource(lambdaMapByRegion, lambdaResource);
Expand Down Expand Up @@ -78,7 +83,7 @@ You have multiple Lambda codebases for region ${region}, we will use "${lambdaMa
}
} else {
// set Lambda info for each alexaRegion and only re-use the Lambda ARN for the first alexaRegion (let the rest create their own Lambda)
const v2CodeUri = `.${path.sep}${CONSTANTS.FILE_PATH.SKILL_CODE.CODE}${path.sep}${stringUtils.filterNonAlphanumeric(codeUri)}`;
const v2CodeUri = `.${path.sep}${CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA}${path.sep}${stringUtils.filterNonAlphanumeric(codeUri)}`;
lambdaMapByRegion[region] = {
arn: index === 0 ? arn : undefined,
codeUri,
Expand All @@ -94,27 +99,36 @@ You have multiple Lambda codebases for region ${region}, we will use "${lambdaMa
function _validateLambdaResource(lambdaResource) {
const { alexaUsage, codeUri, runtime, handler } = lambdaResource;
if (!alexaUsage || alexaUsage.length === 0) {
throw 'Please make sure your alexaUsage is not empty.';
throw new CliError('Please make sure your alexaUsage is not empty.');
}
if (!stringUtils.isNonBlankString(codeUri)) {
throw 'Please make sure your codeUri is set to the path of your Lambda code.';
throw new CliError('Please make sure your codeUri is set to the path of your Lambda code.');
}
if (!stringUtils.isNonBlankString(runtime)) {
throw `Please make sure your runtime for codeUri ${codeUri} is set.`;
throw new CliError(`Please make sure your runtime for codeUri ${codeUri} is set.`);
}
if (!stringUtils.isNonBlankString(handler)) {
throw `Please make sure your handler for codeUri ${codeUri} is set.`;
throw new CliError(`Please make sure your handler for codeUri ${codeUri} is set.`);
}
return lambdaResource;
}

/**
* To confirm users with the upgrade changes
* @param {Object} upgradeInfo the upgrade info { skillId, isHosted,lambdaResources }
* @param {callback} callback { err, previewConfirm }
*/
function previewUpgrade(upgradeInfo, callback) {
ui.displayPreview(upgradeInfo);
ui.confirmPreview((confirmErr, previewConfirm) => {
callback(confirmErr, confirmErr ? null : previewConfirm);
});
}

/**
* To move v1 project to legacy folder
* @param {string} v1RootPath the v1 root path
*/
function moveOldProjectToLegacyFolder(v1RootPath) {
const oldFiles = fs.readdirSync(v1RootPath);
const legacyPath = path.join(v1RootPath, CONSTANTS.FILE_PATH.LEGACY_PATH);
Expand All @@ -125,12 +139,18 @@ function moveOldProjectToLegacyFolder(v1RootPath) {
});
}

/**
* To create v2 project structure
* @param {String} rootPath the root path
* @param {String} skillId the skill id
* @param {String} profile the profile
*/
function createV2ProjectSkeleton(rootPath, skillId, profile) {
// prepare skill packaage folder
// prepare skill package folder
const skillPackagePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE);
fs.ensureDirSync(skillPackagePath);
// preprare skill code folder
const skillCodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.CODE);
// prepare skill code folder
const skillCodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA);
fs.ensureDirSync(skillCodePath);
// prepare ask-resources config
const askResourcesJson = R.clone(ResourcesConfig.BASE);
Expand All @@ -143,6 +163,15 @@ function createV2ProjectSkeleton(rootPath, skillId, profile) {
fs.writeJSONSync(askResourcesFilePath, askResourcesJson, { spaces: CONSTANTS.CONFIGURATION.JSON_DISPLAY_INDENT });
}

/**
* To download skill project
* @param {String} rootPath the root path
* @param {String} skillId the skill id
* @param {String} skillStage the skill stage
* @param {String} profile the profile
* @param {Boolean} doDebug the debug flag
* @param {callback} callback { err }
*/
function downloadSkillPackage(rootPath, skillId, skillStage, profile, doDebug, callback) {
const skillMetaController = new SkillMetadataController({ profile, doDebug });
skillMetaController.getSkillPackage(rootPath, skillId, skillStage, (packageErr) => {
Expand All @@ -161,9 +190,16 @@ function downloadSkillPackage(rootPath, skillId, skillStage, profile, doDebug, c
});
}

/**
* To handle existing lambda code and update ask-resources.js
* @param {String} rootPath the root path
* @param {Object} lambdaResourcesMap the lambda code resources from old project
* lambdaResourcesMap { $alexaRegion: { arn, codeUri, handler, revisionId, runtime, v2CodeUri} }
* @param {String} profile the profile
*/
function handleExistingLambdaCode(rootPath, lambdaResourcesMap, profile) {
// 1.update skill infra type
ResourcesConfig.getInstance().setSkillInfraType(profile, '@ask-cli/lambda-deployer');
ResourcesConfig.getInstance().setSkillInfraType(profile, CONSTANTS.DEPLOYER_TYPE.LAMBDA.NAME);
// 2.set userConfig from default region Lambda configuration
// default will always exist as it's required in a set of valid Lambda resources from the v1 project
let defaultRuntime, defaultHandler;
Expand Down
72 changes: 72 additions & 0 deletions lib/commands/util/upgrade-project/hosted-skill-helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const fs = require('fs-extra');
const R = require('ramda');
const path = require('path');

const SkillMetadataController = require('@src/controllers/skill-metadata-controller');
const ResourcesConfig = require('@src/model/resources-config');
const CONSTANTS = require('@src/utils/constants');

module.exports = {
createV2ProjectSkeleton,
downloadSkillPackage,
handleExistingLambdaCode
};

/**
* To create v2 project structure for an Alexa hosted skill project
* @param {String} rootPath the root path
* @param {String} skillId the skill id
* @param {String} profile the profile
*/
function createV2ProjectSkeleton(rootPath, skillId, profile) {
// prepare skill package folder
const skillPackagePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE);
fs.ensureDirSync(skillPackagePath);
// prepare skill code folder
const skillCodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA);
fs.ensureDirSync(skillCodePath);
// prepare ask-resources config
const askResourcesJson = R.clone(ResourcesConfig.BASE);
askResourcesJson.profiles[profile] = {
skillId
};
const askResourcesFilePath = path.join(rootPath, CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG);
fs.writeJSONSync(askResourcesFilePath, askResourcesJson, { spaces: CONSTANTS.CONFIGURATION.JSON_DISPLAY_INDENT });
}

/**
* To download skill project for an Alexa hosted skill project
* @param {String} rootPath the root path
* @param {String} skillId the skill id
* @param {String} skillStage the skill stage
* @param {String} profile the profile
* @param {Boolean} doDebug the debug flag
* @param {callback} callback { err }
*/
function downloadSkillPackage(rootPath, skillId, skillStage, profile, doDebug, callback) {
const skillMetaController = new SkillMetadataController({ profile, doDebug });
skillMetaController.getSkillPackage(rootPath, skillId, skillStage, (packageErr) => {
if (packageErr) {
return callback(packageErr);
}
callback();
});
}

/**
* To handle existing lambda code and update ask-resources.js for an Alexa hosted skill project
* @param {String} rootPath the root path
* @param {Object} lambdaResourcesMap the lambda code resources from old project { profile: { arn, codeUri, handler, revisionId, runtime, v2CodeUri} }
* @param {String} profile the profile
*/
function handleExistingLambdaCode(rootPath, profile) {
// 1.update skill infra type
ResourcesConfig.getInstance().setSkillInfraType(profile, CONSTANTS.DEPLOYER_TYPE.HOSTED.NAME);
// 2.copy Lambda code from legacy folder and set deployState for each region
const legacyFolderPath = path.join(rootPath, CONSTANTS.FILE_PATH.LEGACY_PATH);
// 3.1 copy code from v1 project to v2
const v1CodePath = path.join(legacyFolderPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA);
const v2CodePath = path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_CODE.LAMBDA);
fs.copySync(v1CodePath, v2CodePath);
ResourcesConfig.getInstance().write();
}
Loading

0 comments on commit 72b9ac7

Please sign in to comment.