Skip to content
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ The IAM User that is used to run the action requires the following IAM permissio
As a note to my future self, in order to work on this repo:

* Clone it
* Run `npm install` to fetch dependencies
* Run `yarn install` to fetch dependencies
* _hack hack hack_
* Run `npm run build` to update `dist/*`, which holds the files actually run
* Read https://help.github.com/en/articles/creating-a-javascript-action if unsure.
Expand Down
87 changes: 87 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
'use strict';

(async function () {
var core = require('@actions/core');
core.setOutput = function () {};
core.setFailed = function (message) {
console.log(message instanceof Error ? message.toString() : message);
process.exitCode = 1;
}

const fs = require('fs');
if (!fs.existsSync('./appspec.yml')) {
core.setFailed("❓ ./appspec.yml does not exist. Make sure you are in the project's top level directory.");
process.exit();
}

const simpleGit = require('simple-git');
const git = simpleGit();
var branchName, commitId;

try {
await git.init();
const remotes = await git.getRemotes(true);
var applicationName, fullRepositoryName;

for (const remote of remotes) {
if (remote.name !== 'origin') {
continue;
}

const url = remote.refs.push;

var matches

if (matches = url.match(/[email protected]:([a-z0-9_-]+)\/([a-z0-9_-]+).git/)) {
applicationName = matches[2];
fullRepositoryName = `${matches[1]}/${matches[2]}`;
}
}

branchName = await git.revparse(['--abbrev-ref', 'HEAD']);
commitId = await git.revparse(['HEAD']);
} catch (e) {
core.setFailed('🌩 Failed to parse git information. Are you sure this is a git repo?')
process.exit();
}

if (!applicationName || !fullRepositoryName) {
core.setFailed("❓ Unable to parse GitHub repository name from the 'origin' remote.");
process.exit();
}

console.log("🚂 OK, let's ship this...");
console.log(`GitHub 💎 repository '${fullRepositoryName}'`);
console.log(`Branch 🎋 ${branchName}`);
console.log(`Commit ⚙️ ${commitId}`);

const prompt = require('prompt');

prompt.message = '';
prompt.start();

try {
await prompt.get({
properties: {
confirm: {
name: 'yes',
message: 'Type "yes" to create deployment',
validator: /yes/,
warning: 'Must respond yes to continue',
default: ''
}
}
});
} catch (e) {
core.setFailed('🙈 Aborted.');
process.exit();
}

const action = require('./create-deployment');
try {
await action.createDeployment(applicationName, fullRepositoryName, branchName, commitId, core);
} catch (e) {
console.log(`👉🏻 ${e.message}`);
process.exit(1);
}
})();
119 changes: 119 additions & 0 deletions create-deployment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
'use strict';

function fetchBranchConfig(branchName) {
const fs = require('fs');
const yaml = require('js-yaml');

let fileContents = fs.readFileSync('./appspec.yml', 'utf8');
let data = yaml.safeLoad(fileContents);

for (var prop in data.branch_config) {
var regex = new RegExp('^' + prop + '$', 'i');
if (branchName.match(regex)) {
if (data.branch_config[prop] == null) {
console.log(`🤷🏻‍♂️ Found an empty appspec.yml -> branch_config for '${branchName}' – skipping deployment`);
process.exit();
}
console.log(`💡 Using appspec.yml -> branch_config '${prop}' for branch '${branchName}'`);
return data.branch_config[prop];
}
}

console.log(`❓ Found no matching appspec.yml -> branch_config for '${branchName}' – skipping deployment`);
process.exit();
}

exports.createDeployment = async function(applicationName, fullRepositoryName, branchName, commitId, core) {
const branchConfig = fetchBranchConfig(branchName);
const safeBranchName = branchName.replace(/[^a-z0-9-/]+/gi, '-').replace(/\/+/, '--');
const deploymentGroupName = branchConfig.deploymentGroupName ? branchConfig.deploymentGroupName.replace('$BRANCH', safeBranchName) : safeBranchName;
const deploymentGroupConfig = branchConfig.deploymentGroupConfig;
const deploymentConfig = branchConfig.deploymentConfig;

console.log(`🎳 Using deployment group '${deploymentGroupName}'`);

const client = require('aws-sdk/clients/codedeploy');
const codeDeploy = new client();

try {
await codeDeploy.updateDeploymentGroup({
...deploymentGroupConfig,
...{
applicationName: applicationName,
currentDeploymentGroupName: deploymentGroupName
}
}).promise();
console.log(`⚙️ Updated deployment group '${deploymentGroupName}'`);
core.setOutput('deploymentGroupCreated', false);
} catch (e) {
if (e.code == 'DeploymentGroupDoesNotExistException') {
await codeDeploy.createDeploymentGroup({
...deploymentGroupConfig,
...{
applicationName: applicationName,
deploymentGroupName: deploymentGroupName,
}
}).promise();
console.log(`🎯 Created deployment group '${deploymentGroupName}'`);
core.setOutput('deploymentGroupCreated', true);
} else {
core.setFailed(`🌩 Unhandled exception`);
throw e;
}
}

let tries = 0;
while (true) {

if (++tries > 5) {
core.setFailed('🤥 Unable to create a new deployment (too much concurrency?)');
return;
}

try {
var {deploymentId: deploymentId} = await codeDeploy.createDeployment({
...deploymentConfig,
...{
applicationName: applicationName,
deploymentGroupName: deploymentGroupName,
revision: {
revisionType: 'GitHub',
gitHubLocation: {
commitId: commitId,
repository: fullRepositoryName
}
}
}
}).promise();
console.log(`🚚️ Created deployment ${deploymentId} – https://console.aws.amazon.com/codesuite/codedeploy/deployments/${deploymentId}?region=${codeDeploy.config.region}`);
core.setOutput('deploymentId', deploymentId);
core.setOutput('deploymentGroupName', deploymentGroupName);
break;
} catch (e) {
if (e.code == 'DeploymentLimitExceededException') {
var [, otherDeployment] = e.message.toString().match(/is already deploying deployment \'(d-\w+)\'/);
console.log(`😶 Waiting for another pending deployment ${otherDeployment}`);
try {
await codeDeploy.waitFor('deploymentSuccessful', {deploymentId: otherDeployment}).promise();
console.log(`🙂 The pending deployment ${otherDeployment} sucessfully finished.`);
} catch (e) {
console.log(`🤔 The other pending deployment ${otherDeployment} seems to have failed.`);
}
continue;
} else {
core.setFailed(`🌩 Unhandled exception`);
throw e;
}
}
}

console.log(`⏲ Waiting for deployment ${deploymentId} to finish`);

try {
await codeDeploy.waitFor('deploymentSuccessful', {deploymentId: deploymentId}).promise();
console.log('🥳 Deployment successful');
} catch (e) {
core.setFailed(`😱 The deployment ${deploymentId} seems to have failed.`);
throw e;
}
}
Loading