Repository: motdotla/node-lambda (js)

Issue:
Feature request: upload to s3 and deploy from bucket
Same thing to what it's doing now, but instead of sending the zip directly to AWS lambda, it first uploads it to a predefined bucket/key(prefix) and deploys from s3. 

Advantage: size limits seem to be much higher when deploying from s3, 250meg zips, vs direct zip upload which is limited to 50megs. 

Pull request description:
Feature upload to s3 and deploy from bucket
fixes #444 

Patch:
diff --git a/README.md b/README.md
index 7ac59e2..9a56cdf 100644
--- a/README.md
+++ b/README.md
@@ -93,6 +93,8 @@ SRC_DIRECTORY              // (default: '')
 DEPLOY_TIMEOUT             // (default: '120000')
 DOCKER_IMAGE               // (default: '')
 DEPLOY_ZIPFILE             // (default: '')
+DEPLOY_S3_BUCKET           // (default: '')
+DEPLOY_S3_KEY              // (default: '')
 AWS_DLQ_TARGET_ARN         // (default: not set)
 ```
 
@@ -188,6 +190,8 @@ Options:
   -D, --prebuiltDirectory []          Prebuilt directory
   -T, --deployTimeout [120000]        Deploy Timeout
   -z, --deployZipfile []              Deploy zipfile
+  -B, --deployS3Bucket []             Use S3 to deploy. Specify it with `--deployS3Key`. (default: )
+  -O, --deployS3Key []                Use S3 to deploy. Specify it with `--deployS3Bucket`. (default: )
   -y, --proxy []                      Proxy server
 ```
 
diff --git a/bin/node-lambda b/bin/node-lambda
index e7af3dd..25b4b26 100755
--- a/bin/node-lambda
+++ b/bin/node-lambda
@@ -46,6 +46,8 @@ const SRC_DIRECTORY = process.env.SRC_DIRECTORY || ''
 const DEPLOY_TIMEOUT = process.env.DEPLOY_TIMEOUT || 120000
 const DOCKER_IMAGE = process.env.DOCKER_IMAGE || ''
 const DEPLOY_ZIPFILE = process.env.DEPLOY_ZIPFILE || ''
+const DEPLOY_S3_BUCKET = process.env.DEPLOY_S3_BUCKET || ''
+const DEPLOY_S3_KEY = process.env.DEPLOY_S3_KEY || ''
 const AWS_KMS_KEY_ARN = process.env.AWS_KMS_KEY_ARN || ''
 const AWS_DLQ_TARGET_ARN = (() => {
   // You can clear the setting by passing an empty string
@@ -101,6 +103,10 @@ program
   .option('-D, --prebuiltDirectory [PREBUILT_DIRECTORY]', 'Prebuilt directory', PREBUILT_DIRECTORY)
   .option('-T, --deployTimeout [DEPLOY_TIMEOUT]', 'Deploy Timeout', DEPLOY_TIMEOUT)
   .option('-z, --deployZipfile [DEPLOY_ZIPFILE]', 'Deploy zipfile', DEPLOY_ZIPFILE)
+  .option('-B, --deployS3Bucket [DEPLOY_S3_BUCKET]',
+    'Use S3 to deploy. Specify it with `--deployS3Key`.', DEPLOY_S3_BUCKET)
+  .option('-O, --deployS3Key [DEPLOY_S3_KEY]',
+    'Use S3 to deploy. Specify it with `--deployS3Bucket`.', DEPLOY_S3_KEY)
   .option('-y, --proxy [PROXY]', 'Proxy server', PROXY)
   .action((prg) => lambda.deploy(prg))
 
diff --git a/lib/main.js b/lib/main.js
index aa3090a..420bcd0 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -176,14 +176,19 @@ so you can easily test run multiple events.
     })
   }
 
+  _isUseS3 (program) {
+    return program.deployS3Bucket != null &&
+      program.deployS3Key != null &&
+      program.deployS3Bucket.length > 0 &&
+      program.deployS3Key.length > 0
+  }
+
   _params (program, buffer) {
     const params = {
       FunctionName: program.functionName +
         (program.environment ? '-' + program.environment : '') +
         (program.lambdaVersion ? '-' + program.lambdaVersion : ''),
-      Code: {
-        ZipFile: buffer
-      },
+      Code: {},
       Handler: program.handler,
       Role: program.role,
       Runtime: program.runtime,
@@ -212,6 +217,15 @@ so you can easily test run multiple events.
       }
     }
 
+    if (this._isUseS3(program)) {
+      params.Code = {
+        S3Bucket: program.deployS3Bucket,
+        S3Key: program.deployS3Key
+      }
+    } else {
+      params.Code = { ZipFile: buffer }
+    }
+
     // Escape characters that is not allowed by AWS Lambda
     params.FunctionName = params.FunctionName.replace(/[^a-zA-Z0-9-_]/g, '_')
 
@@ -477,12 +491,12 @@ so you can easily test run multiple events.
   }
 
   _uploadExisting (lambda, params) {
+    const _params = Object.assign({
+      'FunctionName': params.FunctionName,
+      'Publish': params.Publish
+    }, params.Code)
     return new Promise((resolve, reject) => {
-      const request = lambda.updateFunctionCode({
-        'FunctionName': params.FunctionName,
-        'ZipFile': params.Code.ZipFile,
-        'Publish': params.Publish
-      }, (err) => {
+      const request = lambda.updateFunctionCode(_params, (err) => {
         if (err) return reject(err)
 
         lambda.updateFunctionConfiguration({
@@ -765,43 +779,67 @@ they may not work as expected in the Lambda environment.
     })
   }
 
-  _deployToRegion (program, params, region) {
-    console.log('=> Reading event source file to memory')
-    const eventSourceList = this._eventSourceList(program)
+  _s3PutObject (program, region, buffer) {
+    this._awsConfigUpdate(program, region)
+    const s3 = new aws.S3()
+    return new Promise((resolve, reject) => {
+      const params = {
+        Body: buffer,
+        Bucket: program.deployS3Bucket,
+        Key: program.deployS3Key
+      }
+      console.log(`=> Uploading zip file to S3 ${region}`)
+      s3.putObject(params, (err, data) => {
+        if (err) reject(err)
+        resolve(data)
+      })
+    })
+  }
 
-    return Promise.resolve().then(() => {
-      console.log('=> Uploading zip file to AWS Lambda ' + region + ' with parameters:')
-      console.log(params)
+  _awsConfigUpdate (program, region) {
+    const awsSecurity = { region: region }
 
-      const awsSecurity = { region: region }
+    if (program.profile) {
+      aws.config.credentials = new aws.SharedIniFileCredentials({
+        profile: program.profile
+      })
+    } else {
+      awsSecurity.accessKeyId = program.accessKey
+      awsSecurity.secretAccessKey = program.secretKey
+    }
 
-      if (program.profile) {
-        aws.config.credentials = new aws.SharedIniFileCredentials({
-          profile: program.profile
-        })
-      } else {
-        awsSecurity.accessKeyId = program.accessKey
-        awsSecurity.secretAccessKey = program.secretKey
-      }
+    if (program.sessionToken) {
+      awsSecurity.sessionToken = program.sessionToken
+    }
 
-      if (program.sessionToken) {
-        awsSecurity.sessionToken = program.sessionToken
-      }
+    if (program.deployTimeout) {
+      aws.config.httpOptions.timeout = parseInt(program.deployTimeout)
+    }
 
-      if (program.deployTimeout) {
-        aws.config.httpOptions.timeout = parseInt(program.deployTimeout)
-      }
+    if (program.proxy) {
+      aws.config.httpOptions.agent = proxy(program.proxy)
+    }
 
-      if (program.proxy) {
-        aws.config.httpOptions.agent = proxy(program.proxy)
-      }
+    if (program.endpoint) {
+      aws.config.endpoint = program.endpoint
+    }
 
-      if (program.endpoint) {
-        aws.config.endpoint = program.endpoint
-      }
+    aws.config.update(awsSecurity)
+  }
+
+  _deployToRegion (program, params, region) {
+    console.log('=> Reading event source file to memory')
+    const eventSourceList = this._eventSourceList(program)
 
-      aws.config.update(awsSecurity)
+    return Promise.resolve().then(() => {
+      if (!this._isUseS3(program)) {
+        console.log(`=> Uploading zip file to AWS Lambda ${region} with parameters:`)
+      } else {
+        console.log(`=> Uploading AWS Lambda ${region} with parameters:`)
+      }
+      console.log(params)
 
+      this._awsConfigUpdate(program, region)
       const lambda = new aws.Lambda({ apiVersion: '2015-03-31' })
       const scheduleEvents = new ScheduleEvents(aws)
       const s3Events = new S3Events(aws)
@@ -817,7 +855,7 @@ they may not work as expected in the Lambda environment.
         }).then((existingEventSourceList) => {
           return Promise.all([
             this._uploadExisting(lambda, params).then((results) => {
-              console.log('=> Zip file(s) done uploading. Results follow: ')
+              console.log('=> Done uploading. Results follow: ')
               console.log(results)
               return results
             }).then(results => {
@@ -850,7 +888,7 @@ they may not work as expected in the Lambda environment.
       }).catch(() => {
         // Function does not exist
         return this._uploadNew(lambda, params).then((results) => {
-          console.log('=> Zip file(s) done uploading. Results follow: ')
+          console.log('=> Done uploading. Results follow: ')
           console.log(results)
 
           return Promise.all([
@@ -899,6 +937,11 @@ they may not work as expected in the Lambda environment.
     const regions = program.region.split(',')
     return this._archive(program).then((buffer) => {
       console.log('=> Reading zip file to memory')
+      if (this._isUseS3(program)) {
+        return this._s3PutObject(program, regions[0], buffer)
+      }
+      return buffer
+    }).then((buffer) => {
       const params = this._params(program, buffer)
 
       return Promise.all(regions.map((region) => {


Test patch:
diff --git a/test/main.js b/test/main.js
index 9700772..b1eab77 100644
--- a/test/main.js
+++ b/test/main.js
@@ -100,6 +100,9 @@ const _mockSetting = () => {
   awsMock.mock('S3', 'putBucketNotificationConfiguration', (params, callback) => {
     callback(null, {})
   })
+  awsMock.mock('S3', 'putObject', (params, callback) => {
+    callback(null, {'test': 'putObject'})
+  })
 
   Object.keys(lambdaMockSettings).forEach((method) => {
     awsMock.mock('Lambda', method, (params, callback) => {
@@ -171,6 +174,26 @@ describe('lib/main', function () {
     })
   })
 
+  describe('_isUseS3', () => {
+    it('=== true', () => {
+      assert.isTrue(lambda._isUseS3({
+        deployS3Bucket: 'bucket',
+        deployS3Key: 'key'
+      }))
+    })
+
+    it('=== false', () => {
+      [
+        {},
+        {deployS3Bucket: '', deployS3Key: ''},
+        {deployS3Bucket: 'bucket'},
+        {deployS3Key: 'key'}
+      ].forEach((params) => {
+        assert.isFalse(lambda._isUseS3(params), params)
+      })
+    })
+  })
+
   describe('_params', () => {
     // http://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-FunctionName
     const functionNamePattern =
@@ -259,6 +282,30 @@ describe('lib/main', function () {
       assert.isNull(params.TracingConfig.Mode)
     })
 
+    describe('S3 deploy', () => {
+      it('Do not use S3 deploy', () => {
+        const params = lambda._params(program, 'Buffer')
+        assert.deepEqual(
+          params.Code,
+          { ZipFile: 'Buffer' }
+        )
+      })
+
+      it('Use S3 deploy', () => {
+        const params = lambda._params(Object.assign({
+          deployS3Bucket: 'S3Bucket-value',
+          deployS3Key: 'S3Key-value'
+        }, program), 'Buffer')
+        assert.deepEqual(
+          params.Code,
+          {
+            S3Bucket: 'S3Bucket-value',
+            S3Key: 'S3Key-value'
+          }
+        )
+      })
+    })
+
     describe('params.Publish', () => {
       describe('boolean', () => {
         it('If true, it is set to true', () => {
@@ -1181,6 +1228,20 @@ describe('lib/main', function () {
     })
   })
 
+  describe('Lambda.prototype._s3PutObject()', () => {
+    it('simple test with mock', () => {
+      disableLog()
+      const params = {
+        deployS3Bucket: 'test',
+        deployS3Key: 'test'
+      }
+      return lambda._s3PutObject(params, 'us-east-1', 'buffer').then((result) => {
+        enableLog()
+        assert.deepEqual(result, {'test': 'putObject'})
+      })
+    })
+  })
+
   describe('Lambda.prototype._deployToRegion()', () => {
     it('simple test with mock', () => {
       const params = lambda._params(program, null)