skipper-better-s3
Version:
A better approach to Amazon S3 file management for Skipper
251 lines (224 loc) • 9.04 kB
JavaScript
/**
* skipper-better-s3
*
* @author Robert Rossmann <rr.rossmann@me.com>
* @copyright 2015 Robert Rossmann
* @license http://choosealicense.com/licenses/bsd-3-clause BSD-3-Clause License
*/
const merge = require('semantic-merge')
const local = require('local-scope')()
const Action = require('./action')
module.exports = class Adapter {
/**
* Parse user-provided options into our own internal structure expected by the service
*
* @method Adapter.parseOptions
* @static
*
* @param {Object} opts Options object to be parsed
* @param {Object?} defaults Default configuration to be used for missing properties
* @return {Object} The parsed object
*/
static parseOptions(opts, defaults) {
// Normalise the hell out of it...
opts = opts || {}
defaults = defaults || {}
defaults.service = defaults.service || {}
defaults.request = defaults.request || {}
const config = {
service: {
accessKeyId: opts.key || defaults.service.accessKeyId,
secretAccessKey: opts.secret || defaults.service.secretAccessKey,
apiVersion: '2006-03-01',
region: opts.region || defaults.service.region,
},
request: {
Bucket: opts.bucket || defaults.request.Bucket,
},
options: opts.s3options || {},
// This should only be used when uploading a non-http stream - dirname is usually used
// by Skipper directly and we receive the full path from it when we receive the stream
dirname: opts.dirname || null,
}
config.request = merge(config.request)
.and(opts.s3params || {})
.excluding('s3options')
.into({})
config.service = merge(config.service)
.and(opts.s3config || {})
.into({})
return config
}
/**
* Create a new Adapter instance
*
* @classdesc The Adapter implements the standard interface that Skipper expects from
* each adapter implementation.
*
* @constructor Adapter
*
* @param {Object?} globals Global options to be used for all subsequent requests
* @param {String?} globals.key S3 access key
* @param {String?} globals.secret S3 access secret
* @param {String?} globals.bucket S3 bucket to use for all requests
* @return {Adapter}
*/
constructor(globals) {
local(this).globals = Adapter.parseOptions(globals)
}
/**
* Callback for the `ls()` method
*
* @callback lsDone
* @memberof Adapter
*
* @param {Error?} err Optional error
* @param {String[]?} data An array of string paths
*/
/**
* Get the contents at given path
*
* @method Adapter#ls
*
* @param {String} dirname The path where to perform the listing
* @param {Adapter.lsDone} done Callback function
* @return {void}
*/
ls(dirname, done) {
return new Action(local(this).globals).list(dirname, done)
}
/**
* Callback for the `rm()` method
*
* @callback rmDone
* @memberof Adapter
*
* @param {Error?} err Optional error
* @param {Object?} data Additional data about the request, as returned by S3.
* @see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObject-property
*/
/**
* Remove an item at given path
*
* @method Adapter#rm
*
* @param {String} fd The item to be removed
* @param {Adapter.rmDone} done Callback function
* @return {void}
*/
rm(fd, done) {
return new Action(local(this).globals).remove(fd, done)
}
/**
* Callback for the `read()` method
*
* @callback readDone
* @memberof Adapter
*
* @param {Error?} err Optional error
* @param {Buffer?} body The item's contents, as buffer
* @see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property
*/
/**
* Read an item at given path
*
* @method Adapter#read
*
* @param {String} fd The item to be read
* @param {Adapter.readDone?} done Optional callback. If provided, the item's contents will
* be passed to it. If omitted, a stream will be returned
* instead which you can consume.
* @return {ReadableStream|void}
*/
read(fd, done) {
const action = new Action(local(this).globals)
return done
? action.download(fd, done)
: action.stream(fd)
}
/**
* Callback for the `url()` method
*
* @callback urlDone
* @memberof Adapter
*
* @param {Error?} err Optional error
* @param {String?} url The signed URL
* @see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
*/
/**
* Generate a signed URL for the given S3 operation
*
* @method Adapter#url
*
* @param {String} operation A valid S3 operation identifier
* @param {Object?} opts Optional additional configuration
* @param {String?} opts.key S3 access key
* @param {String?} opts.secret S3 access secret
* @param {String?} opts.bucket S3 bucket to use for this operation
* @param {Object?} opts.s3params Optional object to provide additional options
* for the signed request. The options can be
* anything that is supported by the given S3's
* operation.
* @param {Adapter.urlDone?} done Optional callback. If omitted, you must already
* have valid credentials saved in this service.
* @return {String|void} If called synchronously, will return the signed
* URL for the specified operation.
* @see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property
*/
url(operation, opts, done) {
opts = Adapter.parseOptions(opts, local(this).globals)
return new Action(opts).url(operation, done)
}
/**
* Upload progress handler
*
* This function, if provided, will be called periodically with status information about the
* ongoing upload request.
*
* @callback onProgress
* @memberof Adapter
*
* @param {Object} data Object with current progress information
* @param {String} data.id Unique ID for the current upload request
* @param {String} data.fd Path to the file being written
* @param {String} data.name Name of the file being written
* @param {Integer} data.written Number of bytes written so far
* @param {Integer?} data.total If known, total number of bytes to be written
* @param {Integer} data.percent Percentage progress. If the total amount of bytes to be
* written is unknown, this will be 0.
*/
/**
* Upload a stream of files
*
* @method Adapter#receive
*
* @param {Object?} opts Optional additional configuration
* @param {String?} opts.key S3 access key
* @param {String?} opts.secret S3 access secret
* @param {String?} opts.bucket S3 bucket to upload to
* @param {Adapter.onProgress?} opts.onProgress Function to be called repeatedly as the
* upload progresses
* @param {Object?} opts.s3params Optional object to provide additional
* options for the upload request. The options
* can be anything that is supported by S3's
* `upload()` method.
* @param {Object?} opts.s3options Optional object to further configure the
* upload request. This is passed directly to
* the S3's `upload()` method as second
* parameter.
*
* @see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
* @return {Uploader}
*/
receive(opts) {
// Normalise...
opts = opts || {}
opts = merge(Adapter.parseOptions(opts, local(this).globals))
// Allow the special onProgress event listener to be passed to the upload request
.and({ request: { onProgress: opts.onProgress } })
.recursively.into({})
return new Action(opts).upload()
}
}