Skip to content

Commit

Permalink
feat: hosted skill deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
Chih-Ying committed Mar 23, 2020
1 parent 91a6cd9 commit b303741
Show file tree
Hide file tree
Showing 20 changed files with 708 additions and 76 deletions.
71 changes: 69 additions & 2 deletions lib/clients/git-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ module.exports = class GitClient {
* @param {string} credentialHelperPath the path of git credential helper
*/
configureCredentialHelper(credentialHelperPath) {
const commands = [`git config --local credential.helper ${QUOTES}${QUOTES}`,
`git config --local --add credential.helper ${QUOTES}!${credentialHelperPath}${QUOTES}`,
const commands = [
`git config --local --replace-all credential.helper ${QUOTES}!${credentialHelperPath}${QUOTES}`,
'git config --local credential.UseHttpPath true'];
const options = {
showStdOut: this.verbosityOptions.showOutput,
Expand Down Expand Up @@ -148,6 +148,73 @@ module.exports = class GitClient {
this._execCommands(commands, options);
}

/**
* Execute git delete to delete a local branch
* @param {string} file the file to add content from
*/
deleteBranch(branch) {
const commands = [`git branch -d ${branch}`];
const options = {
showStdOut: this.verbosityOptions.showOutput,
showStdErr: true,
showCmd: this.verbosityOptions.showCommand
};
this._execCommands(commands, options);
}

/**
* Execute git merge to join two development histories together
* @param {String} branch the development branch
*/
merge(branch) {
const commands = [`git merge ${branch}`];
const options = {
showStdOut: this.verbosityOptions.showOutput,
showStdErr: true,
showCmd: this.verbosityOptions.showCommand
};
this._execCommands(commands, options);
}

/**
* Execute git status to show the working tree status in short-format
* @param {callback} callback { error }
*/
shortStatus() {
const command = 'git status -s';
const options = {
showStdOut: this.verbosityOptions.showOutput,
showStdErr: true,
showCmd: this.verbosityOptions.showCommand
};
try {
return this._execChildProcessSync(command, options);
} catch (ex) {
throw new CLiError(`${ex}`);
}
}

/**
* Execute git rev-list to count the list of commit objects
* @param {String} commit1 the first commit
* @param {String} commit2 the second commit
* @param {callback} callback { error }
*/
countCommitDifference(commit1, commit2) {
const command = `git rev-list --count ${commit1}...${commit2}`;
const options = {
showStdOut: this.verbosityOptions.showOutput,
showStdErr: true,
showCmd: this.verbosityOptions.showCommand,
workingDir: this.projectPath
};
try {
return this._execChildProcessSync(command, options);
} catch (ex) {
throw new CLiError(`${ex}`);
}
}

_execCommands(commands, options) {
for (const command of commands) {
try {
Expand Down
45 changes: 33 additions & 12 deletions lib/commands/deploy/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const fs = require('fs-extra');
const path = require('path');

const { AbstractCommand } = require('@src/commands/abstract-command');
Expand All @@ -9,7 +10,9 @@ const optionModel = require('@src/commands/option-model');
const profileHelper = require('@src/utils/profile-helper');
const stringUtils = require('@src/utils/string-utils');
const CONSTANTS = require('@src/utils/constants');
const CliError = require('@src/exceptions/cli-error');
const CliFileNotFoundError = require('@src/exceptions/cli-file-not-found-error');
const CliWarn = require('@src/exceptions/cli-warn');
const helper = require('./helper');

class DeployCommand extends AbstractCommand {
Expand All @@ -33,24 +36,19 @@ class DeployCommand extends AbstractCommand {
let profile;
try {
profile = profileHelper.runtimeProfile(cmd.profile);
// initiate ResourcesConfig model
new ResourcesConfig(path.join(process.cwd(), CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG));
// initiate Manifest model
const skillPackageSrc = ResourcesConfig.getInstance().getSkillMetaSrc(profile);
if (!stringUtils.isNonBlankString(skillPackageSrc)) {
throw new Error('Skill package src is not found in ask-resources.json.');
}
const manifestPath = path.join(skillPackageSrc, CONSTANTS.FILE_PATH.SKILL_PACKAGE.MANIFEST);
new Manifest(manifestPath);
this._filterAlexaHostedSkill(profile);
this._initiateManifestModel(profile);
} catch (err) {
Messenger.getInstance().error(err);

if (err instanceof CliFileNotFoundError) {
if (err instanceof CliWarn) {
Messenger.getInstance().warn(err.message);
} else if (err instanceof CliFileNotFoundError) {
Messenger.getInstance().warn('Please make sure you are in the root directory of the project.');
} else {
Messenger.getInstance().error(err);
}
return cb(err);
}

deployResources(profile, cmd.debug, (deployErr) => {
// Write updates back to resources file
if (deployErr) {
Expand All @@ -72,6 +70,29 @@ class DeployCommand extends AbstractCommand {
});
});
}

_filterAlexaHostedSkill(profile) {
const deployerType = ResourcesConfig.getInstance().getSkillInfraType(profile);
const rootPath = process.cwd();
if (!fs.existsSync(path.join(rootPath, CONSTANTS.FILE_PATH.SKILL_PACKAGE.PACKAGE))) {
throw new CliError('The skill-package does not exist. This skill cannot be deployed.');
}
if (deployerType === CONSTANTS.DEPLOYER_TYPE.HOSTED.NAME) {
throw new CliWarn('Alexa hosted skills can be deployed by performing a git push.\n'
+ 'The master branch gets deployed to skill\'s development stage\n'
+ 'The prod branch gets deployed to skill\'s live stage\n'
+ 'Please run "git push" at the proper branch to deploy hosted skill to your targeted stage.');
}
}

_initiateManifestModel(profile) {
const skillPackageSrc = ResourcesConfig.getInstance().getSkillMetaSrc(profile);
if (!stringUtils.isNonBlankString(skillPackageSrc)) {
throw new CliError('Skill package src is not found in ask-resources.json.');
}
const manifestPath = path.join(skillPackageSrc, CONSTANTS.FILE_PATH.SKILL_PACKAGE.MANIFEST);
new Manifest(manifestPath);
}
}

/**
Expand Down
12 changes: 10 additions & 2 deletions lib/commands/init/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,16 @@ class InitCommand extends AbstractCommand {
Messenger.getInstance().error(cloneErr);
return cb(cloneErr);
}
Messenger.getInstance().info(`\n${skillName} successfully initialized.\n`);
cb();
const templateUrl = CONSTANTS.HOSTED_SKILL.GIT_HOOKS_TEMPLATES.PRE_PUSH.URL;
const filePath = path.join(folderName, '.git', 'hooks', 'pre-push');
hostedSkillController.downloadGitHooksTemplate(templateUrl, filePath, (hooksErr) => {
if (hooksErr) {
Messenger.getInstance().error(hooksErr);
return cb(hooksErr);
}
Messenger.getInstance().info(`\n${skillName} successfully initialized.\n`);
cb();
});
});
});
});
Expand Down
19 changes: 11 additions & 8 deletions lib/commands/util/git-credentials-helper/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,43 @@ class GitCredentialsHelperCommand extends AbstractCommand {
return ['profile', 'debug'];
}

handle(cmd, cb) {
handle(cmd) {
let profile;
try {
profile = profileHelper.runtimeProfile(cmd.profile);
new ResourcesConfig(path.join(process.cwd(), CONSTANTS.FILE_PATH.ASK_RESOURCES_JSON_CONFIG));
} catch (err) {
Messenger.getInstance().error(err);
return cb(err);
return;
}

const skillId = ResourcesConfig.getInstance().getSkillId(profile);
const deployState = ResourcesConfig.getInstance().getSkillInfraDeployState(profile);
const repositoryUrl = deployState.repository.url;
const { repository } = ResourcesConfig.getInstance().getSkillInfraDeployState(profile);
if (!repository) {
Messenger.getInstance().error('Failed to get the git repository from ask-cli project. '
+ 'Please verify the completeness of your skill project.');
return;
}

const smapiClient = new SmapiClient({
profile,
doDebug: cmd.debug
});
// TODO: to use git-credential-cache - Helper to temporarily store passwords in memory
smapiClient.skill.alexaHosted.getGitCredentials(skillId, repositoryUrl, (err, response) => {
smapiClient.skill.alexaHosted.getGitCredentials(skillId, repository.url, (err, response) => {
if (err) {
Messenger.getInstance().error(err);
return cb(err);
return;
}
if (response.statusCode >= 300) {
const error = jsonView.toString(response.body);
Messenger.getInstance().error(error);
return cb(error);
return;
}
const { repositoryCredentials } = response.body;
const output = `username=${repositoryCredentials.username}
password=${repositoryCredentials.password}`;
Messenger.getInstance().info(output);
cb();
});
}
}
Expand Down
6 changes: 5 additions & 1 deletion lib/commands/util/upgrade-project/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,19 +50,23 @@ function extractUpgradeInformation(v1RootPath, profile) {
If the skill has never been deployed in v1 ask-cli, please start from v2 structure.`);
}
if (v1ProjData.isHosted) {
// 3. check git credentials
const hostInfo = R.view(R.lensPath(['alexaHosted', 'gitCredentialsCache']), hiddenConfig);
if (!hostInfo) {
throw new CliError('Failed to find gitCredentialsCache for an Alexa hosted skill.');
}
return {
skillId: v1ProjData.skillId,
isHosted: v1ProjData.isHosted,
gitRepoUrl: `${hostInfo.protocol}://${hostInfo.host}/${hostInfo.path}`
};
}

// 3.resolve Lambda codebase for each region
const lambdaMapByRegion = {};
for (const lambdaResource of v1ProjData.lambdaList) {
_collectLambdaMapFromResource(lambdaMapByRegion, lambdaResource);
}

return {
skillId: v1ProjData.skillId,
lambdaResources: lambdaMapByRegion
Expand Down
62 changes: 60 additions & 2 deletions lib/commands/util/upgrade-project/hosted-skill-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,39 @@ const fs = require('fs-extra');
const R = require('ramda');
const path = require('path');

const HostedSkillController = require('@src/controllers/hosted-skill-controller');
const SkillMetadataController = require('@src/controllers/skill-metadata-controller');
const CLiError = require('@src/exceptions/cli-error');
const ResourcesConfig = require('@src/model/resources-config');
const CONSTANTS = require('@src/utils/constants');
const stringUtils = require('@src/utils/string-utils');

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

/**
* To check dev branch status is clean before upgrading
* @param {Object} gitClient the git client
*/
function checkIfDevBranchClean(gitClient) {
gitClient.checkoutBranch('dev');
const statusOutput = gitClient.shortStatus();
if (statusOutput.toString()) {
throw new CLiError(`Commit the following files in the dev branch before upgrading project:\n${statusOutput}`);
}
const countOutput = gitClient.countCommitDifference('origin/dev', 'dev');
const counter = countOutput.toString().replace(/\D/g, '');
if (stringUtils.isNonBlankString(counter) && counter !== '0') {
throw new CLiError('Upgrade project failed. Please follow the project upgrade instruction from '
+ 'https://github.com/alexa-labs/ask-cli/blob/develop/docs/Upgrade-Project-From-V1.md#upgrade-steps to change to ask-cli v2 structure.');
}
}

/**
* To create v2 project structure for an Alexa hosted skill project
* @param {String} rootPath the root path
Expand Down Expand Up @@ -41,7 +64,7 @@ function createV2ProjectSkeleton(rootPath, skillId, profile) {
* @param {String} skillStage the skill stage
* @param {String} profile the profile
* @param {Boolean} doDebug the debug flag
* @param {callback} callback { err }
* @param {callback} callback { error }
*/
function downloadSkillPackage(rootPath, skillId, skillStage, profile, doDebug, callback) {
const skillMetaController = new SkillMetadataController({ profile, doDebug });
Expand Down Expand Up @@ -77,3 +100,38 @@ function handleExistingLambdaCode(rootPath, gitRepoUrl, profile) {
fs.copySync(v1CodePath, v2CodePath);
ResourcesConfig.getInstance().write();
}

/**
* To update git credential, checkout mater as default branch and set git pre-push template
* @param {String} profile the profile
* @param {Boolean} doDebug the debug flag
* @param {String} gitClient the git client
* @param {callback} callback { error }
*/
function postUpgradeGitSetup(profile, doDebug, gitClient, callback) {
try {
_setGitCredentialHelper(profile, gitClient);
_setMasterAsDefault(gitClient);
} catch (error) {
return callback(error);
}
_setPrePushHookTemplate(profile, doDebug, (hooksErr) => callback(hooksErr || null));
}

function _setGitCredentialHelper(profile, gitClient) {
const credentialHelperPath = `askx util git-credentials-helper --profile ${profile}`;
gitClient.configureCredentialHelper(credentialHelperPath);
}

function _setMasterAsDefault(gitClient) {
gitClient.checkoutBranch('master');
gitClient.merge('dev');
gitClient.deleteBranch('dev');
}

function _setPrePushHookTemplate(profile, doDebug, callback) {
const hostedSkillController = new HostedSkillController(profile, doDebug);
const templateUrl = CONSTANTS.HOSTED_SKILL.GIT_HOOKS_TEMPLATES.PRE_PUSH.URL;
const filePath = path.join('.git', 'hooks', 'pre-push');
hostedSkillController.downloadGitHooksTemplate(templateUrl, filePath, (hooksErr) => callback(hooksErr || null));
}
Loading

0 comments on commit b303741

Please sign in to comment.