diff --git a/lib/Serverless.js b/lib/Serverless.js index 628656d45..033ba13eb 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -25,7 +25,7 @@ BbPromise.longStackTraces(); class Serverless { - constructor(project, config) { + constructor(project, config) { // Add version this._version = require('./../package.json').version; @@ -33,21 +33,21 @@ class Serverless { // Set Default Config this.config = { - interactive: false, - awsAdminKeyId: null, - awsAdminSecretKey: null, - serverlessPath: __dirname + interactive: false, + serverlessPath: __dirname, + awsProfile: null }; this.classes = { // TODO: add Stage, Region - State: require('./ServerlessState'), - Meta: require('./ServerlessMeta'), - Project: require('./ServerlessProject'), - Component: require('./ServerlessComponent'), - Function: require('./ServerlessFunction'), - Endpoint: require('./ServerlessEndpoint'), - Event: require('./ServerlessEvent') + ProviderAws: require('./ServerlessProviderAws'), + State: require('./ServerlessState'), + Meta: require('./ServerlessMeta'), + Project: require('./ServerlessProject'), + Component: require('./ServerlessComponent'), + Function: require('./ServerlessFunction'), + Endpoint: require('./ServerlessEndpoint'), + Event: require('./ServerlessEvent') }; this.setProject( project ); @@ -56,6 +56,7 @@ class Serverless { this.updateConfig(config); // Add Defaults + this._providers = {}; this.actions = {}; this.hooks = {}; this.commands = {}; @@ -63,7 +64,8 @@ class Serverless { this.state = new this.classes.State(this); // temporarily re-added to increase stability if (this.hasProject()) { - // TODO: move this to Project class, along with credentials + + // TODO: Write more logic to handle loading admin.env and loading from s-variables-common // Load Admin ENV information require('dotenv').config({ @@ -71,7 +73,7 @@ class Serverless { path: path.join(this.getProject().getRootPath(), 'admin.env') }); - this._setCredentials(); + this.initProviders(); } // Load Plugins: Framework Defaults @@ -101,26 +103,6 @@ class Serverless { } } - /** - * Set Credentials - * - fill in the credentials by profile or by given credentials - */ - - _setCredentials() { - - // Set Admin API Keys - var profiles = awsMisc.profilesMap(); - - if (process.env.SERVERLESS_ADMIN_AWS_PROFILE && profiles[process.env.SERVERLESS_ADMIN_AWS_PROFILE]) { - this.config.awsAdminKeyId = profiles[process.env.SERVERLESS_ADMIN_AWS_PROFILE]['aws_access_key_id']; - this.config.awsAdminSecretKey = profiles[process.env.SERVERLESS_ADMIN_AWS_PROFILE]['aws_secret_access_key']; - } else { - // Set Admin API Keys - this.config.awsAdminKeyId = process.env.SERVERLESS_ADMIN_AWS_ACCESS_KEY_ID || this.config.awsAdminKeyId; - this.config.awsAdminSecretKey = process.env.SERVERLESS_ADMIN_AWS_SECRET_ACCESS_KEY || this.config.awsAdminSecretKey; - } - } - /** * Init * - Initializes state @@ -130,13 +112,35 @@ class Serverless { init() { return this.getProject().load(); } - // DEPRECATED: Keeping this for backwards compatibility. // TODO: Remove eventually. Many CI/CD systems are using this. _init() { return this.getProject().load(); } + /** + * Providers + */ + + initProviders() { + + // Read admin.env and _meta/variables/s-variables-common.json + + // Initialize AWS + if (process.env.SERVERLESS_AWS_PROFILE || + this.config.awsProfile || (this.config.awsAdminKeyId && this.config.awsAdminSecretKey)) { + this._providers.aws = new this.classes.Aws(this.config); + } + } + + getProvider(name) { + return this._providers[name.toLowerCase()]; + } + + hasProvider(name) { + return this._providers[name.toLowerCase()] != undefined; + } + /** * Execute */ @@ -240,6 +244,10 @@ class Serverless { this.config = extend(this.config, config); } + /** + * Load Project Plugins + */ + loadProjectPlugins() { if( this.hasProject() ) { this._loadPlugins( this.getProject().getRootPath(), this.getProject().getPlugins() ); diff --git a/lib/ServerlessProviderAws.js b/lib/ServerlessProviderAws.js new file mode 100644 index 000000000..54774f844 --- /dev/null +++ b/lib/ServerlessProviderAws.js @@ -0,0 +1,128 @@ +'use strict'; + +/** + * Serverless Provider AWS Class + */ + +const SError = require('./ServerlessError'), + SUtils = require('./utils/index'), + SCli = require('./utils/cli'), + BbPromise = require('bluebird'), + httpsProxyAgent = require('https-proxy-agent'), + path = require('path'), + _ = require('lodash'), + url = require('url'), + fs = require('fs'), + os = require('os'); + +// Load AWS Globally for the first time +const AWS = require('aws-sdk'); + +class ServerlessProviderAws { + + /** + * Constructor + */ + + // TODO: Move project bucket functions here + + constructor(serverless) { + + // Defaults + this._S = serverless; + + // Set ENV var prefix - defaults to SERVERLESS_ + this._config.envPrefix = (process.env.SERVERLESS_ENV_PREFIX || 'SERVERLESS').toUpperCase(); + if (this._config.envPrefix.charAt(this._config.envPrefix.length-1) !== '_') { + this._config.envPrefix = this._config.envPrefix + '_'; + } + + // Detect Profile Prefix + this._config.profilePrefix = process.env[this._config.envPrefix + 'AWS_PROFILE_PREFIX'] ? process.env[this._config.envPrefix + 'AWS_PROFILE_PREFIX'] : null; + if (this._config.profilePrefix && this._config.profilePrefix.charAt(this._config.profilePrefix.length-1) !== '_') { + this._config.profilePrefix = this._config.profilePrefix + '_'; + } + + // Use Proxy + 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); + } + } + + /** + * Set Credentials + */ + + setCredentials(stage) { + + let credentials; + + // Set Credentials + if (stage || process.env[this._config.envPrefix + 'AWS_PROFILE']) { + let profile = stage ? stage : process.env.SERVERLESS_AWS_PROFILE; + profile = (this._config.profilePrefix ? this._config.profilePrefix + '_' + profile : profile).toLowerCase(); + credentials = this.getProfile(profile); + } else if (process.env[this._config.envPrefix + 'AWS_ACCESS_KEY_ID'] && process.env[this._config.envPrefix + 'SECRET_ACCESS_KEY']) { + credentials = { + accessKeyId: process.env[this._config.envPrefix + 'AWS_ACCESS_KEY_ID'], + secretAccessKey: process.env[this._config.envPrefix + 'SECRET_ACCESS_KEY'] + }; + } else if (process.env[this._config.envPrefix + 'AWS_SESSION_TOKEN']) { + credentials = { + sessionToken: process.env[this._config.envPrefix + 'AWS_SESSION_TOKEN'] + }; + } + } + + /** + * 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 []; + } + } + + /** + * Get Profile + * - Gets a single profile from ~/.aws/credentials + */ + + getProfile(awsProfile) { + let profiles = this.getAllProfiles(); + if (!profiles[awsProfile]) { + throw new SError(`Cant find profile ${awsProfile} in ~/.aws/credentials`, awsProfile); + } + return profiles; + }; + +} + +module.exports = ServerlessProviderAws; \ No newline at end of file diff --git a/lib/utils/aws/Misc.js b/lib/utils/aws/Misc.js index 4d96bd7de..d96cdc31b 100644 --- a/lib/utils/aws/Misc.js +++ b/lib/utils/aws/Misc.js @@ -20,7 +20,7 @@ module.exports.validLambdaRegions = [ 'us-east-1', 'us-west-2', // Oregon 'eu-west-1', // Ireland - 'ap-northeast-1', // Tokyo + 'ap-northeast-1' // Tokyo ]; @@ -55,7 +55,6 @@ module.exports.profilesMap = function() { catch (e) { return []; } - }; /** diff --git a/package.json b/package.json index 4657d8538..a7c4599c3 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dotenv": "^2.0.0", "download": "^4.2.0", "fs-extra": "^0.26.4", + "https-proxy-agent": "^1.0.0", "json-diff": "^0.3.1", "keypress": "^0.2.1", "lodash": "^4.2.1",