Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Large restructure of s3-index plugin #7

Merged
merged 1 commit into from
Sep 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 42 additions & 73 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
/* jshint node: true */
'use strict';
var path = require('path');
var fs = require('fs');
var Promise = require('ember-cli/lib/ext/promise');
var DeployPluginBase = require('ember-cli-deploy-plugin');
var S3 = require('./lib/s3');

var readFile = Promise.denodeify(fs.readFile);

module.exports = {
name: 'ember-cli-deploy-s3-index',

Expand All @@ -18,13 +15,10 @@ module.exports = {
defaultConfig: {
region: 'us-east-1',
filePattern: 'index.html',
currentRevisionIdentifier: "current.json",
prefix: '',
distDir: function(context) {
return context.distDir;
},
keyPrefix: function(context) {
return context.project.name() + ':index';
},
revisionKey: function(context) {
var revisionKey = context.revisionData && context.revisionData.revisionKey;
return context.commandOptions.revision || revisionKey;
Expand All @@ -36,88 +30,63 @@ module.exports = {
requiredConfig: ['accessKeyId', 'secretAccessKey', 'bucket'],

upload: function(context) {
var distDir = this.readConfig('distDir');
var filePattern = this.readConfig('filePattern');
var indexUploadKey = this._buildIndexUploadKey();
var filePath = path.join(distDir, filePattern);
var bucket = this.readConfig('bucket');
var prefix = this.readConfig('prefix');
var revisionKey = this.readConfig('revisionKey');
var distDir = this.readConfig('distDir');
var filePattern = this.readConfig('filePattern');
var filePath = path.join(distDir, filePattern);

this.log('trying to upload index to S3! ' + indexUploadKey);
var s3 = new S3({ plugin: this });
var options = {
bucket: bucket,
prefix: prefix,
filePattern: filePattern,
filePath: filePath,
revisionKey: revisionKey,
};

return this._readFileContents(filePath)
.then(s3.upload.bind(s3, indexUploadKey));
this.log('preparing to upload revision to S3 bucket `' + bucket + '`');

var s3 = new S3({ plugin: this });
return s3.upload(options);
},

activate: function(context) {
var bucket = this.readConfig('bucket');
var prefix = this.readConfig('prefix');
var revisionKey = this.readConfig('revisionKey');
var keyPrefix = this.readConfig('keyPrefix');
var filePattern = this.readConfig('filePattern');

if (revisionKey.indexOf(keyPrefix) === -1) {
revisionKey = this._buildIndexUploadKey();
}
var options = {
bucket: bucket,
prefix: prefix,
filePattern: filePattern,
revisionKey: revisionKey,
};

this.log('preparing to activate `' + revisionKey + '`');

return this._fetchRevisionsData()
.then(this._createRevisionsList)
.then(this._activateRevision.bind(this, revisionKey));
var s3 = new S3({ plugin: this });
return s3.activate(options);
},

fetchRevisions: function(context) {
return this._fetchRevisionsData();
},
var bucket = this.readConfig('bucket');
var prefix = this.readConfig('prefix');
var filePattern = this.readConfig('filePattern');

_fetchRevisionsData: function() {
var s3 = new S3({ plugin: this })
var options = {
bucket: bucket,
prefix: prefix,
filePattern: filePattern,
};

return s3.fetchRevisions()
var s3 = new S3({ plugin: this });
return s3.fetchRevisions(options)
.then(function(revisions) {
return { revisions: revisions };
});
},

_createRevisionsList: function(revisionsData) {
var revisions = revisionsData.revisions;

return revisions.map(function(r) { return r.revision; });
},

_activateRevision: function(revisionKey, availableRevisions) {
this.log('Activating revision `' + revisionKey + '`');

if (availableRevisions.indexOf(revisionKey) > -1) {
return this._overwriteCurrentIndex(revisionKey)
.then(this._updateCurrentRevisionPointer.bind(this, revisionKey));
} else {
return Promise.reject("REVISION NOT FOUND!"); // see how we should handle a pipeline failure
}
},

_buildIndexUploadKey: function() {
var keyPrefix = this.readConfig('keyPrefix');
var revisionKey = this.readConfig('revisionKey');

return keyPrefix+':'+revisionKey;
},

_readFileContents: function(path) {
return readFile(path)
.then(function(buffer) {
return buffer.toString();
context.revisions = revisions;
});
},

_updateCurrentRevisionPointer: function(newRevisionKey) {
var s3 = new S3({ plugin: this })
var currentRevisionIdentifier = this.readConfig('currentRevisionIdentifier');

return s3.upload(currentRevisionIdentifier, JSON.stringify({ revision: newRevisionKey }));
},

_overwriteCurrentIndex: function(newRevisionKey) {
var s3 = new S3({ plugin: this });
var bucket = this.readConfig('bucket');

return s3.overwriteCurrentIndex(newRevisionKey, bucket);
}
});

return new DeployPlugin();
Expand Down
166 changes: 72 additions & 94 deletions lib/s3.js
Original file line number Diff line number Diff line change
@@ -1,129 +1,107 @@
var AWS = require('aws-sdk');
var CoreObject = require('core-object');
var Promise = require('ember-cli/lib/ext/promise');
var fs = require('fs');
var path = require('path');
var readFile = Promise.denodeify(fs.readFile);

var headObject = function(client, params) {
return new Promise(function(resolve, reject) {
client.headObject(params, function(err, data) {
if (err && err.code === 'NotFound') {
return resolve();
}
else if (err) {
return reject(err);
}
else {
return resolve(data);
}
});
});
}

module.exports = CoreObject.extend({
init: function(options) {
var plugin = options.plugin;
var config = plugin.pluginConfig;
var readConfig = plugin.readConfig;

this._client = plugin.readConfig('s3Client') || new AWS.S3(config);
this._bucket = plugin.readConfig('bucket');
this._keyPrefix = plugin.readConfig('keyPrefix');
this._filePattern = plugin.readConfig('filePattern');
this._currentRevisionIdentifier = plugin.readConfig('currentRevisionIdentifier');
this._plugin = plugin;
this._client = plugin.readConfig('s3Client') || new AWS.S3(config);
},

fetchRevisions: function() {
return Promise.hash({
revisions: this._getBucketContents(),
current: this._getCurrentData()
})
.then(this._createRevisionDataFromList.bind(this));
},

upload: function(key, fileContent) {
var client = this._client;
upload: function(options) {
var client = this._client;
var plugin = this._plugin;
var bucket = options.bucket;
var acl = options.acl || 'public-read';
var key = path.join(options.prefix, options.filePattern + ":" + options.revisionKey);
var putObject = Promise.denodeify(client.putObject.bind(client));

var params = {
Bucket: this._bucket,
Bucket: bucket,
Key: key,
Body: fileContent,
ACL: acl,
ContentType: 'text/html',
CacheControl: 'max-age=0, no-cache'
}

return new Promise(function(resolve, reject) {
client.putObject(params, function(err, data) {
if (err) {
return reject(err);
} else {
return resolve(data);
}
});
return readFile(options.filePath).then(function(fileContents) {
params.Body = fileContents;
return putObject(params).then(function() {
plugin.log('✔ ' + key);
})
});
},

overwriteCurrentIndex: function(newRevisionKey) {
var client = this._client;
var bucket = this._bucket;
var copySource = encodeURIComponent(bucket+'/'+newRevisionKey);
activate: function(options) {
var plugin = this._plugin;
var client = this._client;
var bucket = options.bucket;
var acl = options.acl || 'public-read';
var revisionKey = path.join(options.prefix, options.filePattern + ":" + options.revisionKey);
var indexKey = path.join(options.prefix, options.filePattern);
var copySource = encodeURIComponent(path.join(bucket, revisionKey));
var copyObject = Promise.denodeify(client.copyObject.bind(client));

var params = {
Bucket: bucket,
CopySource: copySource,
Key: this._filePattern
Key: indexKey,
ACL: acl,
};

return new Promise(function(resolve, reject) {
client.copyObject(params, function(err, data) {
if (err) {
return reject(err);
} else {
return resolve(data);
}
});
});
},

_createRevisionDataFromList: function(data) {
const revisionsData = this._sortBucketContents(data.revisions).Contents;
const currentRevision = data.current;

return revisionsData.map(function(d) {
var revision = d.Key;

return { revision: revision, active: revision === currentRevision };
return this.fetchRevisions(options).then(function(revisions) {
var found = revisions.map(function(element) { return element.revision; }).indexOf(options.revisionKey);
if (found >= 0) {
return copyObject(params).then(function() {
plugin.log('✔ ' + revisionKey + " => " + indexKey);
})
} else {
return Promise.reject("REVISION NOT FOUND!"); // see how we should handle a pipeline failure
}
});
},

_sortBucketContents: function(data) {
data.Contents = data.Contents.sort(function(a, b) {
return new Date(b.LastModified) - new Date(a.LastModified);
});
return data;
},

_getBucketContents: function() {
var client = this._client;
var keyPrefix = this._keyPrefix;
var bucket = this._bucket;
fetchRevisions: function(options) {
var client = this._client;
var bucket = options.bucket;
var revisionPrefix = path.join(options.prefix, options.filePattern + ":");
var indexKey = path.join(options.prefix, options.filePattern);
var listObjects = Promise.denodeify(client.listObjects.bind(client));

return new Promise(function(resolve, reject) {
var params = { Bucket: bucket, Prefix: keyPrefix };

client.listObjects(params, function(err, data) {
if (err) {
return reject(err);
} else {
return resolve(data);
}
return Promise.hash({
revisions: listObjects({ Bucket: bucket, Prefix: revisionPrefix }),
current: headObject(client, { Bucket: bucket, Key: indexKey }),
})
.then(function(data) {
return data.revisions.Contents.sort(function(a, b) {
return new Date(b.LastModified) - new Date(a.LastModified);
}).map(function(d) {
var revision = d.Key.substr(revisionPrefix.length);
var active = data.current && d.ETag === data.current.ETag;
return { revision: revision, timestamp: d.LastModified, active: active };
});
});
},

_getCurrentData: function() {
var client = this._client;
var bucket = this._bucket;
var currentRevisionIdentifier = this._currentRevisionIdentifier;

return new Promise(function(resolve, reject) {
var params = { Bucket: bucket, Key: currentRevisionIdentifier };

client.getObject(params, function(err, data) {
if (err && err.code === 'NoSuchKey') {
return resolve();
}
else if (err) {
return reject(err);
}
else {
var json = JSON.parse(data.Body.toString('utf8'));

return resolve(json.revision);
}
});
});
}
});