Repository: motdotla/node-lambda
Pull request #454 (2018-08-01 07:54:42)

Add class for uploading deploy package to S3
* Add a class to upload to S3 in preparation for #453.
* Implement the process of uploading to `lib/main.js` to S3 when merged.

abetomo: I sorted the region.

Patch:
diff --git a/lib/s3_deploy.js b/lib/s3_deploy.js
new file mode 100644
index 0000000..530b7d6
--- /dev/null
+++ b/lib/s3_deploy.js
@@ -0,0 +1,111 @@
+'use strict'
+
+const crypto = require('crypto')
+// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#createBucket-property
+const S3_LOCATION_POSSIBLE_VALUES = [
+  'EU',
+  'ap-northeast-1',
+  'ap-south-1',
+  'ap-southeast-1',
+  'ap-southeast-2',
+  'cn-north-1',
+  'eu-central-1',
+  'eu-west-1',
+  'sa-east-1',
+  'us-west-1',
+  'us-west-2'
+]
+
+class S3Deploy {
+  constructor (aws, region) {
+    // Authenticated `aws` object in `lib/main.js`
+    this.s3 = new aws.S3({
+      region: region,
+      apiVersion: '2006-03-01'
+    })
+  }
+
+  _md5 (str) {
+    return crypto
+      .createHash('md5')
+      .update(str, 'utf8')
+      .digest('hex')
+  }
+
+  _bucketName (params) {
+    return [
+      params.FunctionName,
+      params.region,
+      this._md5(params.FunctionName + params.region)
+    ]
+      .join('-')
+      .substr(0, 63)
+  }
+
+  _s3Key (params) {
+    return `deploy-package-${params.FunctionName}.zip`
+  }
+
+  _getS3Location (region) {
+    return S3_LOCATION_POSSIBLE_VALUES.includes(region) ? region : null
+  }
+
+  _createBucket (params) {
+    const _params = {
+      Bucket: params.bucketName
+    }
+    const s3Locatoin = this._getS3Location(params.region)
+    if (s3Locatoin != null) {
+      _params.CreateBucketConfiguration = {
+        LocationConstraint: s3Locatoin
+      }
+    }
+    return new Promise((resolve, reject) => {
+      this.s3.createBucket(_params, (err, data) => {
+        if (err) {
+          // Ignored created
+          if (err.code === 'BucketAlreadyOwnedByYou') return resolve({})
+          return reject(err)
+        }
+        resolve(data)
+      })
+    })
+  }
+
+  _putObject (params, buffer) {
+    const _params = {
+      Body: buffer,
+      Bucket: params.bucketName,
+      Key: params.s3Key
+    }
+    return new Promise((resolve, reject) => {
+      this.s3.putObject(_params, (err, data) => {
+        if (err) reject(err)
+        resolve(data)
+      })
+    })
+  }
+
+  putPackage (params, region, buffer) {
+    const _params = Object.assign({region: region}, params)
+    _params.bucketName = this._bucketName(_params)
+    _params.s3Key = this._s3Key(_params)
+
+    return this._createBucket(_params).then((result) => {
+      if (result.Location != null) {
+        console.log('=> S3 Bucket created:')
+        console.log(`===> ${_params.bucketName}`)
+      }
+      return this._putObject(_params, buffer)
+    }).then((result) => {
+      console.log('=> Deploy the zip file to S3:')
+      console.log(`===> ${_params.bucketName}/${_params.s3Key}`)
+      return {
+        S3Bucket: _params.bucketName,
+        S3Key: _params.s3Key
+      }
+    })
+  }
+}
+
+module.exports = S3Deploy


Test patch:
diff --git a/test/s3_deploy.js b/test/s3_deploy.js
new file mode 100644
index 0000000..a4f1d3a
--- /dev/null
+++ b/test/s3_deploy.js
@@ -0,0 +1,107 @@
+'use strict'
+
+const {assert} = require('chai')
+const path = require('path')
+const aws = require('aws-sdk-mock')
+aws.setSDK(path.resolve('node_modules/aws-sdk'))
+const S3Deploy = require('../lib/s3_deploy')
+
+const mockResponse = {
+  createBucket: {'Location': 'createBucket'},
+  putObject: {'ETag': 'putObject'}
+}
+
+var s3Deploy = null
+
+/* global describe, it, before, after */
+describe('lib/s3_deploy', () => {
+  before(() => {
+    aws.mock('S3', 'putObject', (params, callback) => {
+      callback(null, mockResponse.putObject)
+    })
+    aws.mock('S3', 'createBucket', (params, callback) => {
+      callback(null, mockResponse.createBucket)
+    })
+
+    s3Deploy = new S3Deploy(require('aws-sdk'))
+  })
+
+  after(() => {
+    aws.restore('S3')
+  })
+
+  describe('_md5', () => {
+    it('md5("hoge") === "ea703e7aa1efda0064eaa507d9e8ab7e"', () => {
+      assert.equal(s3Deploy._md5('hoge'), 'ea703e7aa1efda0064eaa507d9e8ab7e')
+    })
+  })
+
+  describe('_bucketName', () => {
+    it('FunctionName + region + md5()', () => {
+      const params = {
+        FunctionName: 'node-lambda-name',
+        region: 'test_region'
+      }
+      assert.equal(
+        s3Deploy._bucketName(params),
+        'node-lambda-name-test_region-aac849d59d2be828b793609e03d8241d'
+      )
+    })
+  })
+
+  describe('_s3Key', () => {
+    it('"deploy-package" + FunctionName + ".zip"', () => {
+      const params = {FunctionName: 'node-lambda-name'}
+      assert.equal(
+        s3Deploy._s3Key(params),
+        'deploy-package-node-lambda-name.zip'
+      )
+    })
+  })
+
+  describe('_getS3Location', () => {
+    it('is null', () => {
+      assert.isNull(s3Deploy._getS3Location('hoge'))
+    })
+
+    it('=== "ap-southeast-1"', () => {
+      assert.equal(s3Deploy._getS3Location('ap-southeast-1'), 'ap-southeast-1')
+    })
+  })
+
+  describe('_createBucket', () => {
+    it('using mock', () => {
+      const params = {
+        bucketName: 'node-lambda-test-bucket',
+        region: 'ap-southeast-1'
+      }
+      return s3Deploy._createBucket(params).then((result) => {
+        assert.deepEqual(result, mockResponse.createBucket)
+      })
+    })
+  })
+
+  describe('_putObject', () => {
+    it('using mock', () => {
+      const params = {
+        bucketName: 'node-lambda-test-bucket',
+        s3Key: 'testKey'
+      }
+      return s3Deploy._putObject(params, 'buffer').then((result) => {
+        assert.deepEqual(result, mockResponse.putObject)
+      })
+    })
+  })
+
+  describe('putPackage', () => {
+    it('using mock', () => {
+      const params = {FunctionName: 'node-lambda-test-bucket-20180801'}
+      return s3Deploy.putPackage(params, 'ap-southeast-1', 'buffer').then((result) => {
+        assert.deepEqual(result, {
+          S3Bucket: 'node-lambda-test-bucket-20180801-ap-southeast-1-6c696118a497125',
+          S3Key: 'deploy-package-node-lambda-test-bucket-20180801.zip'
+        })
+      })
+    })
+  })
+})