serverless/lib/ProviderAws.js
2016-03-14 10:41:51 -07:00

354 lines
12 KiB
JavaScript

'use strict';
/**
* Serverless Provider AWS Class
*/
const SError = require('./Error'),
SCli = require('./utils/cli'),
BbPromise = require('bluebird'),
httpsProxyAgent = require('https-proxy-agent'),
path = require('path'),
_ = require('lodash'),
url = require('url'),
fs = require('fs'),
fse = require('fs-extra'),
os = require('os');
let SUtils;
// Load AWS Globally for the first time
const AWS = require('aws-sdk');
class ServerlessProviderAws {
constructor(serverless, config) {
// Defaults
this._S = serverless;
this._config = config || {};
this.sdk = AWS; // We recommend you use the "request" method instead
SUtils = this._S.utils;
// Use HTTPS Proxy (Optional)
let proxy = process.env.proxy || process.env.HTTP_PROXY || process.env.http_proxy || process.env.HTTPS_PROXY || process.env.https_proxy;
if (proxy) {
let proxyOptions;
proxyOptions = url.parse(proxy);
proxyOptions.secureEndpoint = true;
AWS.config.httpOptions.agent = new httpsProxyAgent(proxyOptions);
}
// Detect Profile Prefix. Useful for multiple projects (e.g., myproject_prod)
this._config.profilePrefix = process.env['AWS_PROFILE_PREFIX'] ? process.env['AWS_PROFILE_PREFIX'] : null;
if (this._config.profilePrefix && this._config.profilePrefix.charAt(this._config.profilePrefix.length - 1) !== '_') {
this._config.profilePrefix = this._config.profilePrefix + '_';
}
this.validRegions = [
'us-east-1',
'us-west-2', // Oregon
'eu-west-1', // Ireland
'ap-northeast-1' // Tokyo
];
}
/**
* Request
* - Perform an SDK request
*/
request(service, method, params, stage, region, options) {
let _this = this;
let awsService = new this.sdk[service](_this.getCredentials(stage, region));
let req = awsService[method](params);
// TODO: Add listeners, put Debug statments here...
//req.on('validate', function (r) {});
return SUtils.persistentRequest(function () {
return new BbPromise(function (resolve, reject) {
req.send(function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
});
}
/**
* Get Provider Name
*/
getProviderName() {
return 'Amazon Web Services';
}
/**
* Add credentials, if present, from the serverless configuration
* @param credentials The credentials to add configuration credentials to
* @param config The serverless configuration
*/
addConfigurationCredentials(credentials, config) { // just transfer the credentials
if(config) {
if(config.awsAdminKeyId) {
credentials.accessKeyId = config.awsAdminKeyId;
}
if(config.awsAdminSecretKey) {
credentials.secretAccessKey = config.awsAdminSecretKey;
}
if(config.awsAdminSessionToken) {
credentials.sessionToken = config.awsAdminSessionToken;
}
}
}
/**
* Add credentials, if present, from the environment
* @param credentials The credentials to add environment credentials to
* @param prefix The environment variable prefix to use in extracting credentials from the environment
*/
addEnvironmentCredentials(credentials, prefix) { // separate credential environment variable prefix from obtaining the credentials from the environment.
let environmentCredentials = new AWS.EnvironmentCredentials(prefix);
if(environmentCredentials) {
if(environmentCredentials.accessKeyId) {
credentials.accessKeyId = environmentCredentials.accessKeyId;
}
if(environmentCredentials.secretAccessKey) {
credentials.secretAccessKey = environmentCredentials.secretAccessKey;
}
if (environmentCredentials.sessionToken) {
credentials.sessionToken = environmentCredentials.sessionToken;
}
}
}
/**
* Add credentials from a profile, if the profile exists
* @param credentials The credentials to add profile credentials to
* @param prefix The prefix to the profile environment variable
*/
addProfileCredentialsImpl(credentials, prefix) { // separate profile environment variable prefix from obtaining credentials from the profile.
let profile = process.env[prefix + '_PROFILE'],
profileCredentials;
if(profile) {
profileCredentials = this.getProfile(profile, true);
if (profileCredentials) {
if (profileCredentials.aws_access_key_id) {
credentials.accessKeyId = profileCredentials.aws_access_key_id;
}
if (profileCredentials.aws_secret_access_key) {
credentials.secretAccessKey = profileCredentials.aws_secret_access_key;
}
if (profileCredentials.aws_session_token) { // node.js aws-sdk standard
credentials.sessionToken = profileCredentials.aws_session_token;
}
if (profileCredentials.aws_security_token) { // python boto standard
credentials.sessionToken = profileCredentials.aws_security_token;
}
}
}
}
/**
* Add credentials from a profile, if the profile exists adding the profile name prefix if supplied
* @param credentials The credentials to add profile credentials to
* @param prefix The prefix to the profile environment variable
*/
addProfileCredentials(credentials, prefix) {
if(this._config.profilePrefix) {
prefix = this._config.profilePrefix + prefix;
}
this.addProfileCredentialsImpl(credentials, prefix);
}
/**
* Get Credentials
* - Fetches credentials from ENV vars via profile, access keys, or session token
* - Don't use AWS.EnvironmentCredentials, since we want to require "AWS" in the ENV var names, otherwise provider trampling could occur
* - TODO: Remove Backward Compatibility: Older versions include "ADMIN" in env vars, we're not using that anymore. Too long.
*/
getCredentials(stage, region) {
let credentials = { region: region };
stage = stage ? stage.toUpperCase() : null;
// implicitly already in the config...
this.addConfigurationCredentials(credentials, this._S.config); // use the given configuration credentials if they are the only available credentials.
// first from environment
this.addEnvironmentCredentials(credentials, 'AWS'); // allow for Amazon standard credential environment variable prefix.
this.addEnvironmentCredentials(credentials, 'SERVERLESS_ADMIN_AWS'); // but override with more specific credentials if these are also provided.
this.addEnvironmentCredentials(credentials, 'AWS_' + stage); // and also override these with the Amazon standard *stage specific* credential environment variable prefix.
this.addEnvironmentCredentials(credentials, 'SERVERLESS_ADMIN_AWS_' + stage); // finally override all prior with Serverless prefixed *stage specific* credentials if these are also provided.
// next from profile
this.addProfileCredentials(credentials, 'AWS'); // allow for generic Amazon standard prefix based profile declaration
this.addProfileCredentials(credentials, 'SERVERLESS_ADMIN_AWS'); // allow for generic Serverless standard prefix based profile declaration
this.addProfileCredentials(credentials, 'AWS_' + stage); // allow for *stage specific* Amazon standard prefix based profile declaration
this.addProfileCredentials(credentials, 'SERVERLESS_ADMIN_AWS_' + stage); // allow for *stage specific* Serverless standard prefix based profile declaration
// if they aren't loaded now, the credentials weren't provided by a valid means
if (!credentials.accessKeyId || !credentials.secretAccessKey) {
throw new SError('Cant find AWS credentials', SError.errorCodes.MISSING_AWS_CREDS);
}
return credentials;
}
/**
* Save Credentials
* - Saves AWS API Keys to a profile on the file system
*/
saveCredentials(accessKeyId, secretKey, profileName, stage) {
let configDir = this.getConfigDir(),
credsPath = path.join(configDir, 'credentials');
// Create ~/.aws folder if does not exist
if (!SUtils.dirExistsSync(configDir)) {
fse.mkdirsSync(configDir);
}
let profileEnvVar = (stage ? 'AWS_' + stage + '_PROFILE' : 'AWS_PROFILE').toUpperCase();
SUtils.sDebug('Setting new AWS profile:', profileName);
// Write to ~/.aws/credentials
fs.appendFileSync(
credsPath,
os.EOL +'[' + profileName + ']' + os.EOL +
'aws_access_key_id=' + accessKeyId + os.EOL +
'aws_secret_access_key=' + secretKey + os.EOL);
}
/**
* Get the directory containing AWS configuration files
*/
getConfigDir() {
let env = process.env;
let home = env.HOME ||
env.USERPROFILE ||
(env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null);
if (!home) {
throw new SError('Cant find homedir', SError.errorCodes.MISSING_HOMEDIR);
}
return path.join(home, '.aws');
}
/**
* Get All Profiles
* - Gets all profiles from ~/.aws/credentials
*/
getAllProfiles() {
let credsPath = path.join(this.getConfigDir(), 'credentials');
try {
return AWS.util.ini.parse(AWS.util.readFileSync(credsPath));
}
catch (e) {
return null;
}
}
/**
* Get Profile
* - Gets a single profile from ~/.aws/credentials
*/
getProfile(awsProfile, optional) {
let profile = awsProfile.toLowerCase(),
profiles = this.getAllProfiles();
if (!optional && !profiles[profile]) {
throw new SError(`Cant find profile ${profile} in ~/.aws/credentials`, profile);
}
return profiles[profile];
}
getLambdasStackName(stage, projectName) {
return [projectName, stage, 'l'].join('-');
}
getResourcesStackName(stage, projectName) {
return [projectName, stage, 'r'].join('-');
}
/**
* Get REST API By Name
*/
getApiByName(apiName, stage, region) {
let _this = this;
// Validate Length
if (apiName.length > 1023) {
throw new SError('"'
+apiName
+ '" cannot be used as a REST API name because it\'s over 1023 characters. Please make it shorter.');
}
// Sanitize
apiName = apiName.trim();
let params = {
limit: 500
};
// List all REST APIs
return this.request('APIGateway', 'getRestApis', params, stage, region)
.then(function(response) {
let restApi = null,
found = 0;
// Find REST API w/ same name as project
for (let i = 0; i < response.items.length; i++) {
if (response.items[i].name ===apiName) {
restApi = response.items[i];
found++;
SUtils.sDebug(
'"'
+ stage
+ ' - '
+ region
+ '": found existing REST API on AWS API Gateway with name: '
+apiName);
}
}
// Throw error if they have multiple REST APIs with the same name
if (found > 1) {
throw new SError('You have multiple API Gateway REST APIs in the region ' + region + ' with this name: ' +apiName);
}
if (restApi) return restApi;
});
}
getAccountId(stage, region) {
return this._S.getProject()
.getRegion(stage, region)
.getVariables()
.iamRoleArnLambda
.replace('arn:aws:iam::', '')
.split(':')[0];
}
}
module.exports = ServerlessProviderAws;