From 5bf55fe71c00e3bd2660f6749b24aafa4e0844e9 Mon Sep 17 00:00:00 2001 From: line0 Date: Wed, 27 Sep 2017 22:30:07 +0200 Subject: [PATCH] add support for passing an AWS.Credentials object via options.amazon.credentials --- README.md | 2 +- src/plugin.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++++--- test/test.js | 12 +++++++++ 3 files changed, 84 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 92bf4ec..950a5b5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ gulp.task('deploy', function() { The code above would work as follows * Take the files sepcified by `gulp.src` and zip them on a file named `{ version }-{ timestamp }.zip` (i.e: `1.0.0-2016.04.08_13.26.32.zip`) -* If amazon credentials (`accessKeyId`, `secretAccessKey`) are provided in the `amazon` object, set them on the `AWS.config.credentials`. If not provided, the default values from AWS CLI configuration will be used. +* AWS credentials may be provided either in Form of a `accessKeyId` and `secretAccessKey` or a [`credentials` object](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Credentials.html). If no credentials are provided, the default values from AWS CLI configuration will be used. * Try to upload the zipped file to the bucket specified by `amazon.bucket`. If it fails because the bucket doesn't exist, try to create the bucket and then try to upload the zipped file again * Uploads the ziped files to the bucket on the path `{{ name }}/{{ filename }}` (i.e: `my-application/1.0.0-2016.04.08_13.26.32.zip`) * Creates a new version on the Application specified by `applicationName` with VersionLabel `{ version }-{ timestamp }` (i.e: `1.0.0-2016.04.08_13.26.32`) diff --git a/src/plugin.js b/src/plugin.js index e95706e..fca0899 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -1,4 +1,4 @@ -import { readFileSync } from 'fs' +import { readFileSync, existsSync } from 'fs' import { join } from 'path' import { omit, isEqual } from 'lodash' import { log as gulpLog, colors, PluginError } from 'gulp-util' @@ -9,6 +9,37 @@ import AWS from 'aws-sdk' import pad from 'left-pad' import { S3File, Bean } from './aws' +const credentialProviders = [ + { + Ctor: AWS.SAMLCredentials, + fields: [ + [ 'RoleArn', 'PrincipalArn', 'SAMLAssertion' ] + ] + }, + { + Ctor: AWS.CognitoIdentityCredentials, + fields: [ + [ 'IdentityPoolId' ], + [ 'IdentityId' ] + ] + }, + { + // we can only detect these if a custom profile is specified + // but that is fine because shared ini file credentials using the default profile + // are used by the AWS SDK when no credentials are specified + Ctor: AWS.SharedIniFileCredentials, + fields: [ + [ 'profile' ] + ] + }, + { + Ctor: AWS.TemporaryCredentials, + fields: [ + [ 'SerialNumber', 'TokenCode' ], + [ 'RoleArn' ] + ] + } +] const IS_TEST = process.env['NODE_ENV'] === 'test' const log = IS_TEST ? () => {} : gulpLog @@ -191,8 +222,45 @@ export function buildOptions(opts) { if (!options.amazon) throw new PluginError(PLUGIN_NAME, 'No amazon config provided') - // if keys are provided, create new credentials, otherwise defaults will be used - if (options.amazon.accessKeyId && options.amazon.secretAccessKey) { + if (options.amazon.credentials) { + const creds = options.amazon.credentials + const credsType = typeof(creds) + + if (credsType === 'string') { + // if the credentials are of type string, assume the user is specifying + // an environment variable name prefix + AWS.config.credentials = existsSync(creds) ? new AWS.EnvironmentCredentials(creds) + : new AWS.FileSystemCredentials(creds) + } else if (credsType !== 'object') { + // otherwise the credentials must be an object + throw new PluginError(PLUGIN_NAME, `Amamzon credentials must be an object, got a '${typeof(creds)}'.`) + } else if (creds.constructor.name === 'Credentials' || + typeof(creds.constructor.__super__) === 'function' && + creds.constructor.__super__.name === 'Credentials') { + // support pre-build objects of or inheriting the AWS.Credentials class + AWS.config.credentials = creds + } else if (creds.accessKeyId && creds.secretAccessKey) { + // support simple access key credentials + AWS.config.credentials = new AWS.Credentials(creds.accessKeyId, creds.secretAccessKey, creds.sessionToken) + } else { + // otherwise try to find a matching provider for the supplied credentials object + // if that fails, create temporary credentials (which do not have any mandatory parameter) as a last resort + const provider = credentialProviders.find(prov => + prov.fields.find(fields => + fields.every(field => creds[field] !== undefined) + ) + ) + if (provider === undefined) { + AWS.config.credentials = new AWS.TemporaryCredentials() + } else try { + AWS.config.credentials = new provider.Ctor(creds) + } catch(err) { + throw new PluginError(PLUGIN_NAME, `An error occured while trying to construct AWS.${provider.Ctor.name} from supplied credentials object: ${err}`) + } + } + } else if (options.amazon.accessKeyId && options.amazon.secretAccessKey) { + // legacy support for the access key id and secret access key + // passed in directly via the options.amazon object AWS.config.credentials = new AWS.Credentials({ accessKeyId: opts.amazon.accessKeyId, secretAccessKey: opts.amazon.secretAccessKey diff --git a/test/test.js b/test/test.js index 4cd7689..22a48ee 100644 --- a/test/test.js +++ b/test/test.js @@ -578,6 +578,18 @@ describe('Gulp plugin', () => { AWS.Credentials.called.should.be.false() should(AWS.config.credentials).be.null() }) + + it('updates AWS.config.credentials with a Credentials object', () => { + spy(AWS, 'Credentials') + const credentials = new AWS.Credentials() + buildOptions({ + amazon: { + credentials: credentials + } + }) + AWS.Credentials.calledOnce.should.be.true() + AWS.config.credentials.should.be.equal(credentials) + }) }) describe('gulpEbDeploy', () => {