diff --git a/README.md b/README.md index 343120e..3235c6b 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,19 @@ The region your bucket is located in. (e.g. set this to `eu-west-1` if your buck *Default:* `undefined` +### awsPrefix + +A optional prefix to add to the uploaded destination of your built fastboot assets. Useful if your app is hosted at a nested path on S3. For example if you set `awsPrefix` to `'blog'`, `fastboot-deploy-info.json` will look something like this: +```json +{ + "bucket": "my-bucket", + "key": "blog/dist-0.0.0+a3323e2.zip" +} +``` +**Note** that a trailing slash is added to the value to separate it from the `archivePrefix` value. + +*Default:* `''` + ### revisionKey The unique revision number for the version of the app. By default this option will use either the `revision` passed in from the command line or the `revisionData.revisionKey` property from the deployment context. diff --git a/index.js b/index.js index 137f0d1..f794abb 100644 --- a/index.js +++ b/index.js @@ -52,7 +52,7 @@ function _list(opts) { return new Date(b.LastModified) - new Date(a.LastModified); }) .map((d) => { - let match = d.Key.match(new RegExp(archivePrefix+'([^.]*)\\.zip')); + let match = d.Key.match(new RegExp(archivePrefix+'(.*)\\.zip')); if (!match) { return; // ignore files that are no zipped app builds } @@ -91,7 +91,8 @@ module.exports = { return context.fastbootDownloaderManifestContent; }, - manifestKey: 'fastboot-deploy-info.json' + manifestKey: 'fastboot-deploy-info.json', + awsPrefix: '' }, requiredConfig: ['bucket', 'region'], @@ -100,10 +101,17 @@ module.exports = { let revisionKey = this.readConfig('revisionKey'); let bucket = this.readConfig('bucket'); let archivePrefix = this.readConfig('archivePrefix'); + let awsPrefix = this.readConfig('awsPrefix'); // update manifest-file to point to passed revision let downloaderManifestContent = this.readConfig('downloaderManifestContent'); - let manifest = downloaderManifestContent(bucket, `${archivePrefix}${revisionKey}.zip`); + let buildKey = `${archivePrefix}${revisionKey}.zip`; + if (awsPrefix) { + buildKey = `${awsPrefix}/${buildKey}`; + } + + this.log(`creating manifest for bucket: ${bucket} and buildKey: ${buildKey}`, {verbose: true}); + let manifest = downloaderManifestContent(bucket, buildKey); let AWS = require('aws-sdk'); let RSVP = require('rsvp'); let accessKeyId = this.readConfig('accessKeyId'); @@ -111,6 +119,8 @@ module.exports = { let region = this.readConfig('region'); let manifestKey = this.readConfig('manifestKey'); + manifestKey = awsPrefix ? `${awsPrefix}/${manifestKey}` : manifestKey; + let client = new AWS.S3({ accessKeyId, secretAccessKey, @@ -118,6 +128,8 @@ module.exports = { }); let putObject = RSVP.denodeify(client.putObject.bind(client)); + this.log(`updating manifest at ${manifestKey}`, {verbose: true}); + return putObject({ Bucket: bucket, Key: manifestKey, @@ -135,6 +147,7 @@ module.exports = { let secretAccessKey = this.readConfig('secretAccessKey'); let bucket = this.readConfig('bucket'); let region = this.readConfig('region'); + let awsPrefix = this.readConfig('awsPrefix'); let client = new AWS.S3({ accessKeyId, @@ -146,10 +159,13 @@ module.exports = { let data = fs.readFileSync(context.fastbootArchivePath); + let key = awsPrefix ? `${awsPrefix}/${context.fastbootArchiveName}` : context.fastbootArchiveName; + + this.log(`uploading fastboot archive to ${bucket}/${key}`, {verbose: true}); return putObject({ Bucket: bucket, Body: data, - Key: context.fastbootArchiveName + Key: key }); }, @@ -160,12 +176,22 @@ module.exports = { let bucket = this.readConfig('bucket'); let region = this.readConfig('region'); let manifestKey = this.readConfig('manifestKey'); + let awsPrefix = this.readConfig('awsPrefix'); + + archivePrefix = awsPrefix ? `${awsPrefix}/${archivePrefix}` : archivePrefix; + manifestKey = awsPrefix ? `${awsPrefix}/${manifestKey}` : manifestKey; let opts = { accessKeyId, secretAccessKey, archivePrefix, bucket, region, manifestKey }; - return _list(opts); + return _list(opts) + .then(({ revisions }) => { + revisions.forEach(r => { + this.log(`${r.revision} | ${r.timestamp} | active: ${r.active}`, {verbose: true}); + }); + return { revisions }; + }); }, fetchInitialRevisions: function() { @@ -175,14 +201,22 @@ module.exports = { let bucket = this.readConfig('bucket'); let region = this.readConfig('region'); let manifestKey = this.readConfig('manifestKey'); + let awsPrefix = this.readConfig('awsPrefix'); + + archivePrefix = awsPrefix ? `${awsPrefix}/${archivePrefix}` : archivePrefix; + manifestKey = awsPrefix ? `${awsPrefix}/${manifestKey}` : manifestKey; let opts = { accessKeyId, secretAccessKey, archivePrefix, bucket, region, manifestKey }; - return _list(opts) - .then((data) => { - return { initialRevisions: data.revisions }; + return _list(opts, this) + .then(({ revisions }) => { + revisions.forEach(r => { + this.log(`${r.revision} | ${r.timestamp} | active: ${r.active}`, {verbose: true}); + }); + + return { initialRevisions: revisions }; }); } }); diff --git a/tests/index-test.js b/tests/index-test.js index b417db2..921a61c 100644 --- a/tests/index-test.js +++ b/tests/index-test.js @@ -33,18 +33,21 @@ function cleanBucket() { }); } -function setupTestData() { +function setupTestData(ops = {}) { function addTestData() { let existingDists = ['dist-12.zip', 'dist-34.zip', 'dist-56.zip']; let promises = existingDists.map((n) => { + if (ops.awsPrefix) { + n = `${ops.awsPrefix}/${n}`; + } return put({ Bucket: process.env.TEST_BUCKET, Key: n, Body: 'Body: ' + n }); }); promises.push(put({ Bucket: process.env.TEST_BUCKET, - Key: 'fastboot-deploy-info.json', + Key: (ops.awsPrefix ? `${ops.awsPrefix}/` : '') + 'fastboot-deploy-info.json', Body: JSON.stringify({ bucket: process.env.TEST_BUCKET, - key: 'dist-34.zip' + key: (ops.awsPrefix ? `${ops.awsPrefix}/` : '') + 'dist-34.zip' }) })); @@ -154,6 +157,31 @@ describe('fastboot-app-server-aws plugin', function() { assert.isTrue(false, 'upload failed'); }); }); + + it('uploads objects to a nested path if `awsPrefix` is set', function() { + let FILE_NAME = 'dist-78.zip'; + let CONTENT = 'testtest'; + let PREFIX = 'blog'; + let KEY = `${PREFIX}/${FILE_NAME}`; + + fs.writeFileSync(FILE_NAME, CONTENT); + + context.fastbootArchivePath = FILE_NAME; + context.fastbootArchiveName = FILE_NAME; + + context.config['fastboot-app-server-aws'].awsPrefix = PREFIX; + + return plugin.upload(context) + .then(() => { + return get({ Bucket: process.env.TEST_BUCKET, Key: KEY }); + }) + .then((data) => { + assert.equal(data.Body, CONTENT, 'file was uploaded correctly'); + }) + .catch(() => { + assert.isTrue(false, 'upload failed'); + }); + }); }); describe('#fetchRevisions', function() { @@ -166,6 +194,19 @@ describe('fastboot-app-server-aws plugin', function() { }); }); + it('respects `awsPrefix` when looking for revisions', function() { + let PREFIX = 'blog'; + return setupTestData({ awsPrefix: PREFIX }).then(() => { + context.config['fastboot-app-server-aws'].awsPrefix = PREFIX; + return plugin.fetchRevisions(context) + .then((data) => { + let revisions = data.revisions.map((d) => d.revision); + assert.deepEqual(revisions, ['12', '34', '56']); + assert.isTrue(data.revisions[1].active, 'revision 34 marked current'); + }); + }) + }); + it('does not fail when bucket is empty', function() { return cleanBucket() .then(() => { @@ -188,6 +229,19 @@ describe('fastboot-app-server-aws plugin', function() { }); }); + it('respects `awsPrefix` when fetching initial revisions', function() { + let PREFIX = 'blog'; + return setupTestData({ awsPrefix: PREFIX }).then(() => { + context.config['fastboot-app-server-aws'].awsPrefix = PREFIX; + return plugin.fetchInitialRevisions(context) + .then((data) => { + let revisions = data.initialRevisions.map((d) => d.revision); + assert.deepEqual(revisions, ['12', '34', '56']); + assert.isTrue(data.initialRevisions[1].active, 'revision 34 marked current'); + }); + }) + }); + it('does not fail when bucket is empty', function() { return cleanBucket() .then(() => { @@ -226,6 +280,35 @@ describe('fastboot-app-server-aws plugin', function() { assert.isTrue(false, "can't find manifest-file"); }); }); + + it('uploads the manifest with a key prefix if `awsPrefix` is set', function() { + let PREFIX = 'blog'; + + context.commandOptions = { + revision: '56' + }; + context.config['fastboot-app-server-aws'].awsPrefix = PREFIX; + + return plugin.activate(context) + .then(() => { + return get({ + Bucket: process.env.TEST_BUCKET, + Key: `${PREFIX}/fastboot-deploy-info.json` + }); + }) + .then((data) => { + let expected = { + bucket: process.env.TEST_BUCKET, + key: `${PREFIX}/dist-56.zip` + }; + let actual = JSON.parse(data.Body.toString()); + + assert.deepEqual(actual, expected, 'manifest file updated as expected'); + }) + .catch(() => { + assert.isTrue(false, "can't find manifest-file"); + }); + }); }); }); });