Repository: motdotla/node-lambda
Pull request #356 (2017-07-14 03:02:10)

Add the ability to set KMSKeyArn to a Lambda function
The AWS SDK has the ability to set the KMSKeyArn when creating a Lambda function.
https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html

This PR adds the ability to set the KMSKeyArn. By default the value is not included in the params.


Patch:
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..6f2925a
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,15 @@
+environment:
+  matrix:
+    - nodejs_version: '8'
+    - nodejs_version: '7'
+    - nodejs_version: '6'
+    - nodejs_version: '5'
+    - nodejs_version: '4'
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - npm install
+test_script:
+  - node --version
+  - npm --version
+  - npm test
+build: off
diff --git a/bin/node-lambda b/bin/node-lambda
index 088a603..9e91b8a 100755
--- a/bin/node-lambda
+++ b/bin/node-lambda
@@ -43,6 +43,7 @@ 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 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
   // when executing updateFunctionConfiguration
@@ -76,6 +77,7 @@ program
   .option('-b, --vpcSubnets [' + AWS_VPC_SUBNETS + ']', 'Lambda Function VPC Subnets', AWS_VPC_SUBNETS)
   .option('-g, --vpcSecurityGroups [' + AWS_VPC_SECURITY_GROUPS + ']', 'Lambda VPC Security Group',
     AWS_VPC_SECURITY_GROUPS)
+  .option('-K, --kmsKeyArn [' + AWS_KMS_KEY_ARN + ']', 'Lambda KMS Key ARN', AWS_KMS_KEY_ARN)
   .option('-Q, --deadLetterConfigTargetArn [' + AWS_DLQ_TARGET_ARN + ']', 'Lambda DLQ resource',
     AWS_DLQ_TARGET_ARN)
   .option('-T, --tracingConfig [' + AWS_TRACING_CONFIG + ']', 'Lambda tracing settings',
diff --git a/lib/cloudwatch_logs.js b/lib/cloudwatch_logs.js
new file mode 100644
index 0000000..5fc0dd3
--- /dev/null
+++ b/lib/cloudwatch_logs.js
@@ -0,0 +1,54 @@
+'use strict'
+
+class CloudWatchLogs {
+  constructor (aws) {
+    // Authenticated `aws` object in `lib/main.js`
+    this.lambda = new aws.Lambda({
+      apiVersion: '2015-03-31'
+    })
+    this.cloudwatchlogs = new aws.CloudWatchLogs({
+      apiVersion: '2014-03-28'
+    })
+  }
+
+  _logGroupName (params) {
+    return `/aws/lambda/${params.FunctionName}`
+  }
+
+  _createLogGroup (params) {
+    return new Promise((resolve, reject) => {
+      this.cloudwatchlogs.createLogGroup({
+        logGroupName: this._logGroupName(params)
+      }, (err, data) => {
+        if (err) {
+          if (err.code === 'ResourceAlreadyExistsException') {
+            // If it exists it will result in an error but there is no problem.
+            return resolve({})
+          }
+          return reject(err)
+        }
+
+        resolve(data)
+      })
+    })
+  }
+
+  _putRetentionPolicy (params) {
+    return new Promise((resolve, reject) => {
+      this.cloudwatchlogs.putRetentionPolicy({
+        logGroupName: this._logGroupName(params),
+        retentionInDays: params.retentionInDays
+      }, (err, data) => {
+        if (err) return reject(err)
+        resolve(data)
+      })
+    })
+  }
+
+  setLogsRetentionPolicy (params) {
+    return this._createLogGroup(params)
+      .then(() => this._putRetentionPolicy(params))
+  }
+}
+
+module.exports = CloudWatchLogs
diff --git a/lib/main.js b/lib/main.js
index a4cb32d..49e900f 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -176,6 +176,7 @@ Lambda.prototype._params = (program, buffer) => {
     Environment: {
       Variables: null
     },
+    KMSKeyArn: program.kmsKeyArn,
     DeadLetterConfig: {
       TargetArn: null
     },
@@ -473,6 +474,7 @@ Lambda.prototype._uploadExisting = (lambda, params) => {
         'Runtime': params.Runtime,
         'VpcConfig': params.VpcConfig,
         'Environment': params.Environment,
+        'KMSKeyArn': params.KMSKeyArn,
         'DeadLetterConfig': params.DeadLetterConfig,
         'TracingConfig': params.TracingConfig
       }, (err, data) => {
diff --git a/package.json b/package.json
index 1fe5023..15de49e 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
   },
   "repository": {
     "type": "git",
-    "url": "git://github.com/rebelmail/node-lambda.git"
+    "url": "git://github.com/motdotla/node-lambda.git"
   },
   "keywords": [
     "lambda",


Test patch:
diff --git a/test/cloudwatch_logs.js b/test/cloudwatch_logs.js
new file mode 100644
index 0000000..0032591
--- /dev/null
+++ b/test/cloudwatch_logs.js
@@ -0,0 +1,73 @@
+'use strict'
+
+const assert = require('chai').assert
+const path = require('path')
+const aws = require('aws-sdk-mock')
+aws.setSDK(path.resolve('node_modules/aws-sdk'))
+const CloudWatchLogs = require(path.join('..', 'lib', 'cloudwatch_logs'))
+
+const mockResponse = {
+  createLogGroup: {
+    testCreateLogGroupResponse: 'An empty object is returned in the actual API'
+  },
+
+  putRetentionPolicy: {
+    testPutRetentionPolicyResponse: 'An empty object is returned in the actual API'
+  }
+}
+
+const params = {
+  FunctionName: 'node-lambda-test-function',
+  retentionInDays: 14
+}
+
+var logs = null
+
+/* global before, after, describe, it */
+describe('lib/cloudwatch_logs', () => {
+  before(() => {
+    aws.mock('CloudWatchLogs', 'createLogGroup', (params, callback) => {
+      callback(null, mockResponse.createLogGroup)
+    })
+    aws.mock('CloudWatchLogs', 'putRetentionPolicy', (params, callback) => {
+      callback(null, mockResponse.putRetentionPolicy)
+    })
+
+    logs = new CloudWatchLogs(require('aws-sdk'))
+  })
+
+  after(() => aws.restore('CloudWatchLogs'))
+
+  describe('_logGroupName', () => {
+    it('correct value', () => {
+      assert.equal(
+        logs._logGroupName(params),
+        '/aws/lambda/node-lambda-test-function'
+      )
+    })
+  })
+
+  describe('_createLogGroup', () => {
+    it('using mock', () => {
+      return logs._createLogGroup(params).then((data) => {
+        assert.deepEqual(data, mockResponse.createLogGroup)
+      })
+    })
+  })
+
+  describe('_putRetentionPolicy', () => {
+    it('using mock', () => {
+      return logs._putRetentionPolicy(params).then((data) => {
+        assert.deepEqual(data, mockResponse.putRetentionPolicy)
+      })
+    })
+  })
+
+  describe('setLogsRetentionPolicy', () => {
+    it('using mock', () => {
+      return logs.setLogsRetentionPolicy(params).then((data) => {
+        assert.deepEqual(data, mockResponse.putRetentionPolicy)
+      })
+    })
+  })
+})
diff --git a/test/main.js b/test/main.js
index 7239c5f..7657b47 100644
--- a/test/main.js
+++ b/test/main.js
@@ -196,6 +196,19 @@ describe('lib/main', function () {
       assert.equal(Object.keys(params.VpcConfig.SecurityGroupIds).length, 0)
     })
 
+    it('appends KMSKeyArn to params when KMS params set', () => {
+      ['', 'arn:aws:kms:test'].forEach((v) => {
+        program.kmsKeyArn = v
+        const params = lambda._params(program)
+        assert.equal(params.KMSKeyArn, v, v)
+      })
+    })
+
+    it('does not append KMSKeyArn when params are not set', () => {
+      const params = lambda._params(program)
+      assert.isUndefined(params.KMSKeyArn)
+    })
+
     it('appends DeadLetterConfig to params when DLQ params set', () => {
       ['', 'arn:aws:sqs:test'].forEach((v) => {
         program.deadLetterConfigTargetArn = v
diff --git a/test/schedule_events.js b/test/schedule_events.js
index d917852..1003a4c 100644
--- a/test/schedule_events.js
+++ b/test/schedule_events.js
@@ -54,6 +54,11 @@ describe('lib/schedule_events', () => {
     schedule = new ScheduleEvents(require('aws-sdk'))
   })
 
+  after(() => {
+    aws.restore('CloudWatchEvents')
+    aws.restore('Lambda')
+  })
+
   describe('_ruleDescription (default)', () => {
     it('correct value', () => {
       assert.equal(