diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..49dafa5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true ; top-most EditorConfig file + +; Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..ec951a5 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + "extends": "airbnb", + "plugins": [], + "rules": { + "func-names": "off", + "strict": "off", + "prefer-rest-params": "off", + "react/require-extension" : "off", + "import/no-extraneous-dependencies" : "off", + "no-console": "off" + }, + "env": { + "mocha": true, + "jest": true + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f356e3 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Serverless S3 Sync + +> A plugin to sync local directories and S3 prefixes for Serverless Framework. + +## Install + +Run `npm install` in your Serverless project. + +```sh +$ npm install --save serverless-s3-sync +``` + +Add the plugin to your serverless.yml file + +```yaml +plugins: + - serverless-s3-sync +``` + +## Setup + +```yaml +custom: + s3Sync: + - bucketName: my-static-site-assets # required + bucketPrefix: assets/ # optional + localDir: dist/assets # required + - bucketName: my-other-site + localDir: path/to/other-site + +resources: + Resources: + AssetsBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: my-static-site-assets + OtherSiteBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: my-other-site +``` + +## Usage + +Run `sls deploy`, local directories and S3 prefixes are synced. + +Run `sls remove`, S3 objects in S3 prefixes are removed. diff --git a/index.js b/index.js new file mode 100644 index 0000000..ce9aba3 --- /dev/null +++ b/index.js @@ -0,0 +1,123 @@ +'use strict'; + +const BbPromise = require('bluebird'); +const AWS = require('aws-sdk'); +const s3 = require('@monolambda/s3'); +const client = s3.createClient({ + s3Client: new AWS.S3({}) +}); +const chalk = require('chalk'); + +class ServerlessS3Sync { + constructor(serverless, options) { + this.serverless = serverless; + this.options = options || {}; + this.s3Sync = this.serverless.service.custom.s3Sync; + this.servicePath = this.serverless.service.serverless.config.servicePath; + + this.hooks = { + 'after:deploy:deploy': () => BbPromise.bind(this).then(this.sync), + 'before:remove:remove': () => BbPromise.bind(this).then(this.clear) + }; + } + + sync() { + if (!Array.isArray(this.s3Sync)) { + return Promise.resolve(); + } + const cli = this.serverless.cli; + const messagePrefix = 'S3 Sync: '; + cli.consoleLog(`${messagePrefix}${chalk.yellow('Syncing directories and S3 prefixes...')}`); + const servicePath = this.servicePath; + const promises = this.s3Sync.map((s) => { + if (!s.hasOwnProperty('bucketPrefix')) { + s.bucketPrefix = ''; + } + if (!s.bucketName || !s.localDir) { + throw 'Invalid custom.s3Sync'; + } + return new Promise((resolve) => { + const params = { + maxAsyncS3: 5, + localDir: servicePath + '/' + s.localDir, + deleteRemoved: true, + followSymlinks: false, + s3Params: { + Bucket: s.bucketName, + Prefix: s.bucketPrefix + } + }; + const uploader = client.uploadDir(params); + uploader.on('error', (err) => { + throw err; + }); + let percent = 0; + uploader.on('progress', () => { + if (uploader.progressTotal === 0) { + return; + } + let current = Math.round(uploader.progressAmount/uploader.progressTotal * 10) * 10; + if (current > percent) { + percent = current; + cli.printDot(); + } + }); + uploader.on('end', () => { + resolve('done'); + }); + }); + }); + return Promise.all(promises) + .then(() => { + cli.printDot(); + cli.consoleLog(''); + cli.consoleLog(`${messagePrefix}${chalk.yellow('Synced.')}`); + }); + } + + clear() { + if (!Array.isArray(this.s3Sync)) { + return Promise.resolve(); + } + const cli = this.serverless.cli; + const messagePrefix = 'S3 Sync: '; + cli.consoleLog(`${messagePrefix}${chalk.yellow('Removing S3 objects...')}`); + const promises = this.s3Sync.map((s) => { + if (!s.hasOwnProperty('bucketPrefix')) { + s.bucketPrefix = ''; + } + return new Promise((resolve) => { + const params = { + Bucket: s.bucketName, + Prefix: s.bucketPrefix + }; + const uploader = client.deleteDir(params); + uploader.on('error', (err) => { + throw err; + }); + let percent = 0; + uploader.on('progress', () => { + if (uploader.progressTotal === 0) { + return; + } + let current = Math.round(uploader.progressAmount/uploader.progressTotal * 10) * 10; + if (current > percent) { + percent = current; + cli.printDot(); + } + }); + uploader.on('end', () => { + resolve('done'); + }); + }); + }); + return Promise.all(promises) + .then(() => { + cli.printDot(); + cli.consoleLog(''); + cli.consoleLog(`${messagePrefix}${chalk.yellow('Removed.')}`); + }); + } +} + +module.exports = ServerlessS3Sync; diff --git a/package.json b/package.json new file mode 100644 index 0000000..7420ea6 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "serverless-s3-sync", + "version": "0.0.1", + "description": "A plugin to sync local directories and S3 prefixes for Serverless Framework.", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "serverless", + "s3" + ], + "author": "k1LoW (https://github.com/k1LoW)", + "license": "MIT", + "dependencies": { + "@monolambda/s3": "^1.0.1", + "bluebird": "^3.5.0", + "chalk": "^2.0.1" + } +}