Skip to content

Commit

Permalink
add support for passing an AWS.Credentials object via options.amazon.…
Browse files Browse the repository at this point in the history
…credentials
  • Loading branch information
line0 committed Nov 20, 2017
1 parent 559fcba commit d156f6e
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 16 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"left-pad": "^1.1.1",
"lodash": "^4.8.2",
"plexer": "^1.0.1",
"through2": "^2.0.1"
"through2": "^2.0.1",
"uuid": "^3.1.0"
},
"devDependencies": {
"babel-cli": "^6.14.0",
Expand Down
80 changes: 76 additions & 4 deletions src/plugin.js
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -9,6 +9,43 @@ import AWS from 'aws-sdk'
import pad from 'left-pad'
import { S3File, Bean } from './aws'

const credentialProviders = [
{
Ctor: AWS.Credentials,
fields: [
[ 'accessKeyId', 'secretAccessKey' ]
]
},
{
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

Expand Down Expand Up @@ -129,7 +166,7 @@ export async function deploy(opts, file, s3file, bean) {
if (e.code !== 'NoSuchBucket')
throw e

await s3file.create()
await s3file.create(opts.region)
await s3file.upload(file)
}

Expand Down Expand Up @@ -191,8 +228,43 @@ 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 !== undefined) {
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.FileSystemCredentials(creds)
: new AWS.EnvironmentCredentials(creds)
} else if (credsType !== 'object') {
// otherwise the credentials must be an object
throw new PluginError(PLUGIN_NAME, `Amazon 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 {
// otherwise try to find a matching provider for the supplied credentials object
const provider = credentialProviders.find(prov =>
prov.fields.find(fields =>
fields.every(field => creds[field] !== undefined)
)
)
if (provider === undefined)
throw new PluginError(PLUGIN_NAME, `Could not find a matching AWS credentials provider for the supplied credentials object.`)

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
log('options.amazon.accessKeyId and options.amazon.secretAccessKey are deprecated and will be removed in a future version. Use options.amazon.credentials instead.')
AWS.config.credentials = new AWS.Credentials({
accessKeyId: opts.amazon.accessKeyId,
secretAccessKey: opts.amazon.secretAccessKey
Expand Down
185 changes: 175 additions & 10 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
/* eslint require-jsdoc: "off", new-cap: "off", no-invalid-this: "off" */
import { readFileSync } from 'fs'
import { readFileSync, writeFileSync, unlinkSync } from 'fs'
import should from 'should'
import { spy, stub } from 'sinon'
import AWS from 'aws-sdk'
import { File } from 'gulp-util'
import { S3File, Bean } from '../src/aws'
import * as plugin from '../src/plugin'
import gulpEbDeploy from '../src'
import os from 'os'
import uuidv4 from 'uuid/v4'
import path from 'path'

describe('Gulp plugin', () => {
let file
Expand Down Expand Up @@ -539,7 +542,26 @@ describe('Gulp plugin', () => {
}
AWS.config.credentials = null
})
it('updates AWS.config.credentials with the provided values', () => {

it('sets AWS.config with signatureVersion v4 by default', () => {
spy(AWS, 'Credentials')
buildOptions({
amazon: {}
})
AWS.config.signatureVersion.should.be.equal('v4')
})

it('allows to set a signatureVersion for AWS.config', () => {
spy(AWS, 'Credentials')
buildOptions({
amazon: {
signatureVersion: 'v2'
}
})
AWS.config.signatureVersion.should.be.equal('v2')
})

it('updates AWS.config.credentials with legacy values', () => {
spy(AWS, 'Credentials')
buildOptions({
amazon: {
Expand All @@ -548,26 +570,127 @@ describe('Gulp plugin', () => {
}
})
AWS.Credentials.calledOnce.should.be.true()
AWS.config.credentials.should.be.instanceOf(AWS.Credentials)
AWS.config.credentials.accessKeyId.should.be.equal('__accessKeyId')
AWS.config.credentials.secretAccessKey.should.be.equal('__secretAccessKey')
})

it('sets AWS.config with signatureVersion v4 by default', () => {
spy(AWS, 'Credentials')
it('updates AWS.config.credentials with access key id and secret access key.', () => {
buildOptions({
amazon: {}
amazon: {
credentials: {
accessKeyId: '__accessKeyId',
secretAccessKey: '__secretAccessKey'
}
}
})
AWS.config.signatureVersion.should.be.equal('v4')
AWS.config.credentials.should.be.instanceOf(AWS.Credentials)
AWS.config.credentials.accessKeyId.should.be.equal('__accessKeyId')
AWS.config.credentials.secretAccessKey.should.be.equal('__secretAccessKey')
})

it('allows to set a signatureVersion for AWS.config', () => {
spy(AWS, 'Credentials')
it('updates AWS.config.credentials with SAML credentials.', () => {
buildOptions({
amazon: {
signatureVersion: 'v2'
credentials: {
RoleArn: '__roleArn',
PrincipalArn: '__principalArn',
SAMLAssertion: '__samlAssertion'
}
}
})
AWS.config.signatureVersion.should.be.equal('v2')
AWS.config.credentials.should.be.instanceOf(AWS.SAMLCredentials)
AWS.config.credentials.params.RoleArn.should.be.equal('__roleArn')
AWS.config.credentials.params.PrincipalArn.should.be.equal('__principalArn')
AWS.config.credentials.params.SAMLAssertion.should.be.equal('__samlAssertion')
})

it('updates AWS.config.credentials with MFA temporary credentials.', () => {
AWS.config.credentials = new AWS.Credentials()
buildOptions({
amazon: {
credentials: {
SerialNumber: '__serialNumber',
TokenCode: '__tokenCode'
}
}
})
AWS.config.credentials.should.be.instanceOf(AWS.TemporaryCredentials)
AWS.config.credentials.params.SerialNumber.should.be.equal('__serialNumber')
AWS.config.credentials.params.TokenCode.should.be.equal('__tokenCode')
})

it('updates AWS.config.credentials with IAM role temporary credentials.', () => {
AWS.config.credentials = new AWS.Credentials()
buildOptions({
amazon: {
credentials: {
RoleArn: '__roleArn'
}
}
})
AWS.config.credentials.should.be.instanceOf(AWS.TemporaryCredentials)
AWS.config.credentials.params.RoleArn.should.be.equal('__roleArn')
})

it('updates AWS.config.credentials with Cognito identity ID credentials.', () => {
buildOptions({
amazon: {
credentials: {
IdentityId: '__indentityId'
}
}
})
AWS.config.credentials.should.be.instanceOf(AWS.CognitoIdentityCredentials)
AWS.config.credentials.params.IdentityId.should.be.equal('__indentityId')
})

it('updates AWS.config.credentials with Cognito identity pool ID credentials.', () => {
buildOptions({
amazon: {
credentials: {
IdentityPoolId: '__indentityPoolId'
}
}
})
AWS.config.credentials.should.be.instanceOf(AWS.CognitoIdentityCredentials)
AWS.config.credentials.params.IdentityPoolId.should.be.equal('__indentityPoolId')
})

it('updates AWS.config.credentials with an environment credential prefix.', () => {
process.env.__envPrefix_ACCESS_KEY_ID = '__accessKeyId'
process.env.__envPrefix_SECRET_ACCESS_KEY = '__secretAccessKey'

buildOptions({
amazon: {
credentials: '__envPrefix'
}
})
AWS.config.credentials.should.be.instanceOf(AWS.EnvironmentCredentials)
AWS.config.credentials.accessKeyId.should.be.equal('__accessKeyId')
AWS.config.credentials.secretAccessKey.should.be.equal('__secretAccessKey')

process.env.__envPrefix_ACCESS_KEY_ID = ''
process.env.__envPrefix_SECRET_ACCESS_KEY = ''
})

it('updates AWS.config.credentials with credentials loaded from a credential file', () => {
const fileName = path.join(os.tmpdir(), `credentials-${uuidv4()}.json`)
writeFileSync(fileName, JSON.stringify({
accessKeyId: '__accessKeyId',
secretAccessKey: '__secretAccessKey'
}))

buildOptions({
amazon: {
credentials: fileName
}
})
unlinkSync(fileName)

AWS.config.credentials.should.be.instanceOf(AWS.FileSystemCredentials)
AWS.config.credentials.accessKeyId.should.be.equal('__accessKeyId')
AWS.config.credentials.secretAccessKey.should.be.equal('__secretAccessKey')
})

it('does not update AWS.config.credentials if no access parameters were specified', () => {
Expand All @@ -578,6 +701,48 @@ 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)
})

it('throws an error when provided credentials are not a string or object', () => {
(() => buildOptions({
amazon: {
credentials: 0
}
})).should.throw()
})

it('throws an error when no matching credential provider is found', () => {
(() => buildOptions({
amazon: {
credentials: {
unknown: '__unknown'
}
}
})).should.throw()
})

it('rethrows an error thrown in the an AWS credentials constructor', () => {
// temporary credentials missing master credentials
(() => buildOptions({
amazon: {
credentials: {
SerialNumber: '__serialNumber',
TokenCode: '__tokenCode'
}
}
})).should.throw()
})
})

describe('gulpEbDeploy', () => {
Expand Down

0 comments on commit d156f6e

Please sign in to comment.