2015-09-08 11:31:42 -05:00

502 lines
12 KiB
JavaScript

'use strict';
/**
* JAWS Services: AWS
*/
var Promise = require('bluebird'),
AWS = require('aws-sdk'),
path = require('path'),
os = require('os'),
JawsError = require('../jaws-error/index'),
utils = require('../utils'),
async = require('async'),
fs = require('fs');
Promise.promisifyAll(fs);
/**
* Set AWS SDK Creds and region from a given profile
*
* @param awsProfile
* @param awsRegion
*/
module.exports.configAWS = function(awsProfile, awsRegion) {
// Check Profile Exists
this.profilesGet(awsProfile);
// Set Credentials
AWS.config.credentials = new AWS.SharedIniFileCredentials({
profile: awsProfile,
});
// Set Region
AWS.config.update({
region: awsRegion,
});
};
/**
* Get the directory containing AWS configuration files
*
* @returns {string}
*/
module.exports.getConfigDir = function() {
var env = process.env;
var home = env.HOME ||
env.USERPROFILE ||
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);
if (!home) {
throw new JawsError('Cant find homedir', JawsError.errorCodes.MISSING_HOMEDIR);
}
return path.join(home, '.aws');
};
/**
* Gets a map of AWS profiles from ~/.aws/credentials
*
* @returns {*}
*/
module.exports.profilesMap = function() {
var credsPath = path.join(this.getConfigDir(), 'credentials');
return AWS.util.ini.parse(AWS.util.readFileSync(credsPath));
};
/**
* Set a new AWS profile on the filesystem.
*
* Creates entry in ~/.aws/config and credentials
*
* @param awsProfile
* @param awsRegion
* @param accessKeyId
* @param secretKey
*/
module.exports.profilesSet = function(awsProfile, awsRegion, accessKeyId, secretKey) {
var credsPath = path.join(this.getConfigDir(), 'credentials');
fs.appendFileSync(
credsPath,
'[' + awsProfile + ']' + os.EOL +
'aws_access_key_id = ' + accessKeyId.trim() + os.EOL +
'aws_secret_access_key = ' + secretKey.trim() + os.EOL);
var profileNameForConfig = (awsProfile == 'default') ? 'default' : 'profile ' + awsProfile;
fs.appendFileSync(
path.join(path.dirname(credsPath), 'config'),
'[' + profileNameForConfig + ']' + os.EOL +
'region = ' + awsRegion + os.EOL);
};
/**
* Get AWS profiles from the filesystem
*
* @param awsProfile
* @returns {list} profiles
*/
module.exports.profilesGet = function(awsProfile) {
var profiles = this.profilesMap();
if (!profiles[awsProfile]) {
throw new JawsError('Cant find profile ' + awsProfile + ' in ~/.aws/credentials', awsProfile);
}
return profiles;
};
/**
* IAM: Get role
*
* @param awsProfile
* @param awsRegion
* @param roleName
* @returns {Promise}
*/
exports.iamGetRole = function(awsProfile, awsRegion, roleName) {
var _this = this;
return new Promise(function(resolve, reject) {
// Config AWS
_this.configAWS(awsProfile, awsRegion);
// Instantiate
var IAM = new AWS.IAM({
apiVersion: '2010-05-08',
});
var params = {
RoleName: roleName,
};
IAM.getRole(params, function(error, data) {
if (error) {
return reject(new JawsError(
error.message,
JawsError.errorCodes.UNKNOWN));
} else {
return resolve(data);
}
});
});
};
/**
* CloudFormation: Create Stack
*
* @param awsProfile
* @param awsRegion
* @param projRootPath
* @param projName
* @param projStage
* @param projNotificationEmail
* @returns {Promise}
*/
exports.cfCreateStack = function(awsProfile, awsRegion, projRootPath, projName, projStage, projNotificationEmail) {
var _this = this;
return new Promise(function(resolve, reject) {
// Config AWS
_this.configAWS(awsProfile, awsRegion);
// Instantiate
var CF = new AWS.CloudFormation({
apiVersion: '2010-05-15',
});
var params = {
StackName: projStage + '-' + projName, // stack names are alphanumeric + -, no _ :(
Capabilities: [
'CAPABILITY_IAM',
],
OnFailure: 'ROLLBACK',
Parameters: [{
ParameterKey: 'aaProjectName',
ParameterValue: projName,
UsePreviousValue: false,
}, {
ParameterKey: 'aaStage',
ParameterValue: projStage,
UsePreviousValue: false,
}, {
ParameterKey: 'aaDataModelPrefix',
ParameterValue: projStage,
UsePreviousValue: false,
}, {
ParameterKey: 'aaHostedZoneName',
ParameterValue: 'mydomain.com', //TODO: should we prompt for this?
UsePreviousValue: false,
}, {
ParameterKey: 'aaNotficationEmail',
ParameterValue: projNotificationEmail,
UsePreviousValue: false,
}, {
ParameterKey: 'aaDefaultDynamoRWThroughput',
ParameterValue: '1',
UsePreviousValue: false,
},],
Tags: [{
Key: 'STAGE',
Value: projStage,
},],
// Gotta be careful, TemplateBody has a limit of 51,200 bytes. If we hit limit use TemplateURL
TemplateBody: JSON.stringify(require(path.join(projRootPath, 'jaws-cf.json'))),
};
CF.createStack(params, function(error, data) {
if (error) {
console.error(error);
return reject(new JawsError(error.message, JawsError.errorCodes.UNKNOWN));
} else {
return resolve(data);
}
});
});
};
exports.monitorCfCreate = function(cfData, awsProfile, region, spinner) {
var _this = this;
return new Promise(function(resolve, reject) {
var stackStatus = null,
stackData = null;
async.whilst(
function() {
return stackStatus !== 'CREATE_COMPLETE';
},
function(callback) {
// Call AWS every 5 minutes until CF Stack has been created
setTimeout(function() {
_this.cfDescribeStacks(awsProfile, region, cfData.StackId)
.then(function(data) {
stackData = data;
stackStatus = stackData.Stacks[0].StackStatus;
if (!stackStatus || ['CREATE_IN_PROGRESS', 'CREATE_COMPLETE'].indexOf(stackStatus) === -1) {
spinner.stop(true);
return reject(new JawsError(
'Something went wrong while creating your JAWS resources',
JawsError.errorCodes.UNKNOWN));
} else {
return callback();
}
});
}, 5000);
},
function() {
// Stop Spinner, inform
spinner.stop(true);
console.log('CloudFormation Stack ' + stackData.Stacks[0].StackName + ' successfully created.');
return resolve(stackData.Stacks[0].Outputs);
}
);
});
};
exports.createBucket = function(awsProfile, awsRegion, bucketName) {
this.configAWS(awsProfile, awsRegion);
var s3 = Promise.promisifyAll(new AWS.S3());
return s3.getBucketAclAsync({Bucket: bucketName})
.then(function() {
})
.error(function(err) {
if (err.code == 'AccessDenied') {
throw new JawsError(
'Bucket ' + bucketName + ' already exists and you do not have permissions to use it',
JawsError.errorCodes.ACCESS_DENIED
);
}
return s3.createBucketAsync({
Bucket: bucketName,
ACL: 'private',
});
});
};
exports.putS3Object = function(awsProfile, awsRegion, params) {
this.configAWS(awsProfile, awsRegion);
var s3 = Promise.promisifyAll(new AWS.S3());
return s3.putObjectAsync(params);
};
/**
* Get object from s3
*
* @param awsProfile
* @param awsRegion
* @param params
* @returns {Promise} s3 data object response
*/
exports.getS3Object = function(awsProfile, awsRegion, params) {
this.configAWS(awsProfile, awsRegion);
var s3 = Promise.promisifyAll(new AWS.S3());
return s3.getObjectAsync(params);
};
/**
* Get the env file for a given stage
*
* @param awsProfile
* @param awsRegion
* @param bucketName
* @param projectName
* @param stage
* @returns {Promise} s3 data object response
*/
exports.getEnvFile = function(awsProfile, awsRegion, bucketName, projectName, stage) {
var key = ['JAWS', 'envVars', projectName, stage].join('/'),
params = {
Bucket: bucketName,
Key: key,
};
utils.logIfVerbose('Getting env file from ' + bucketName + '/' + key);
return this.getS3Object(awsProfile, awsRegion, params);
};
/**
* Put up the env file for a given stage
*
* @param awsProfile
* @param awsRegion
* @param bucketName
* @param projectName
* @param stage
*/
exports.putEnvFile = function(awsProfile, awsRegion, bucketName, projectName, stage, contents) {
var params = {
Bucket: bucketName,
Key: ['JAWS', 'envVars', projectName, stage].join('/'),
ACL: 'private',
ContentType: 'text/plain',
Body: contents,
};
return this.putS3Object(awsProfile, awsRegion, params);
};
/**
* CloudFormation: Describe Stack
*
* @param awsProfile
* @param awsRegion
* @param stackName
* @returns {Promise}
*/
exports.cfDescribeStacks = function(awsProfile, awsRegion, stackName) {
var _this = this;
return new Promise(function(resolve, reject) {
// Config AWS
_this.configAWS(awsProfile, awsRegion);
// Instantiate
var CF = new AWS.CloudFormation({
apiVersion: '2010-05-15',
});
var params = {
StackName: stackName,
};
CF.describeStacks(params, function(error, data) {
if (error) {
return reject(new JawsError(
error.message,
JawsError.errorCodes.UNKNOWN));
} else {
return resolve(data);
}
});
});
};
/**
* CloudFormation: Describe Stack Resource
*
* @param awsProfile
* @param awsRegion
* @param stackId
* @param cfResourceId
* @returns {Promise}
*/
exports.cfDescribeStackResource = function(awsProfile, awsRegion, stackId, cfResourceId) {
var _this = this;
return new Promise(function(resolve, reject) {
// Config AWS
_this.configAWS(awsProfile, awsRegion);
// Instantiate
var CF = new AWS.CloudFormation({
apiVersion: '2010-05-15',
});
var params = {
LogicalResourceId: cfResourceId,
StackName: stackId,
};
CF.describeStackResource(params, function(error, data) {
if (error) {
return reject(new JawsError(
error.message,
JawsError.errorCodes.UNKNOWN));
} else {
return resolve(data);
}
});
});
};
/**
* CloudWatchLogs: Get Log Streams
*
* @param logGroupName
* @param limit
* @returns {Promise}
*/
exports.cwGetLogStreams = function(logGroupName, limit) {
return new Promise(function(resolve, reject) {
// Instantiate
var cwLogs = new AWS.CloudWatchLogs({
apiVersion: '2014-03-28',
});
var params = {
logGroupName: logGroupName,
descending: true,
limit: limit || 5,
orderBy: 'LastEventTime',
};
cwLogs.describeLogStreams(params, function(error, data) {
if (error) {
return reject(new JawsError(
error.message,
JawsError.errorCodes.UNKNOWN));
} else {
return resolve(data);
}
});
});
};
/**
* CloudWatchLogs: Get Log Stream Events
*
* @param logGroupName
* @param logStreamName
* @returns {Promise}
*/
exports.cwGetStreamEvents = function(logGroupName, logStreamName) {
return new Promise(function(resolve, reject) {
// Instantiate
var cwLogs = new AWS.CloudWatchLogs({
apiVersion: '2014-03-28',
});
var params = {
logGroupName: logGroupName,
logStreamName: logStreamName,
};
cwLogs.getLogEvents(params, function(err, data) {
if (error) {
return reject(new JawsError(
error.message,
JawsError.errorCodes.UNKNOWN));
} else {
return resolve(data);
}
});
});
};