mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
502 lines
12 KiB
JavaScript
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);
|
|
}
|
|
});
|
|
});
|
|
};
|