├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── package.json ├── tasks ├── lambda_deploy.js ├── lambda_invoke.js └── lambda_package.js ├── test ├── expected │ ├── custom_options │ ├── default_options │ └── failure_options ├── fixtures │ ├── custom_event.json │ ├── custom_index.js │ ├── event.json │ ├── failing_index.js │ ├── index.js │ ├── package_custom │ │ ├── .npmignore │ │ ├── custom.json │ │ ├── index.js │ │ └── package.json │ ├── package_default │ │ ├── .test │ │ ├── index.js │ │ └── package.json │ └── package_folder_option │ │ ├── .test │ │ ├── index.js │ │ └── package.json ├── integ │ ├── lambda_invoke_test.js │ └── lambda_package_test.js ├── unit │ ├── arn_parser_test.js │ ├── date_facade_test.js │ ├── deploy_task_test.js │ ├── invoke_task_test.js │ └── package_task_test.js └── utils │ ├── fs_mock.js │ ├── grunt_mock.js │ └── npm_mock.js └── utils ├── arn_parser.js ├── date_facade.js ├── deploy_task.js ├── invoke_task.js └── package_task.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | tmp 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | 5 | before_install: npm install -g grunt-cli -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-aws-lambda 3 | * https://github.com/Tim-B/grunt-aws-lambda 4 | * 5 | * Copyright (c) 2014 Tim-B 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function (grunt) { 12 | 13 | // Project configuration. 14 | grunt.initConfig({ 15 | jshint: { 16 | all: [ 17 | 'Gruntfile.js', 18 | 'tasks/*.js', 19 | 'utils/*.js', 20 | 'test/utils/*.js', 21 | 'test/unit/*_test.js', 22 | 'test/integ/*_test.js' 23 | ], 24 | options: { 25 | jshintrc: '.jshintrc' 26 | } 27 | }, 28 | 29 | // Before generating any new files, remove any previously-created files. 30 | clean: { 31 | tests: ['tmp'] 32 | }, 33 | 34 | // Configuration to be run (and then tested). 35 | lambda_invoke: { 36 | default_options: { 37 | options: { 38 | file_name: 'test/fixtures/index.js', 39 | event: 'test/fixtures/event.json' 40 | } 41 | }, 42 | custom_options: { 43 | options: { 44 | file_name: 'test/fixtures/custom_index.js', 45 | event: 'test/fixtures/custom_event.json', 46 | handler: 'myfunction' 47 | } 48 | }, 49 | failure_options: { 50 | options: { 51 | file_name: 'test/fixtures/failing_index.js', 52 | event: 'test/fixtures/event.json', 53 | handler: 'myfunction' 54 | } 55 | }, 56 | package_folder_options: { 57 | options: { 58 | package_folder: 'test/fixtures/package_folder_option', 59 | file_name: 'index.js', 60 | event: '../../../test/fixtures/event.json' 61 | } 62 | } 63 | }, 64 | lambda_package: { 65 | default_options: { 66 | options: { 67 | dist_folder: 'tmp/dist', 68 | package_folder: 'test/fixtures/package_default' 69 | } 70 | }, 71 | custom_options: { 72 | options: { 73 | dist_folder: 'tmp/dist', 74 | include_time: false, 75 | package_folder: 'test/fixtures/package_custom', 76 | include_files: ['custom.json'] 77 | } 78 | } 79 | }, 80 | lambda_deploy: { 81 | default_options: { 82 | options: { 83 | }, 84 | function: 'lambda-test' 85 | } 86 | }, 87 | // Unit tests. 88 | nodeunit: { 89 | unit: ['test/unit/*_test.js'], 90 | integ: ['test/integ/*_test.js'] 91 | } 92 | }); 93 | 94 | // Actually load this plugin's task(s). 95 | grunt.task.loadTasks('tasks'); 96 | 97 | // These plugins provide necessary tasks. 98 | grunt.loadNpmTasks('grunt-contrib-jshint'); 99 | grunt.loadNpmTasks('grunt-contrib-clean'); 100 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 101 | 102 | // Whenever the "test" task is run, first clean the "tmp" dir, then run this 103 | // plugin's task(s), then test the result. 104 | grunt.registerTask('test', ['clean', 'lambda_package', 'nodeunit']); 105 | 106 | // By default, lint and run all tests. 107 | grunt.registerTask('default', ['jshint', 'test']); 108 | }; 109 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Tim-B 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-aws-lambda 2 | 3 | > A grunt plugin to assist in developing functions for [AWS Lambda](http://aws.amazon.com/lambda/). 4 | 5 | [![Build Status](https://travis-ci.org/Tim-B/grunt-aws-lambda.svg)](https://travis-ci.org/Tim-B/grunt-aws-lambda) 6 | 7 | This plugin provides helpers for: 8 | * Running Lambda functions locally 9 | * Managing npm dependencies which are required by your function 10 | * Packaging required dependencies with your function in a Lambda compatible zip 11 | * Uploading package to Lambda 12 | 13 | ## Getting Started 14 | This plugin requires Grunt `~0.4.5` 15 | 16 | If you haven't used [Grunt](http://gruntjs.com/) before, be sure to check out the [Getting Started](http://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](http://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 17 | 18 | ```shell 19 | npm install grunt-aws-lambda --save-dev 20 | ``` 21 | 22 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 23 | 24 | ```js 25 | grunt.loadNpmTasks('grunt-aws-lambda'); 26 | ``` 27 | 28 | ## Gotchas 29 | 30 | ### Add dist to your .npmignore 31 | 32 | This will save you from packaging previous packages in future ones. 33 | 34 | For example your `.npmignore` might look something like this: 35 | ``` 36 | event.json 37 | Gruntfile.js 38 | dist 39 | *.iml 40 | ``` 41 | 42 | [Read More](#default-options-1) 43 | 44 | ## Authenticating to AWS 45 | 46 | This library supports providing credentials for AWS via an IAM Role, an AWS CLI profile, environment variables, a JSON file on disk, or passed in credentials. 47 | To learn more, please see the [below section](#aws-credentials) 48 | 49 | ## grunt-aws-lambda tasks 50 | 51 | ### Overview 52 | 53 | This plugin contains 3 tasks: 54 | * lambda_invoke - Wrapper to run and test lambda functions locally and view output. 55 | * lambda_package - Packages function along with any npm dependencies in a zip format suitable for lambda. 56 | * lambda_deploy - Uploads the zip package to lambda. 57 | 58 | lambda_invoke and lambda_package can be used independently, lambda_deploy will invoke lambda_package before uploading 59 | the produced zip file. 60 | 61 | ### lambda_invoke 62 | 63 | In your project's Gruntfile, add a section named `lambda_invoke` to the data object passed into `grunt.initConfig()`. 64 | 65 | ```js 66 | grunt.initConfig({ 67 | lambda_invoke: { 68 | default: { 69 | options: { 70 | // Task-specific options go here. 71 | } 72 | } 73 | }, 74 | }); 75 | ``` 76 | 77 | #### Options 78 | 79 | ##### options.handler 80 | Type: `String` 81 | Default value: `handler` 82 | 83 | Name of the handler function to invoke. 84 | 85 | ##### options.file_name 86 | Type: `String` 87 | Default value: `index.js` 88 | 89 | Name of your script file which contains your handler function. 90 | 91 | ##### options.event 92 | Type: `String` 93 | Default value: `event.json` 94 | 95 | Name of the .json file containing your test event relative to your Gruntfile. 96 | 97 | #### Usage Examples 98 | 99 | ##### Default Options 100 | In this example, the default options are used therefore if we have the following in our `Gruntfile.js`: 101 | 102 | ```js 103 | grunt.initConfig({ 104 | lambda_invoke: { 105 | default: { 106 | options: { 107 | // Task-specific options go here. 108 | } 109 | } 110 | }, 111 | }); 112 | ``` 113 | And the following in `index.js` 114 | 115 | ```js 116 | exports.handler = function (event, context) { 117 | console.log('value1 = ' + event.key1); 118 | console.log('value2 = ' + event.key2); 119 | console.log('value3 = ' + event.key3); 120 | 121 | context.done(null, 'Hello World'); // SUCCESS with message 122 | }; 123 | ``` 124 | 125 | And the following in `event.json` 126 | ```json 127 | { 128 | "key1": "value1", 129 | "key2": "value2", 130 | "key3": "value3" 131 | } 132 | ``` 133 | 134 | Then we run `grunt lambda_invoke`, we should get the following output: 135 | 136 | ``` 137 | Running "lambda_invoke" task 138 | 139 | value1 = value1 140 | value2 = value2 141 | value3 = value3 142 | 143 | Message 144 | ------- 145 | Hello World 146 | 147 | Done, without errors. 148 | ``` 149 | 150 | 151 | ### lambda_package 152 | 153 | This task generates a lambda package including npm dependencies using the default npm install functionality. 154 | 155 | In your project's Gruntfile, add a section named `lambda_package` to the data object passed into `grunt.initConfig()`. 156 | 157 | ```js 158 | grunt.initConfig({ 159 | lambda_package: { 160 | default: { 161 | options: { 162 | // Task-specific options go here. 163 | } 164 | } 165 | }, 166 | }); 167 | ``` 168 | 169 | #### Options 170 | 171 | ##### options.include_files 172 | Type: `Array` 173 | Default value: `[]` 174 | 175 | List of files to explicitly include in the package, even if they would be ignored by NPM 176 | 177 | ##### options.include_time 178 | Type: `Boolean` 179 | Default value: `true` 180 | 181 | Whether or not to timestamp the packages, if set to true the current date/time will be included in the zip name, if false 182 | then the package name will be constant and consist of just the package name and version. 183 | 184 | ##### options.include_version 185 | Type: `Boolean` 186 | Default value: `true` 187 | 188 | Whether or not to include the NPM package version in the artifact package name. Set to false if you'd prefer a static 189 | package file name regardless of the version. 190 | 191 | ##### options.package_folder 192 | Type: `String` 193 | Default value: `./` 194 | 195 | The path to your npm package, must contain the package.json file. 196 | 197 | ##### options.dist_folder 198 | Type: `String` 199 | Default value: `dist` 200 | 201 | The folder where the complete zip files should be saved relative to the Gruntfile. 202 | 203 | #### Usage Examples 204 | 205 | ##### Default Options 206 | In this example, the default options are used therefore if we have the following in our `Gruntfile.js`: 207 | 208 | ```js 209 | grunt.initConfig({ 210 | lambda_package: { 211 | default: { 212 | options: { 213 | // Task-specific options go here. 214 | } 215 | } 216 | }, 217 | }); 218 | ``` 219 | And the following in `package.json` 220 | 221 | ```json 222 | { 223 | "name": "my-lambda-function", 224 | "description": "An Example Lamda Function", 225 | "version": "0.0.1", 226 | "private": "true", 227 | "dependencies": { 228 | "jquery": "2.1.1" 229 | }, 230 | "devDependencies": { 231 | "grunt": "0.4.*", 232 | "grunt-pack": "0.1.*", 233 | "grunt-aws-lambda": "0.1.*" 234 | } 235 | } 236 | ``` 237 | 238 | Then we run `grunt lambda_package`, we should see a new zip file in a new folder called `dist` called something like: 239 | 240 | `my-lambda-function_0-0-1_2014-10-30-18-29-4.zip` 241 | 242 | If you unzip that and look inside you should see something like: 243 | ``` 244 | index.js 245 | package.json 246 | node_modules/ 247 | node_modules/jquery 248 | node_modules/jquery/... etc 249 | ``` 250 | 251 | Given that by default the dist folder is inside your function folder you can easily end up bundling previous packages 252 | inside subsequent packages therefore it is **strongly advised that you add dist to your .npmignore**. 253 | 254 | For example your `.npmignore` might look something like this: 255 | ``` 256 | event.json 257 | Gruntfile.js 258 | dist 259 | *.iml 260 | ``` 261 | 262 | ### lambda_deploy 263 | 264 | In your project's Gruntfile, add a section named `lambda_deploy` to the data object passed into `grunt.initConfig()`. 265 | 266 | ```js 267 | grunt.initConfig({ 268 | lambda_deploy: { 269 | default: { 270 | arn: 'arn:aws:lambda:us-east-1:123456781234:function:my-function', 271 | options: { 272 | // Task-specific options go here. 273 | } 274 | } 275 | }, 276 | }); 277 | ``` 278 | 279 | #### Options 280 | 281 | 282 | ##### arn 283 | Type: `String` 284 | Default value: None - Required 285 | 286 | The ARN of your target Lambda function. 287 | 288 | ##### function 289 | Type: `String` 290 | Default value: None - Required (if you havn't specified an ARN) 291 | 292 | *This option is deprecated, use arn instead*. The name of your target Lambda function, ie. the name of the function in the AWS console. 293 | 294 | ##### Proxy 295 | On Linux based hosts you can set proxy server for deploy task by specifying standard environment variable - https_proxy. 296 | E.g: 297 | env https_proxy=http://localhost:8080 grunt deploy 298 | 299 | ##### package 300 | Type: `String` 301 | Default value: Package name set by package task of same target - see below. 302 | 303 | The name of the package to be uploaded. 304 | 305 | When the lambda_package task runs it sets the package value for the lambda_deploy target with the same name. 306 | 307 | Therefore if lambda_package and lambda_deploy have a target (eg. default) with the same name you will not 308 | need to provide this value - it will be passed automatically. 309 | 310 | For example, your Gruntfile.js might contain the following: 311 | 312 | 313 | ```js 314 | grunt.initConfig({ 315 | lambda_deploy: { 316 | default: { 317 | arn: 'arn:aws:lambda:us-east-1:123456781234:function:my-function' 318 | } 319 | }, 320 | lambda_package: { 321 | default: { 322 | } 323 | } 324 | }); 325 | ``` 326 | 327 | You could then run `grunt lambda_package lambda_deploy` and it'll automatically create the package and deploy it without 328 | having to specify a package name. 329 | 330 | ##### options.profile 331 | Type: `String` 332 | Default value: `null` 333 | 334 | If you wish to use a specific AWS credentials profile you can specify it here, otherwise it will use the environment default. 335 | You can also specify it with the environment variable `AWS_PROFILE` 336 | 337 | ##### options.RoleArn 338 | Type: `String` 339 | Default value: `null` 340 | 341 | If you wish to assume a specific role from an EC2 instance you can specify it here, otherwise it will use the environment default. 342 | 343 | ##### options.accessKeyId 344 | Type: `String` 345 | Default value: `null` 346 | 347 | If you wish to use hardcoded AWS credentials you should specify the Access Key ID here 348 | 349 | ##### options.secretAccessKey 350 | Type: `String` 351 | Default value: `null` 352 | 353 | If you wish to use hardcoded AWS credentials you should specify the Secret Access Key here 354 | 355 | ##### options.credentialsJSON 356 | Type: `String` 357 | Default value: `null` 358 | 359 | If you wish to use hardcoded AWS credentials saved in a JSON file, put the path to the JSON here. The JSON must conform to the [AWS format](http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html#Credentials_from_Disk). 360 | 361 | ##### options.region 362 | Type: `String` 363 | Default value: `us-east-1` 364 | 365 | Specify the AWS region your functions will be uploaded to. Note that if an ARN is supplied this option is not required. 366 | 367 | ##### options.timeout 368 | Type: `Integer` 369 | Default value: `null` 370 | Depending on your Lambda function, you might need to increase the timeout value. The default timeout assigned by AWS is currently 3 seconds. 371 | If you wish to increase this timeout set the value here. 372 | 373 | ##### options.memory 374 | Type: `Integer` 375 | Default value: `null` 376 | 377 | Sets the memory assigned to the function. If null then the current setting for the function will be used. Value is in MB and must be a multiple of 64. 378 | 379 | ##### options.handler 380 | Type: `String` 381 | Default value: `null` 382 | 383 | Sets the handler for your lambda function. If left null, the current setting will remain unchanged. 384 | 385 | ##### options.enableVersioning 386 | Type: `boolean` 387 | Default value: `false` 388 | 389 | When enabled each deployment creates a new version. 390 | 391 | ##### options.aliases 392 | Type: `String` or `Array` 393 | Default value: `null` 394 | 395 | If a string or an array of strings then creates these aliases. If versioning enabled then points to the created version, 396 | otherwise points to `$LATEST`. 397 | 398 | It is recommended that `enableVersioning` is also enabled when using this feature. 399 | 400 | Examples: 401 | 402 | Creates one `beta` alias: 403 | ```js 404 | aliases: 'beta' 405 | ``` 406 | 407 | Creates two aliases, `alias1` and `alias2`: 408 | ```js 409 | aliases: [ 410 | 'alias1', 411 | 'alias2' 412 | ] 413 | ``` 414 | 415 | ##### options.enablePackageVersionAlias 416 | Type: `boolean` 417 | Default value: false 418 | 419 | When enabled creates a second alias using the NPM package version. When the NPM package version is bumped a new 420 | alias will be created, allowing you to keep the old alias available for backward compatibility. 421 | 422 | It is recommended that `enableVersioning` is also enabled when using this feature. 423 | 424 | ##### options.subnetIds 425 | Type: `Array` 426 | Default value: `null` 427 | 428 | A list of one or more subnet IDs in your VPC. 429 | 430 | ##### options.securityGroupIds 431 | Type: `Array` 432 | Default value: `null` 433 | 434 | A list of one or more security groups IDs in your VPC. 435 | 436 | If your Lambda function accesses resources in a VPC you must provide at least one security group and one subnet ID. These must belong to the same VPC 437 | 438 | #### Usage Examples 439 | 440 | ##### Default Options 441 | In this example, the default options are used therefore if we have the following in our `Gruntfile.js`: 442 | 443 | ```js 444 | grunt.initConfig({ 445 | lambda_deploy: { 446 | default: { 447 | arn: 'arn:aws:lambda:us-east-1:123456781234:function:my-function' 448 | } 449 | } 450 | }); 451 | ``` 452 | And now if you run `grunt lambda_deploy` your package should be created and uploaded to the specified function. 453 | 454 | 455 | ##### Increasing the Timeout Options to 10 seconds 456 | In this example, the timeout value is increased to 10 seconds and set memory to 256mb. 457 | 458 | ```js 459 | grunt.initConfig({ 460 | lambda_deploy: { 461 | default: { 462 | arn: 'arn:aws:lambda:us-east-1:123456781234:function:my-function', 463 | options: { 464 | timeout : 10, 465 | memory: 256 466 | } 467 | } 468 | } 469 | }); 470 | ``` 471 | 472 | ##### Example with a beta and prod deployment configuration 473 | 474 | Deploy to beta with `deploy` and to prod with `deploy_prod`: 475 | 476 | ```js 477 | grunt.initConfig({ 478 | lambda_invoke: { 479 | default: { 480 | } 481 | }, 482 | lambda_deploy: { 483 | default: { 484 | options: { 485 | aliases: 'beta', 486 | enableVersioning: true 487 | }, 488 | arn: 'arn:aws:lambda:us-east-1:123456789123:function:myfunction' 489 | }, 490 | prod: { 491 | options: { 492 | aliases: 'prod', 493 | enableVersioning: true 494 | }, 495 | arn: 'arn:aws:lambda:us-east-1:123456789123:function:myfunction' 496 | } 497 | }, 498 | lambda_package: { 499 | default: { 500 | }, 501 | prod: { 502 | } 503 | } 504 | }); 505 | 506 | grunt.registerTask('deploy', ['lambda_package', 'lambda_deploy:default']); 507 | grunt.registerTask('deploy_prod', ['lambda_package', 'lambda_deploy:prod']); 508 | ``` 509 | 510 | ## Misc info 511 | 512 | ### Streamlining deploy 513 | 514 | You can combine the lambda_package and lambda_deploy into a single deploy task by adding the following to your 515 | Gruntfile.js: 516 | 517 | ```js 518 | grunt.registerTask('deploy', ['lambda_package', 'lambda_deploy']); 519 | ``` 520 | 521 | You can then run `grunt deploy` to perform both these functions in one step. 522 | 523 | ### AWS credentials 524 | 525 | The AWS SDK is configured to look for credentials in the following order: 526 | 527 | 1. an IAM Role (if running on EC2) 528 | 2. an AWS CLI profile (from `~/.aws/credentials`) 529 | 3. environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`) 530 | 4. a JSON file on disk 531 | 5. Hardcoded credentials passed into grunt-aws 532 | 533 | The preferred method of authenticating during local development is by providing credentials in `~/.aws/credentials`, 534 | it should look something like this: 535 | 536 | ``` 537 | [default] 538 | aws_access_key_id = 539 | aws_secret_access_key = 540 | ``` 541 | 542 | For more information [read this documentation](http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html). 543 | 544 | ### AWS permissions 545 | 546 | To run the deploy command the AWS credentials require permissions to access lambda including `lambda:GetFunction`, 547 | `lambda:UploadFunction`, `lambda:UpdateFunctionCode`, `lambda:UpdateFunctionConfiguration` and 548 | `iam:PassRole` for the role which is assigned to the function. 549 | 550 | It is recommended that the following policy be applied to the user: 551 | 552 | ```json 553 | { 554 | "Version": "2012-10-17", 555 | "Statement": [ 556 | { 557 | "Sid": "Stmt1442787227063", 558 | "Action": [ 559 | "lambda:GetFunction", 560 | "lambda:UploadFunction", 561 | "lambda:UpdateFunctionCode", 562 | "lambda:UpdateFunctionConfiguration", 563 | "lambda:GetAlias", 564 | "lambda:UpdateAlias", 565 | "lambda:CreateAlias", 566 | "lambda:PublishVersion" 567 | ], 568 | "Effect": "Allow", 569 | "Resource": "arn:aws:lambda:*" 570 | }, 571 | { 572 | "Sid": "Stmt1442787265773", 573 | "Action": [ 574 | "iam:PassRole" 575 | ], 576 | "Effect": "Allow", 577 | "Resource": "arn:aws:iam:::role/" 578 | } 579 | ] 580 | } 581 | ``` 582 | 583 | ## Contributing 584 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/). 585 | 586 | ## Release History 587 | ### 0.1.0 588 | Initial release 589 | 590 | ### 0.2.0 591 | Adding some unit tests, refactoring deploy task into single task and converting tasks to multitasks 592 | 593 | ### 0.3.0 594 | Adding more warnings for various failure cases 595 | 596 | ### 0.4.0 597 | 598 | * Added support for succeed and fail functions - [pull request by jonyo](https://github.com/Tim-B/grunt-aws-lambda/pull/11) 599 | * Added NPM to package.json - [pull request by jonyo](https://github.com/Tim-B/grunt-aws-lambda/pull/13), should address [issue 2](https://github.com/Tim-B/grunt-aws-lambda/issues/2#issuecomment-104805707) 600 | * Added timeout and memory options - [timeout pull request by aidancasey](https://github.com/Tim-B/grunt-aws-lambda/pull/3) 601 | * Bumped aws-sdk version 602 | * Bumped adm-zip version, will hopefully address [issue 4](https://github.com/Tim-B/grunt-aws-lambda/issues/4) 603 | 604 | ### 0.5.0 605 | * Fixed issue where dotfiles weren't packaged - [see issue 17](https://github.com/Tim-B/grunt-aws-lambda/issues/17) 606 | * Fixed issue where task could be done before zip writing is finished - [pull request by qen](https://github.com/Tim-B/grunt-aws-lambda/pull/16) 607 | * Monkey patched node-archiver to force permissions to be 777 for all files in package - [see issue 6](https://github.com/Tim-B/grunt-aws-lambda/issues/6) 608 | 609 | ### 0.6.0 610 | * Fixing a minor issue caused by some code that shouldn't have been commented out. 611 | 612 | ### 0.7.0 613 | * Removing some unneeded files from the NPM package. 614 | 615 | ### 0.8.0 616 | * Adding `include_files` option to package - [pull request by dhleong](https://github.com/Tim-B/grunt-aws-lambda/pull/19) 617 | 618 | ### 0.9.0 619 | * Parsing region automatically from ARN - [pull request by jvwing](https://github.com/Tim-B/grunt-aws-lambda/pull/25) 620 | 621 | ### 0.10.0 622 | * Making NPM a regular dependency to resolve [#20](https://github.com/Tim-B/grunt-aws-lambda/issues/20) - [pull request by timdp](https://github.com/Tim-B/grunt-aws-lambda/pull/27) 623 | 624 | ### 0.11.0 625 | * Including AWS API error message in deployment failure - [pull request by CaseyBurns](https://github.com/Tim-B/grunt-aws-lambda/pull/40) 626 | * Providing a method to pass AWS credentials in either the Gruntfile or credentials file - [pull request by robbiet480](https://github.com/Tim-B/grunt-aws-lambda/pull/34) 627 | * Adding support for AWS temporary credentials - [pull request by olih](https://github.com/Tim-B/grunt-aws-lambda/pull/46) 628 | 629 | ### 0.12.0 630 | 631 | * Added package_folder option to lambda_invoke task - [pull request by dcaravana](https://github.com/Tim-B/grunt-aws-lambda/pull/62) 632 | * Adding optional files to contain JSON objects for clientContext and identity to be passed into the lambda_invoke command - [pull request by Skorch](https://github.com/Tim-B/grunt-aws-lambda/pull/51) 633 | * Add handler option to lambda_deploy task - [pull request by Rawbz](https://github.com/Tim-B/grunt-aws-lambda/pull/52) 634 | * Fix lambda_deploy config example - [pull request by pracucci](https://github.com/Tim-B/grunt-aws-lambda/pull/56) 635 | * Context object methods cleanup - [pull request by ubergoob](https://github.com/Tim-B/grunt-aws-lambda/pull/58), also fixes [issue 54](https://github.com/Tim-B/grunt-aws-lambda/issues/54) 636 | * When deploy_arn is not defined in the Gruntfile the value is undefined - [pull request by varunvairavan](https://github.com/Tim-B/grunt-aws-lambda/pull/60) 637 | * Extensive refactoring to improve testability and a new unit test suite 638 | * Bumped AWS SDK version to 2.2.32 639 | * Added support for versioning and aliases 640 | * Added support for excluding the package version from the package artifact name - based on [pull request by leecrossley](https://github.com/Tim-B/grunt-aws-lambda/pull/59) 641 | 642 | ### 0.13.0 643 | 644 | * Added support for Node 4.3 runtime callback function - [pull request by bobhigs](https://github.com/Tim-B/grunt-aws-lambda/pull/76) 645 | * Added VPC support - [pull request by beeva-arturomartinez](https://github.com/Tim-B/grunt-aws-lambda/pull/71) 646 | * Added local proxy support - [pull request by alekstr](https://github.com/Tim-B/grunt-aws-lambda/pull/66) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-aws-lambda", 3 | "description": "A grunt plugin to help develop AWS Lambda functions.", 4 | "version": "0.13.0", 5 | "homepage": "https://github.com/Tim-B/grunt-aws-lambda", 6 | "author": { 7 | "name": "Tim-B", 8 | "email": "tim@galacticcode.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/Tim-B/grunt-aws-lambda.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/Tim-B/grunt-aws-lambda/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/Tim-B/grunt-aws-lambda/blob/master/LICENSE-MIT" 21 | } 22 | ], 23 | "engines": { 24 | "node": ">= 0.8.0" 25 | }, 26 | "scripts": { 27 | "test": "grunt" 28 | }, 29 | "dependencies": { 30 | "temporary": "~0.0.8", 31 | "archiver": "~0.14.4", 32 | "mkdirp": "~0.5.0", 33 | "rimraf": "~2.2.8", 34 | "glob": "~4.3.0", 35 | "aws-sdk": "~2.2.32", 36 | "proxy-agent": "latest", 37 | "npm": "^2.10.0", 38 | "q": "^1.4.1" 39 | }, 40 | "devDependencies": { 41 | "grunt-contrib-jshint": "^0.9.2", 42 | "grunt-contrib-clean": "^0.5.0", 43 | "grunt-contrib-nodeunit": "^0.3.3", 44 | "mockery": "^1.4.0", 45 | "grunt": "~0.4.5", 46 | "adm-zip": "~0.4.4", 47 | "sinon": "^1.17.3" 48 | }, 49 | "peerDependencies": { 50 | "grunt": ">=0.4.0" 51 | }, 52 | "keywords": [ 53 | "gruntplugin" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /tasks/lambda_deploy.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-aws-lambda 3 | * https://github.com/Tim-B/grunt-aws-lambda 4 | * 5 | * Copyright (c) 2014 Tim-B 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var deployTask = require('../utils/deploy_task'); 12 | 13 | module.exports = function (grunt) { 14 | 15 | // Please see the Grunt documentation for more information regarding task 16 | // creation: http://gruntjs.com/creating-tasks 17 | 18 | grunt.registerMultiTask('lambda_deploy', 'Uploads a package to lambda', deployTask.getHandler(grunt)); 19 | }; 20 | -------------------------------------------------------------------------------- /tasks/lambda_invoke.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-aws-lambda 3 | * https://github.com/Tim-B/grunt-aws-lambda 4 | * 5 | * Copyright (c) 2014 Tim-B 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var invokeTask = require('../utils/invoke_task'); 12 | 13 | module.exports = function (grunt) { 14 | 15 | 16 | 17 | // Please see the Grunt documentation for more information regarding task 18 | // creation: http://gruntjs.com/creating-tasks 19 | 20 | grunt.registerMultiTask('lambda_invoke', 'Invokes a lambda function for testing purposes', invokeTask.getHandler(grunt)); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /tasks/lambda_package.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-aws-lambda 3 | * https://github.com/Tim-B/grunt-aws-lambda 4 | * 5 | * Copyright (c) 2014 Tim-B 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var packageTask = require('../utils/package_task'); 12 | 13 | module.exports = function (grunt) { 14 | 15 | // Please see the Grunt documentation for more information regarding task 16 | // creation: http://gruntjs.com/creating-tasks 17 | 18 | grunt.registerMultiTask('lambda_package', 'Creates a package to be uploaded to lambda', packageTask.getHandler(grunt)); 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /test/expected/custom_options: -------------------------------------------------------------------------------- 1 | Running "lambda_invoke:custom_options" (lambda_invoke) task 2 | 3 | value1 = value4 4 | value2 = value5 5 | value3 = value6 6 | 7 | Success! Message: 8 | ------------------ 9 | Hello World 10 | 11 | Done, without errors. -------------------------------------------------------------------------------- /test/expected/default_options: -------------------------------------------------------------------------------- 1 | Running "lambda_invoke:default_options" (lambda_invoke) task 2 | 3 | value1 = value1 4 | value2 = value2 5 | value3 = value3 6 | 7 | Success! Message: 8 | ------------------ 9 | Hello World 10 | 11 | Done, without errors. -------------------------------------------------------------------------------- /test/expected/failure_options: -------------------------------------------------------------------------------- 1 | Running "lambda_invoke:failure_options" (lambda_invoke) task 2 | 3 | value1 = value1 4 | value2 = value2 5 | value3 = value3 6 | 7 | Failure! Message: 8 | ------------------ 9 | Hello World 10 | Warning: Task "lambda_invoke:failure_options" failed. Use --force to continue. 11 | 12 | Aborted due to warnings. -------------------------------------------------------------------------------- /test/fixtures/custom_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "value4", 3 | "key2": "value5", 4 | "key3": "value6" 5 | } -------------------------------------------------------------------------------- /test/fixtures/custom_index.js: -------------------------------------------------------------------------------- 1 | exports.myfunction = function (event, context) { 2 | console.log('value1 = ' + event.key1); 3 | console.log('value2 = ' + event.key2); 4 | console.log('value3 = ' + event.key3); 5 | 6 | context.done(null, 'Hello World'); 7 | }; -------------------------------------------------------------------------------- /test/fixtures/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "value1", 3 | "key2": "value2", 4 | "key3": "value3" 5 | } -------------------------------------------------------------------------------- /test/fixtures/failing_index.js: -------------------------------------------------------------------------------- 1 | exports.myfunction = function (event, context) { 2 | console.log('value1 = ' + event.key1); 3 | console.log('value2 = ' + event.key2); 4 | console.log('value3 = ' + event.key3); 5 | 6 | context.fail('Hello World'); 7 | }; -------------------------------------------------------------------------------- /test/fixtures/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context) { 2 | console.log('value1 = ' + event.key1); 3 | console.log('value2 = ' + event.key2); 4 | console.log('value3 = ' + event.key3); 5 | 6 | context.succeed('Hello World'); 7 | }; -------------------------------------------------------------------------------- /test/fixtures/package_custom/.npmignore: -------------------------------------------------------------------------------- 1 | custom.json 2 | -------------------------------------------------------------------------------- /test/fixtures/package_custom/custom.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/fixtures/package_custom/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context) { 2 | console.log('value1 = ' + event.key1); 3 | console.log('value2 = ' + event.key2); 4 | console.log('value3 = ' + event.key3); 5 | 6 | context.done(null, 'Hello World'); 7 | }; -------------------------------------------------------------------------------- /test/fixtures/package_custom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "another-lambda-function", 3 | "description": "Another Example Lamda Function", 4 | "version": "0.0.1", 5 | "private": "true", 6 | "dependencies": { 7 | "jquery": "2.1.1" 8 | }, 9 | "devDependencies": { 10 | } 11 | } -------------------------------------------------------------------------------- /test/fixtures/package_default/.test: -------------------------------------------------------------------------------- 1 | Hello World -------------------------------------------------------------------------------- /test/fixtures/package_default/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context) { 2 | console.log('value1 = ' + event.key1); 3 | console.log('value2 = ' + event.key2); 4 | console.log('value3 = ' + event.key3); 5 | 6 | context.done(null, 'Hello World'); 7 | }; -------------------------------------------------------------------------------- /test/fixtures/package_default/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-lambda-function", 3 | "description": "An Example Lamda Function", 4 | "version": "0.0.1", 5 | "private": "true", 6 | "dependencies": { 7 | }, 8 | "devDependencies": { 9 | } 10 | } -------------------------------------------------------------------------------- /test/fixtures/package_folder_option/.test: -------------------------------------------------------------------------------- 1 | Hello World -------------------------------------------------------------------------------- /test/fixtures/package_folder_option/index.js: -------------------------------------------------------------------------------- 1 | exports.handler = function (event, context) { 2 | context.done(null, process.cwd()); 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/package_folder_option/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-lambda-function", 3 | "description": "An Example Lamda Function", 4 | "version": "0.0.1", 5 | "private": "true", 6 | "dependencies": { 7 | }, 8 | "devDependencies": { 9 | } 10 | } -------------------------------------------------------------------------------- /test/integ/lambda_invoke_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | var path = require('path'); 5 | 6 | /* 7 | ======== A Handy Little Nodeunit Reference ======== 8 | https://github.com/caolan/nodeunit 9 | 10 | Test methods: 11 | test.expect(numAssertions) 12 | test.done() 13 | Test assertions: 14 | test.ok(value, [message]) 15 | test.equal(actual, expected, [message]) 16 | test.notEqual(actual, expected, [message]) 17 | test.deepEqual(actual, expected, [message]) 18 | test.notDeepEqual(actual, expected, [message]) 19 | test.strictEqual(actual, expected, [message]) 20 | test.notStrictEqual(actual, expected, [message]) 21 | test.throws(block, [error], [message]) 22 | test.doesNotThrow(block, [error], [message]) 23 | test.ifError(value) 24 | */ 25 | 26 | function getNormalizedFile(filepath) { 27 | return grunt.util.normalizelf(grunt.file.read(filepath)); 28 | } 29 | 30 | exports.lambda_invoke = { 31 | setUp: function (done) { 32 | // setup here if necessary 33 | done(); 34 | }, 35 | default_options: function (test) { 36 | test.expect(1); 37 | 38 | grunt.util.spawn({ 39 | grunt: true, 40 | args: ['lambda_invoke:default_options', '--no-color'] 41 | }, function (err, result, code) { 42 | 43 | var expected = getNormalizedFile('test/expected/default_options'); 44 | var actual = grunt.util.normalizelf(result.stdout); 45 | test.equal(actual, expected); 46 | test.done(); 47 | }); 48 | }, 49 | custom_options: function (test) { 50 | test.expect(1); 51 | 52 | grunt.util.spawn({ 53 | grunt: true, 54 | args: ['lambda_invoke:custom_options', '--no-color'] 55 | }, function (err, result, code) { 56 | 57 | var expected = getNormalizedFile('test/expected/custom_options'); 58 | var actual = grunt.util.normalizelf(result.stdout); 59 | test.equal(actual, expected); 60 | test.done(); 61 | }); 62 | }, 63 | failure_options: function (test) { 64 | test.expect(1); 65 | 66 | grunt.util.spawn({ 67 | grunt: true, 68 | args: ['lambda_invoke:failure_options', '--no-color'] 69 | }, function (err, result, code) { 70 | 71 | var expected = getNormalizedFile('test/expected/failure_options'); 72 | var actual = grunt.util.normalizelf(result.stdout); 73 | test.equal(actual, expected); 74 | test.done(); 75 | }); 76 | }, 77 | package_folder_options: function (test) { 78 | test.expect(2); 79 | 80 | grunt.util.spawn({ 81 | grunt: true, 82 | args: ['lambda_invoke:package_folder_options', '--no-color'] 83 | }, function (err, result, code) { 84 | 85 | var cwd = process.cwd(); 86 | 87 | // test cwd inside the function 88 | var expected_cwd = 'Running "lambda_invoke:package_folder_options" (lambda_invoke) task\n\n\nSuccess! Message:\n------------------\n' + 89 | path.join(cwd, 'test/fixtures/package_folder_option') + 90 | '\n\nDone, without errors.'; 91 | 92 | var actual_cwd = grunt.util.normalizelf(result.stdout); 93 | test.equal(actual_cwd, expected_cwd); 94 | 95 | // test back from the function 96 | test.equal(process.cwd(), cwd); 97 | 98 | test.done(); 99 | }); 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /test/integ/lambda_package_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | var glob = require("glob"); 5 | var AdmZip = require('adm-zip'); 6 | 7 | /* 8 | ======== A Handy Little Nodeunit Reference ======== 9 | https://github.com/caolan/nodeunit 10 | 11 | Test methods: 12 | test.expect(numAssertions) 13 | test.done() 14 | Test assertions: 15 | test.ok(value, [message]) 16 | test.equal(actual, expected, [message]) 17 | test.notEqual(actual, expected, [message]) 18 | test.deepEqual(actual, expected, [message]) 19 | test.notDeepEqual(actual, expected, [message]) 20 | test.strictEqual(actual, expected, [message]) 21 | test.notStrictEqual(actual, expected, [message]) 22 | test.throws(block, [error], [message]) 23 | test.doesNotThrow(block, [error], [message]) 24 | test.ifError(value) 25 | */ 26 | 27 | function getNormalizedFile(filepath) { 28 | return grunt.util.normalizelf(grunt.file.read(filepath)); 29 | } 30 | 31 | exports.lambda_package = { 32 | setUp: function (done) { 33 | // setup here if necessary 34 | done(); 35 | }, 36 | default_options: function (test) { 37 | test.expect(5); 38 | glob("my-lambda-function_0-0-1_*.zip", {cwd: 'tmp/dist'}, function (er, files) { 39 | test.equals(1, files.length); 40 | 41 | var zip = new AdmZip('tmp/dist/' + files[0]); 42 | var zipEntries = zip.getEntries(); 43 | 44 | test.equals(3, zipEntries.length); 45 | 46 | var required = [ 47 | 'index.js', 48 | 'package.json', 49 | '.test' 50 | ]; 51 | 52 | zipEntries.forEach(function (item) { 53 | if (required.indexOf(item.entryName) !== -1) { 54 | test.ok(true, "Found " + item.entryName); 55 | } 56 | }); 57 | 58 | test.done(); 59 | }); 60 | }, 61 | custom_options: function (test) { 62 | test.expect(6); 63 | var zip = new AdmZip("tmp/dist/another-lambda-function_0-0-1_latest.zip"); 64 | var zipEntries = zip.getEntries(); 65 | 66 | var required = [ 67 | 'custom.json', 68 | 'index.js', 69 | 'package.json', 70 | 'node_modules/', 71 | 'node_modules/jquery/', 72 | 'node_modules/jquery/package.json' 73 | ]; 74 | 75 | zipEntries.forEach(function (item) { 76 | if (required.indexOf(item.entryName) !== -1) { 77 | test.ok(true, "Found " + item.entryName); 78 | } 79 | }); 80 | 81 | test.done(); 82 | } 83 | 84 | }; 85 | -------------------------------------------------------------------------------- /test/unit/arn_parser_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var arnParser = require('../../utils/arn_parser'); 4 | 5 | exports.parseFullArn = function(test){ 6 | var arn = 'arn:aws:lambda:us-west-2:123456789012:function:MyFunctionName'; 7 | var functionInfo = arnParser.parse(arn); 8 | test.ok(functionInfo, 'parser should return function info'); 9 | test.equal(functionInfo.region, 'us-west-2'); 10 | test.equal(functionInfo.accountId, '123456789012'); 11 | test.equal(functionInfo.functionName, 'MyFunctionName'); 12 | test.done(); 13 | }; 14 | 15 | exports.parsePartialArn = function(test){ 16 | var arn = '123456789012:MyFunctionName'; 17 | var functionInfo = arnParser.parse(arn); 18 | test.ok(functionInfo, 'parser should return function info'); 19 | test.equal(functionInfo.region, undefined); 20 | test.equal(functionInfo.accountId, '123456789012'); 21 | test.equal(functionInfo.functionName, 'MyFunctionName'); 22 | test.done(); 23 | }; 24 | 25 | exports.parseFunctionName = function(test){ 26 | var arn = 'MyFunctionName'; 27 | var functionInfo = arnParser.parse(arn); 28 | test.ok(functionInfo, 'parser should return function info'); 29 | test.equal(functionInfo.region, undefined); 30 | test.equal(functionInfo.accountId, undefined); 31 | test.equal(functionInfo.functionName, 'MyFunctionName'); 32 | test.done(); 33 | }; 34 | 35 | exports.parseEmptyArn = function(test){ 36 | var arn = ''; 37 | var functionInfo = arnParser.parse(arn); 38 | test.equal(functionInfo, undefined); 39 | test.done(); 40 | }; 41 | 42 | exports.parseUndefinedArn = function(test){ 43 | var functionInfo = arnParser.parse(undefined); 44 | test.equal(functionInfo, undefined); 45 | test.done(); 46 | }; 47 | 48 | exports.parseBadArn = function(test){ 49 | var arn = ':#!!'; 50 | var functionInfo = arnParser.parse(arn); 51 | test.equal(functionInfo, undefined); 52 | test.done(); 53 | }; -------------------------------------------------------------------------------- /test/unit/date_facade_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * grunt-aws-lambda 5 | * https://github.com/Tim-B/grunt-aws-lambda 6 | * 7 | * Copyright (c) 2014 Tim-B 8 | * Licensed under the MIT license. 9 | */ 10 | 11 | /* 12 | ======== A Handy Little Nodeunit Reference ======== 13 | https://github.com/caolan/nodeunit 14 | 15 | Test methods: 16 | test.expect(numAssertions) 17 | test.done() 18 | Test assertions: 19 | test.ok(value, [message]) 20 | test.equal(actual, expected, [message]) 21 | test.notEqual(actual, expected, [message]) 22 | test.deepEqual(actual, expected, [message]) 23 | test.notDeepEqual(actual, expected, [message]) 24 | test.strictEqual(actual, expected, [message]) 25 | test.notStrictEqual(actual, expected, [message]) 26 | test.throws(block, [error], [message]) 27 | test.doesNotThrow(block, [error], [message]) 28 | test.ifError(value) 29 | */ 30 | 31 | var dateFacade = require('../../utils/date_facade'); 32 | 33 | var dateFacadeTest = {}; 34 | 35 | dateFacadeTest.testGetFormattedTimestamp = function(test) { 36 | var fixedDate = new Date(2016, 1, 13, 14, 38, 13); 37 | test.equal(dateFacade.getFormattedTimestamp(fixedDate), '2016-1-13-14-38-13'); 38 | test.done(); 39 | }; 40 | 41 | dateFacadeTest.testGetHumanReadableTimestamp = function(test) { 42 | var fixedDate = new Date(2016, 2, 13, 14, 38, 13); 43 | test.ok(dateFacade.getHumanReadableTimestamp(fixedDate).indexOf('Sun Mar 13 2016 14:38:13') > -1); 44 | test.done(); 45 | }; 46 | module.exports = dateFacadeTest; 47 | -------------------------------------------------------------------------------- /test/unit/deploy_task_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * grunt-aws-lambda 5 | * https://github.com/Tim-B/grunt-aws-lambda 6 | * 7 | * Copyright (c) 2014 Tim-B 8 | * Licensed under the MIT license. 9 | */ 10 | 11 | /* 12 | ======== A Handy Little Nodeunit Reference ======== 13 | https://github.com/caolan/nodeunit 14 | 15 | Test methods: 16 | test.expect(numAssertions) 17 | test.done() 18 | Test assertions: 19 | test.ok(value, [message]) 20 | test.equal(actual, expected, [message]) 21 | test.notEqual(actual, expected, [message]) 22 | test.deepEqual(actual, expected, [message]) 23 | test.notDeepEqual(actual, expected, [message]) 24 | test.strictEqual(actual, expected, [message]) 25 | test.notStrictEqual(actual, expected, [message]) 26 | test.throws(block, [error], [message]) 27 | test.doesNotThrow(block, [error], [message]) 28 | test.ifError(value) 29 | */ 30 | 31 | var mockery = require('mockery'); 32 | var path = require('path'); 33 | var sinon = require('sinon'); 34 | 35 | mockery.registerAllowable('../../utils/invoke_task'); 36 | var gruntMock = require('../utils/grunt_mock'); 37 | var fsMock = require('../utils/fs_mock'); 38 | 39 | var deployTaskTest = {}; 40 | 41 | var awsSDKMock, 42 | lambdaAPIMock, 43 | defaultGruntConfig, 44 | proxyAgentMock; 45 | 46 | deployTaskTest.setUp = function(done) { 47 | mockery.enable({ 48 | warnOnReplace: false, 49 | warnOnUnregistered: false, 50 | useCleanCache: true 51 | }); 52 | 53 | lambdaAPIMock = { 54 | getFunction: sinon.stub().callsArgWithAsync(1, null, { 55 | data: { 56 | Configuration: { 57 | timeout: 100, 58 | MemorySize: 128, 59 | Handler: 'handler' 60 | } 61 | } 62 | }), 63 | updateFunctionConfiguration: sinon.stub().callsArgWithAsync(1, null, {}), 64 | updateFunctionCode: sinon.stub().callsArgWithAsync(1, null, {}), 65 | publishVersion: sinon.stub().callsArgWithAsync(1, null, { 66 | Version: 5 67 | }), 68 | getAlias: sinon.stub().callsArgWithAsync(1, {statusCode: 404}, {}), 69 | createAlias: sinon.stub().callsArgWithAsync(1, null, {}), 70 | updateAlias: sinon.stub().callsArgWithAsync(1, null, {}) 71 | }; 72 | 73 | awsSDKMock = { 74 | SharedIniFileCredentials: sinon.stub(), 75 | EC2MetadataCredentials: sinon.stub(), 76 | TemporaryCredentials: sinon.stub(), 77 | config: { 78 | update: sinon.stub(), 79 | loadFromPath: sinon.stub() 80 | }, 81 | Lambda: function(params) { 82 | return lambdaAPIMock; 83 | } 84 | }; 85 | 86 | proxyAgentMock = sinon.spy(); 87 | 88 | fsMock.reset(); 89 | mockery.registerMock('fs', fsMock); 90 | 91 | fsMock.setFileContent('some-package.zip', 'abc123'); 92 | 93 | mockery.registerMock('aws-sdk', awsSDKMock); 94 | 95 | mockery.registerMock('proxy-agent', proxyAgentMock); 96 | 97 | var dateFacadeMock = { 98 | getHumanReadableTimestamp: sinon.stub().returns('Sat Feb 13 2016 21:46:15 GMT-0800 (PST)') 99 | }; 100 | mockery.registerMock('./date_facade', dateFacadeMock); 101 | 102 | defaultGruntConfig = { 103 | 'lambda_deploy.fake-target.function': 'some-function', 104 | 'lambda_deploy.fake-target.package': './dist/some-package.zip' 105 | }; 106 | 107 | delete process.env.https_proxy; 108 | 109 | done(); 110 | }; 111 | 112 | deployTaskTest.tearDown = function(done) { 113 | mockery.disable(); 114 | done(); 115 | }; 116 | 117 | deployTaskTest.testDeploySucceed = function(test) { 118 | test.expect(9); 119 | 120 | var deployTask = require('../../utils/deploy_task'); 121 | 122 | var harnessParams = { 123 | options: {}, 124 | config: defaultGruntConfig, 125 | callback: function(harness) { 126 | test.equal(harness.status, true); 127 | test.equal(harness.output.length, 3); 128 | test.equal(harness.output[0], 'Uploading...'); 129 | test.equal(harness.output[1], 'Package deployed.'); 130 | test.equal(harness.output[2], 'No config updates to make.'); 131 | 132 | test.ok(awsSDKMock.config.update.calledWith({region: 'us-east-1'})); 133 | test.ok(lambdaAPIMock.getFunction.calledWith({FunctionName: 'some-function'})); 134 | test.ok(lambdaAPIMock.updateFunctionCode.calledWith({FunctionName: 'some-function', ZipFile: 'abc123'})); 135 | test.ok(!lambdaAPIMock.updateFunctionConfiguration.called); 136 | test.done(); 137 | } 138 | }; 139 | gruntMock.execute(deployTask.getHandler, harnessParams); 140 | }; 141 | 142 | deployTaskTest.testDeployUsingProxy = function(test) { 143 | test.expect(6); 144 | 145 | var deployTask = require('../../utils/deploy_task'); 146 | 147 | var proxy = 'http://localhost:8080'; 148 | process.env.https_proxy = proxy; 149 | 150 | var harnessParams = { 151 | options: {}, 152 | config: defaultGruntConfig, 153 | callback: function(harness) { 154 | test.equal(harness.status, true); 155 | test.equal(harness.output.length, 3); 156 | test.equal(harness.output[0], 'Uploading...'); 157 | test.equal(harness.output[1], 'Package deployed.'); 158 | test.equal(harness.output[2], 'No config updates to make.'); 159 | 160 | test.ok(proxyAgentMock.calledWith(proxy)); 161 | test.done(); 162 | } 163 | }; 164 | 165 | gruntMock.execute(deployTask.getHandler, harnessParams); 166 | }; 167 | 168 | deployTaskTest.testDeployWithoutProxy = function(test) { 169 | test.expect(6); 170 | 171 | var deployTask = require('../../utils/deploy_task'); 172 | 173 | var harnessParams = { 174 | options: {}, 175 | config: defaultGruntConfig, 176 | callback: function(harness) { 177 | test.equal(harness.status, true); 178 | test.equal(harness.output.length, 3); 179 | test.equal(harness.output[0], 'Uploading...'); 180 | test.equal(harness.output[1], 'Package deployed.'); 181 | test.equal(harness.output[2], 'No config updates to make.'); 182 | 183 | test.ok(!proxyAgentMock.called); 184 | test.done(); 185 | } 186 | }; 187 | 188 | gruntMock.execute(deployTask.getHandler, harnessParams); 189 | }; 190 | 191 | deployTaskTest.testProfile = function(test) { 192 | test.expect(3); 193 | 194 | var deployTask = require('../../utils/deploy_task'); 195 | 196 | var harnessParams = { 197 | options: { 198 | profile: 'some-profile' 199 | }, 200 | config: defaultGruntConfig, 201 | callback: function(harness) { 202 | test.equal(harness.status, true); 203 | test.equal(harness.output.length, 3); 204 | 205 | test.ok(awsSDKMock.SharedIniFileCredentials.calledWith({profile: 'some-profile'})); 206 | test.done(); 207 | } 208 | }; 209 | gruntMock.execute(deployTask.getHandler, harnessParams); 210 | }; 211 | 212 | deployTaskTest.testRoleArn = function(test) { 213 | test.expect(3); 214 | 215 | var deployTask = require('../../utils/deploy_task'); 216 | 217 | var harnessParams = { 218 | options: { 219 | RoleArn: 'arn:some:role' 220 | }, 221 | config: defaultGruntConfig, 222 | callback: function(harness) { 223 | test.equal(harness.status, true); 224 | test.equal(harness.output.length, 3); 225 | 226 | test.ok(awsSDKMock.TemporaryCredentials.calledWith({RoleArn: 'arn:some:role'})); 227 | test.done(); 228 | } 229 | }; 230 | gruntMock.execute(deployTask.getHandler, harnessParams); 231 | }; 232 | 233 | deployTaskTest.testAccessKeys = function(test) { 234 | test.expect(3); 235 | 236 | var deployTask = require('../../utils/deploy_task'); 237 | 238 | var harnessParams = { 239 | options: { 240 | accessKeyId: 'some-access-key', 241 | secretAccessKey: 'some-secret-access-key' 242 | }, 243 | config: defaultGruntConfig, 244 | callback: function(harness) { 245 | test.equal(harness.status, true); 246 | test.equal(harness.output.length, 3); 247 | 248 | test.ok(awsSDKMock.config.update.calledWith({accessKeyId: 'some-access-key', 249 | secretAccessKey: 'some-secret-access-key'})); 250 | test.done(); 251 | } 252 | }; 253 | gruntMock.execute(deployTask.getHandler, harnessParams); 254 | }; 255 | 256 | deployTaskTest.testCredentialsJSON = function(test) { 257 | test.expect(3); 258 | 259 | var deployTask = require('../../utils/deploy_task'); 260 | 261 | var harnessParams = { 262 | options: { 263 | credentialsJSON: 'credentials.json' 264 | }, 265 | config: defaultGruntConfig, 266 | callback: function(harness) { 267 | test.equal(harness.status, true); 268 | test.equal(harness.output.length, 3); 269 | 270 | test.ok(awsSDKMock.config.loadFromPath.calledWith('credentials.json')); 271 | test.done(); 272 | } 273 | }; 274 | gruntMock.execute(deployTask.getHandler, harnessParams); 275 | }; 276 | 277 | deployTaskTest.testRegion = function(test) { 278 | test.expect(3); 279 | 280 | var deployTask = require('../../utils/deploy_task'); 281 | 282 | var harnessParams = { 283 | options: { 284 | region: 'mars-north-8' 285 | }, 286 | config: defaultGruntConfig, 287 | callback: function(harness) { 288 | test.equal(harness.status, true); 289 | test.equal(harness.output.length, 3); 290 | 291 | test.ok(awsSDKMock.config.update.calledWith({region: 'mars-north-8'})); 292 | test.done(); 293 | } 294 | }; 295 | gruntMock.execute(deployTask.getHandler, harnessParams); 296 | }; 297 | 298 | deployTaskTest.testTimeout = function(test) { 299 | test.expect(4); 300 | 301 | var deployTask = require('../../utils/deploy_task'); 302 | 303 | var harnessParams = { 304 | options: { 305 | timeout: 3000 306 | }, 307 | config: defaultGruntConfig, 308 | callback: function(harness) { 309 | test.equal(harness.status, true); 310 | test.equal(harness.output.length, 3); 311 | test.equal(harness.output[2], 'Config updated.'); 312 | 313 | test.ok(lambdaAPIMock.updateFunctionConfiguration.calledWithMatch({Timeout: 3000})); 314 | test.done(); 315 | } 316 | }; 317 | gruntMock.execute(deployTask.getHandler, harnessParams); 318 | }; 319 | 320 | deployTaskTest.testMemorySize = function(test) { 321 | test.expect(4); 322 | 323 | var deployTask = require('../../utils/deploy_task'); 324 | 325 | var harnessParams = { 326 | options: { 327 | memory: 1024 328 | }, 329 | config: defaultGruntConfig, 330 | callback: function(harness) { 331 | test.equal(harness.status, true); 332 | test.equal(harness.output.length, 3); 333 | test.equal(harness.output[2], 'Config updated.'); 334 | 335 | test.ok(lambdaAPIMock.updateFunctionConfiguration.calledWithMatch({MemorySize: 1024})); 336 | test.done(); 337 | } 338 | }; 339 | gruntMock.execute(deployTask.getHandler, harnessParams); 340 | }; 341 | 342 | deployTaskTest.testVpcConfig = function(test) { 343 | test.expect(4); 344 | 345 | var deployTask = require('../../utils/deploy_task'); 346 | 347 | var harnessParams = { 348 | options: { 349 | subnetIds: ['subnet-XXXXX'], 350 | securityGroupIds: ['sg-XXXXXX'] 351 | }, 352 | config: defaultGruntConfig, 353 | callback: function(harness) { 354 | test.equal(harness.status, true); 355 | test.equal(harness.output.length, 3); 356 | test.equal(harness.output[2], 'Config updated.'); 357 | 358 | test.ok(lambdaAPIMock.updateFunctionConfiguration.calledWithMatch({VpcConfig: { 359 | SubnetIds : ['subnet-XXXXX'], 360 | SecurityGroupIds : ['sg-XXXXXX'] 361 | }})); 362 | test.done(); 363 | } 364 | }; 365 | gruntMock.execute(deployTask.getHandler, harnessParams); 366 | }; 367 | 368 | deployTaskTest.testHandler = function(test) { 369 | test.expect(4); 370 | 371 | var deployTask = require('../../utils/deploy_task'); 372 | 373 | var harnessParams = { 374 | options: { 375 | handler: 'some-handler' 376 | }, 377 | config: defaultGruntConfig, 378 | callback: function(harness) { 379 | test.equal(harness.status, true); 380 | test.equal(harness.output.length, 3); 381 | test.equal(harness.output[2], 'Config updated.'); 382 | 383 | test.ok(lambdaAPIMock.updateFunctionConfiguration.calledWithMatch({Handler: 'some-handler'})); 384 | test.done(); 385 | } 386 | }; 387 | gruntMock.execute(deployTask.getHandler, harnessParams); 388 | }; 389 | 390 | deployTaskTest.testEnableVersioning = function(test) { 391 | test.expect(5); 392 | 393 | var deployTask = require('../../utils/deploy_task'); 394 | 395 | var harnessParams = { 396 | options: { 397 | enableVersioning: true 398 | }, 399 | config: defaultGruntConfig, 400 | callback: function(harness) { 401 | test.equal(harness.status, true); 402 | test.equal(harness.output.length, 4); 403 | test.equal(harness.output[2], 'No config updates to make.'); 404 | test.equal(harness.output[3], 'Version 5 published.'); 405 | 406 | test.ok(lambdaAPIMock.publishVersion.calledWithMatch({FunctionName: 'some-function', 407 | Description: 'Deployed on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 408 | test.done(); 409 | } 410 | }; 411 | gruntMock.execute(deployTask.getHandler, harnessParams); 412 | }; 413 | 414 | deployTaskTest.testNewAlias = function(test) { 415 | test.expect(6); 416 | 417 | var deployTask = require('../../utils/deploy_task'); 418 | 419 | var harnessParams = { 420 | options: { 421 | aliases: 'beta' 422 | }, 423 | config: defaultGruntConfig, 424 | callback: function(harness) { 425 | test.equal(harness.status, true); 426 | test.equal(harness.output.length, 4); 427 | test.equal(harness.output[2], 'No config updates to make.'); 428 | test.equal(harness.output[3], 'Alias beta updated pointing to version $LATEST.'); 429 | 430 | test.ok(lambdaAPIMock.createAlias.calledWithMatch({FunctionName: 'some-function', Name: 'beta', 431 | FunctionVersion: '$LATEST', Description: 'Deployed on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 432 | test.ok(!lambdaAPIMock.updateAlias.called); 433 | test.done(); 434 | } 435 | }; 436 | gruntMock.execute(deployTask.getHandler, harnessParams); 437 | }; 438 | 439 | deployTaskTest.testUpdatedAlias = function(test) { 440 | test.expect(6); 441 | 442 | lambdaAPIMock.getAlias = sinon.stub().callsArgWithAsync(1, null, {}); 443 | 444 | var deployTask = require('../../utils/deploy_task'); 445 | 446 | var harnessParams = { 447 | options: { 448 | aliases: 'beta' 449 | }, 450 | config: defaultGruntConfig, 451 | callback: function(harness) { 452 | test.equal(harness.status, true); 453 | test.equal(harness.output.length, 4); 454 | test.equal(harness.output[2], 'No config updates to make.'); 455 | test.equal(harness.output[3], 'Alias beta updated pointing to version $LATEST.'); 456 | 457 | test.ok(lambdaAPIMock.updateAlias.calledWithMatch({FunctionName: 'some-function', Name: 'beta', 458 | FunctionVersion: '$LATEST', Description: 'Deployed on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 459 | test.ok(!lambdaAPIMock.createAlias.called); 460 | test.done(); 461 | } 462 | }; 463 | gruntMock.execute(deployTask.getHandler, harnessParams); 464 | }; 465 | 466 | deployTaskTest.testAliasAndVersion = function(test) { 467 | test.expect(6); 468 | 469 | var deployTask = require('../../utils/deploy_task'); 470 | 471 | var harnessParams = { 472 | options: { 473 | aliases: 'beta', 474 | enableVersioning: true 475 | }, 476 | config: defaultGruntConfig, 477 | callback: function(harness) { 478 | test.equal(harness.status, true); 479 | test.equal(harness.output.length, 5); 480 | test.equal(harness.output[2], 'No config updates to make.'); 481 | test.equal(harness.output[3], 'Version 5 published.'); 482 | test.equal(harness.output[4], 'Alias beta updated pointing to version 5.'); 483 | 484 | test.ok(lambdaAPIMock.createAlias.calledWithMatch({FunctionName: 'some-function', Name: 'beta', 485 | FunctionVersion: 5, Description: 'Deployed on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 486 | test.done(); 487 | } 488 | }; 489 | gruntMock.execute(deployTask.getHandler, harnessParams); 490 | }; 491 | 492 | deployTaskTest.testEnablePackageVersionAlias = function(test) { 493 | test.expect(6); 494 | 495 | var deployTask = require('../../utils/deploy_task'); 496 | 497 | defaultGruntConfig['lambda_deploy.fake-target.version'] = '1.2.3'; 498 | 499 | var harnessParams = { 500 | options: { 501 | enablePackageVersionAlias: true 502 | }, 503 | config: defaultGruntConfig, 504 | callback: function(harness) { 505 | test.equal(harness.status, true); 506 | test.equal(harness.output.length, 4); 507 | test.equal(harness.output[2], 'No config updates to make.'); 508 | test.equal(harness.output[3], 'Alias 1-2-3 updated pointing to version $LATEST.'); 509 | 510 | test.ok(lambdaAPIMock.createAlias.calledWithMatch({FunctionName: 'some-function', Name: '1-2-3', 511 | FunctionVersion: '$LATEST', Description: 'Deployed version 1.2.3 on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 512 | test.ok(!lambdaAPIMock.updateAlias.called); 513 | test.done(); 514 | } 515 | }; 516 | gruntMock.execute(deployTask.getHandler, harnessParams); 517 | }; 518 | 519 | deployTaskTest.testMultipleAliases = function(test) { 520 | test.expect(9); 521 | 522 | var deployTask = require('../../utils/deploy_task'); 523 | 524 | var harnessParams = { 525 | options: { 526 | aliases: [ 527 | 'foo', 528 | 'bar', 529 | 'baz' 530 | ] 531 | }, 532 | config: defaultGruntConfig, 533 | callback: function(harness) { 534 | test.equal(harness.status, true); 535 | test.equal(harness.output.length, 6); 536 | test.equal(harness.output[2], 'No config updates to make.'); 537 | test.equal(harness.output[3], 'Alias foo updated pointing to version $LATEST.'); 538 | test.equal(harness.output[4], 'Alias bar updated pointing to version $LATEST.'); 539 | test.equal(harness.output[5], 'Alias baz updated pointing to version $LATEST.'); 540 | 541 | test.ok(lambdaAPIMock.createAlias.calledWithMatch({FunctionName: 'some-function', Name: 'foo', 542 | FunctionVersion: '$LATEST', Description: 'Deployed on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 543 | test.ok(lambdaAPIMock.createAlias.calledWithMatch({FunctionName: 'some-function', Name: 'bar', 544 | FunctionVersion: '$LATEST', Description: 'Deployed on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 545 | test.ok(lambdaAPIMock.createAlias.calledWithMatch({FunctionName: 'some-function', Name: 'baz', 546 | FunctionVersion: '$LATEST', Description: 'Deployed on Sat Feb 13 2016 21:46:15 GMT-0800 (PST)'})); 547 | test.done(); 548 | } 549 | }; 550 | gruntMock.execute(deployTask.getHandler, harnessParams); 551 | }; 552 | 553 | module.exports = deployTaskTest; 554 | -------------------------------------------------------------------------------- /test/unit/invoke_task_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | ======== A Handy Little Nodeunit Reference ======== 5 | https://github.com/caolan/nodeunit 6 | 7 | Test methods: 8 | test.expect(numAssertions) 9 | test.done() 10 | Test assertions: 11 | test.ok(value, [message]) 12 | test.equal(actual, expected, [message]) 13 | test.notEqual(actual, expected, [message]) 14 | test.deepEqual(actual, expected, [message]) 15 | test.notDeepEqual(actual, expected, [message]) 16 | test.strictEqual(actual, expected, [message]) 17 | test.notStrictEqual(actual, expected, [message]) 18 | test.throws(block, [error], [message]) 19 | test.doesNotThrow(block, [error], [message]) 20 | test.ifError(value) 21 | */ 22 | 23 | var mockery = require('mockery'); 24 | var path = require('path'); 25 | 26 | mockery.registerAllowable('../../utils/invoke_task'); 27 | var gruntMock = require('../utils/grunt_mock'); 28 | var fsMock = require('../utils/fs_mock'); 29 | 30 | var invokeTaskTests = {}; 31 | 32 | var setLambdaFunction = function(lambda, file_name, handler) { 33 | if(!file_name) { 34 | file_name = 'index.js'; 35 | } 36 | if(!handler) { 37 | handler = 'handler'; 38 | } 39 | var lambdaContainer = {}; 40 | lambdaContainer[handler] = lambda; 41 | mockery.registerMock(path.resolve(file_name), lambdaContainer); 42 | }; 43 | 44 | invokeTaskTests.setUp = function(done) { 45 | mockery.enable({ 46 | warnOnReplace: false, 47 | warnOnUnregistered: false, 48 | useCleanCache: true 49 | }); 50 | fsMock.reset(); 51 | mockery.registerMock('fs', fsMock); 52 | fsMock.setJSONContent('event.json', { 53 | 'thisFile': 'event.json' 54 | }); 55 | fsMock.setJSONContent('client_context.json', { 56 | 'thisFile': 'client_context.json' 57 | }); 58 | fsMock.setJSONContent('identity.json', { 59 | 'thisFile': 'identity.json' 60 | }); 61 | done(); 62 | }; 63 | 64 | invokeTaskTests.tearDown = function(done) { 65 | mockery.disable(); 66 | done(); 67 | }; 68 | 69 | invokeTaskTests.testLambdaEnvironment = function(test) { 70 | test.expect(12); 71 | 72 | setLambdaFunction(function(event, context) { 73 | test.equals(event.thisFile, 'event.json'); 74 | test.notEqual(context.done, undefined); 75 | test.notEqual(context.succeed, undefined); 76 | test.notEqual(context.fail, undefined); 77 | test.equals(context.awsRequestId, 'LAMBDA_INVOKE'); 78 | test.equals(context.logStreamName, 'LAMBDA_INVOKE'); 79 | test.equals(context.clientContext.thisFile, 'client_context.json'); 80 | test.equals(context.identity.thisFile, 'identity.json'); 81 | context.succeed('My Message'); 82 | }); 83 | 84 | var invokeTask = require('../../utils/invoke_task'); 85 | 86 | var harnessParams = { 87 | options: {}, 88 | callback: function(harness) { 89 | test.equal(harness.status, true); 90 | test.equal(harness.output.length, 5); 91 | test.equal(harness.output[2], 'Success! Message:'); 92 | test.equal(harness.output[4], 'My Message'); 93 | test.done(); 94 | } 95 | }; 96 | gruntMock.execute(invokeTask.getHandler, harnessParams); 97 | }; 98 | 99 | invokeTaskTests.testDoneSucceed = function(test) { 100 | test.expect(4); 101 | 102 | setLambdaFunction(function(event, context) { 103 | context.done(null, 'My Message'); 104 | }); 105 | 106 | var invokeTask = require('../../utils/invoke_task'); 107 | 108 | var harnessParams = { 109 | options: {}, 110 | callback: function(harness) { 111 | test.equal(harness.status, true); 112 | test.equal(harness.output.length, 5); 113 | test.equal(harness.output[2], 'Success! Message:'); 114 | test.equal(harness.output[4], 'My Message'); 115 | test.done(); 116 | } 117 | }; 118 | gruntMock.execute(invokeTask.getHandler, harnessParams); 119 | }; 120 | 121 | invokeTaskTests.testDoneWithObjectStatus = function(test) { 122 | test.expect(4); 123 | 124 | setLambdaFunction(function(event, context) { 125 | context.done(null, {some: "object"}); 126 | }); 127 | 128 | var invokeTask = require('../../utils/invoke_task'); 129 | 130 | var harnessParams = { 131 | options: {}, 132 | callback: function(harness) { 133 | test.equal(harness.status, true); 134 | test.equal(harness.output.length, 5); 135 | test.equal(harness.output[2], 'Success! Message:'); 136 | test.equal(harness.output[4], '{"some":"object"}'); 137 | test.done(); 138 | } 139 | }; 140 | gruntMock.execute(invokeTask.getHandler, harnessParams); 141 | }; 142 | 143 | invokeTaskTests.testDoneUndefined = function(test) { 144 | test.expect(4); 145 | 146 | setLambdaFunction(function(event, context) { 147 | var notDefined; 148 | context.done(notDefined, 'My Message'); 149 | }); 150 | 151 | var invokeTask = require('../../utils/invoke_task'); 152 | 153 | var harnessParams = { 154 | options: {}, 155 | callback: function(harness) { 156 | test.equal(harness.status, true); 157 | test.equal(harness.output.length, 5); 158 | test.equal(harness.output[2], 'Success! Message:'); 159 | test.equal(harness.output[4], 'My Message'); 160 | test.done(); 161 | } 162 | }; 163 | gruntMock.execute(invokeTask.getHandler, harnessParams); 164 | }; 165 | 166 | invokeTaskTests.testDoneError = function(test) { 167 | test.expect(4); 168 | 169 | setLambdaFunction(function(event, context) { 170 | var error = {message: 'Some Error'}; 171 | context.done(error, 'My Message'); 172 | }); 173 | 174 | var invokeTask = require('../../utils/invoke_task'); 175 | 176 | var harnessParams = { 177 | options: {}, 178 | callback: function(harness) { 179 | test.equal(harness.status, false); 180 | test.equal(harness.output.length, 5); 181 | test.equal(harness.output[2], 'Failure! Message:'); 182 | test.equal(harness.output[4], '{"message":"Some Error"}'); 183 | test.done(); 184 | } 185 | }; 186 | gruntMock.execute(invokeTask.getHandler, harnessParams); 187 | }; 188 | 189 | invokeTaskTests.testFail = function(test) { 190 | test.expect(4); 191 | 192 | setLambdaFunction(function(event, context) { 193 | var error = {message: 'Some Error'}; 194 | context.fail(error); 195 | }); 196 | 197 | var invokeTask = require('../../utils/invoke_task'); 198 | 199 | var harnessParams = { 200 | options: {}, 201 | callback: function(harness) { 202 | test.equal(harness.status, false); 203 | test.equal(harness.output.length, 5); 204 | test.equal(harness.output[2], 'Failure! Message:'); 205 | test.equal(harness.output[4], '{"message":"Some Error"}'); 206 | test.done(); 207 | } 208 | }; 209 | gruntMock.execute(invokeTask.getHandler, harnessParams); 210 | }; 211 | 212 | invokeTaskTests.testFileName = function(test) { 213 | test.expect(4); 214 | 215 | setLambdaFunction(function(event, context) { 216 | context.succeed('My Message'); 217 | }, 'something.js'); 218 | 219 | var invokeTask = require('../../utils/invoke_task'); 220 | 221 | var harnessParams = { 222 | options: { 223 | 'file_name': 'something.js' 224 | }, 225 | callback: function(harness) { 226 | test.equal(harness.status, true); 227 | test.equal(harness.output.length, 5); 228 | test.equal(harness.output[2], 'Success! Message:'); 229 | test.equal(harness.output[4], 'My Message'); 230 | test.done(); 231 | } 232 | }; 233 | gruntMock.execute(invokeTask.getHandler, harnessParams); 234 | }; 235 | 236 | invokeTaskTests.testHandler = function(test) { 237 | test.expect(4); 238 | 239 | setLambdaFunction(function(event, context) { 240 | context.succeed('My Message'); 241 | }, 'index.js', 'something'); 242 | 243 | var invokeTask = require('../../utils/invoke_task'); 244 | 245 | var harnessParams = { 246 | options: { 247 | 'handler': 'something' 248 | }, 249 | callback: function(harness) { 250 | test.equal(harness.status, true); 251 | test.equal(harness.output.length, 5); 252 | test.equal(harness.output[2], 'Success! Message:'); 253 | test.equal(harness.output[4], 'My Message'); 254 | test.done(); 255 | } 256 | }; 257 | gruntMock.execute(invokeTask.getHandler, harnessParams); 258 | }; 259 | 260 | invokeTaskTests.testFileName = function(test) { 261 | test.expect(4); 262 | 263 | setLambdaFunction(function(event, context) { 264 | context.succeed('My Message'); 265 | }, 'something.js'); 266 | 267 | var invokeTask = require('../../utils/invoke_task'); 268 | 269 | var harnessParams = { 270 | options: { 271 | 'file_name': 'something.js' 272 | }, 273 | callback: function(harness) { 274 | test.equal(harness.status, true); 275 | test.equal(harness.output.length, 5); 276 | test.equal(harness.output[2], 'Success! Message:'); 277 | test.equal(harness.output[4], 'My Message'); 278 | test.done(); 279 | } 280 | }; 281 | gruntMock.execute(invokeTask.getHandler, harnessParams); 282 | }; 283 | 284 | invokeTaskTests.testEvent = function(test) { 285 | test.expect(5); 286 | 287 | fsMock.setJSONContent('hello.json', { 288 | 'thisFile': 'hello.json' 289 | }); 290 | 291 | setLambdaFunction(function(event, context) { 292 | test.equals(event.thisFile, 'hello.json'); 293 | context.succeed('My Message'); 294 | }); 295 | 296 | var invokeTask = require('../../utils/invoke_task'); 297 | 298 | var harnessParams = { 299 | options: { 300 | 'event': 'hello.json' 301 | }, 302 | callback: function(harness) { 303 | test.equal(harness.status, true); 304 | test.equal(harness.output.length, 5); 305 | test.equal(harness.output[2], 'Success! Message:'); 306 | test.equal(harness.output[4], 'My Message'); 307 | test.done(); 308 | } 309 | }; 310 | gruntMock.execute(invokeTask.getHandler, harnessParams); 311 | }; 312 | 313 | invokeTaskTests.testClientContext = function(test) { 314 | test.expect(5); 315 | 316 | fsMock.setJSONContent('something.json', { 317 | 'thisFile': 'something.json' 318 | }); 319 | 320 | setLambdaFunction(function(event, context) { 321 | test.equals(context.clientContext.thisFile, 'something.json'); 322 | context.succeed('My Message'); 323 | }); 324 | 325 | var invokeTask = require('../../utils/invoke_task'); 326 | 327 | var harnessParams = { 328 | options: { 329 | 'client_context': 'something.json' 330 | }, 331 | callback: function(harness) { 332 | test.equal(harness.status, true); 333 | test.equal(harness.output.length, 5); 334 | test.equal(harness.output[2], 'Success! Message:'); 335 | test.equal(harness.output[4], 'My Message'); 336 | test.done(); 337 | } 338 | }; 339 | gruntMock.execute(invokeTask.getHandler, harnessParams); 340 | }; 341 | 342 | invokeTaskTests.testIdentity = function(test) { 343 | test.expect(5); 344 | 345 | fsMock.setJSONContent('something.json', { 346 | 'thisFile': 'something.json' 347 | }); 348 | 349 | setLambdaFunction(function(event, context) { 350 | test.equals(context.identity.thisFile, 'something.json'); 351 | context.succeed('My Message'); 352 | }); 353 | 354 | var invokeTask = require('../../utils/invoke_task'); 355 | 356 | var harnessParams = { 357 | options: { 358 | 'identity': 'something.json' 359 | }, 360 | callback: function(harness) { 361 | test.equal(harness.status, true); 362 | test.equal(harness.output.length, 5); 363 | test.equal(harness.output[2], 'Success! Message:'); 364 | test.equal(harness.output[4], 'My Message'); 365 | test.done(); 366 | } 367 | }; 368 | gruntMock.execute(invokeTask.getHandler, harnessParams); 369 | }; 370 | 371 | invokeTaskTests.testPackageFolder = function(test) { 372 | test.expect(6); 373 | 374 | var original = process.cwd(); 375 | var tmpDir = path.resolve('./tmp'); 376 | 377 | setLambdaFunction(function(event, context) { 378 | test.equal(process.cwd(), tmpDir); 379 | context.done(null, 'My Message'); 380 | }, './tmp/index.js'); 381 | 382 | var invokeTask = require('../../utils/invoke_task'); 383 | 384 | var harnessParams = { 385 | options: { 386 | 'package_folder': './tmp' 387 | }, 388 | callback: function(harness) { 389 | test.equal(process.cwd(), original); 390 | test.equal(harness.status, true); 391 | test.equal(harness.output.length, 5); 392 | test.equal(harness.output[2], 'Success! Message:'); 393 | test.equal(harness.output[4], 'My Message'); 394 | test.done(); 395 | } 396 | }; 397 | gruntMock.execute(invokeTask.getHandler, harnessParams); 398 | }; 399 | 400 | invokeTaskTests.testNoClientContext = function(test) { 401 | test.expect(5); 402 | 403 | setLambdaFunction(function(event, context) { 404 | test.equal(context.clientContext, null); 405 | context.done(null, 'My Message'); 406 | }); 407 | 408 | var invokeTask = require('../../utils/invoke_task'); 409 | 410 | var harnessParams = { 411 | options: { 412 | 'client_context': 'not-a-file.json' 413 | }, 414 | callback: function(harness) { 415 | test.equal(harness.status, true); 416 | test.equal(harness.output.length, 5); 417 | test.equal(harness.output[2], 'Success! Message:'); 418 | test.equal(harness.output[4], 'My Message'); 419 | test.done(); 420 | } 421 | }; 422 | gruntMock.execute(invokeTask.getHandler, harnessParams); 423 | }; 424 | 425 | invokeTaskTests.testNoIdentity = function(test) { 426 | test.expect(5); 427 | 428 | setLambdaFunction(function(event, context) { 429 | test.equal(context.identity, null); 430 | context.done(null, 'My Message'); 431 | }); 432 | 433 | var invokeTask = require('../../utils/invoke_task'); 434 | 435 | var harnessParams = { 436 | options: { 437 | 'identity': 'not-a-file.json' 438 | }, 439 | callback: function(harness) { 440 | test.equal(harness.status, true); 441 | test.equal(harness.output.length, 5); 442 | test.equal(harness.output[2], 'Success! Message:'); 443 | test.equal(harness.output[4], 'My Message'); 444 | test.done(); 445 | } 446 | }; 447 | gruntMock.execute(invokeTask.getHandler, harnessParams); 448 | }; 449 | 450 | invokeTaskTests.testCallbackSucceed = function(test) { 451 | test.expect(4); 452 | 453 | setLambdaFunction(function(event, context, callback) { 454 | callback(null, 'My Message'); 455 | }); 456 | 457 | var invokeTask = require('../../utils/invoke_task'); 458 | var harnessParams = { 459 | options: {}, 460 | callback: function(harness) { 461 | test.equal(harness.status, true); 462 | test.equal(harness.output.length, 5); 463 | test.equal(harness.output[2], 'Success! Message:'); 464 | test.equal(harness.output[4], 'My Message'); 465 | test.done(); 466 | } 467 | }; 468 | gruntMock.execute(invokeTask.getHandler, harnessParams); 469 | }; 470 | 471 | invokeTaskTests.testCallbackWithObjectStatus = function(test) { 472 | test.expect(4); 473 | 474 | setLambdaFunction(function(event, context, callback) { 475 | callback(null, {some: "object"}); 476 | }); 477 | 478 | var invokeTask = require('../../utils/invoke_task'); 479 | 480 | var harnessParams = { 481 | options: {}, 482 | callback: function(harness) { 483 | test.equal(harness.status, true); 484 | test.equal(harness.output.length, 5); 485 | test.equal(harness.output[2], 'Success! Message:'); 486 | test.equal(harness.output[4], '{"some":"object"}'); 487 | test.done(); 488 | } 489 | }; 490 | gruntMock.execute(invokeTask.getHandler, harnessParams); 491 | }; 492 | 493 | invokeTaskTests.testCallbackUndefined = function(test) { 494 | test.expect(4); 495 | 496 | setLambdaFunction(function(event, context, callback) { 497 | var notDefined; 498 | callback(notDefined, 'My Message'); 499 | }); 500 | 501 | var invokeTask = require('../../utils/invoke_task'); 502 | 503 | var harnessParams = { 504 | options: {}, 505 | callback: function(harness) { 506 | test.equal(harness.status, true); 507 | test.equal(harness.output.length, 5); 508 | test.equal(harness.output[2], 'Success! Message:'); 509 | test.equal(harness.output[4], 'My Message'); 510 | test.done(); 511 | } 512 | }; 513 | gruntMock.execute(invokeTask.getHandler, harnessParams); 514 | }; 515 | 516 | invokeTaskTests.testCallbackError = function(test) { 517 | test.expect(4); 518 | 519 | setLambdaFunction(function(event, context, callback) { 520 | var error = {message: 'Some Error'}; 521 | callback(error, 'My Message'); 522 | }); 523 | 524 | var invokeTask = require('../../utils/invoke_task'); 525 | 526 | var harnessParams = { 527 | options: {}, 528 | callback: function(harness) { 529 | test.equal(harness.status, false); 530 | test.equal(harness.output.length, 5); 531 | test.equal(harness.output[2], 'Failure! Message:'); 532 | test.equal(harness.output[4], '{"message":"Some Error"}'); 533 | test.done(); 534 | } 535 | }; 536 | gruntMock.execute(invokeTask.getHandler, harnessParams); 537 | }; 538 | 539 | 540 | module.exports = invokeTaskTests; -------------------------------------------------------------------------------- /test/unit/package_task_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * grunt-aws-lambda 5 | * https://github.com/Tim-B/grunt-aws-lambda 6 | * 7 | * Copyright (c) 2014 Tim-B 8 | * Licensed under the MIT license. 9 | */ 10 | 11 | /* 12 | ======== A Handy Little Nodeunit Reference ======== 13 | https://github.com/caolan/nodeunit 14 | 15 | Test methods: 16 | test.expect(numAssertions) 17 | test.done() 18 | Test assertions: 19 | test.ok(value, [message]) 20 | test.equal(actual, expected, [message]) 21 | test.notEqual(actual, expected, [message]) 22 | test.deepEqual(actual, expected, [message]) 23 | test.notDeepEqual(actual, expected, [message]) 24 | test.strictEqual(actual, expected, [message]) 25 | test.notStrictEqual(actual, expected, [message]) 26 | test.throws(block, [error], [message]) 27 | test.doesNotThrow(block, [error], [message]) 28 | test.ifError(value) 29 | */ 30 | 31 | var mockery = require('mockery'); 32 | var path = require('path'); 33 | var sinon = require('sinon'); 34 | 35 | mockery.registerAllowable('../../utils/invoke_task'); 36 | var gruntMock = require('../utils/grunt_mock'); 37 | var fsMock = require('../utils/fs_mock'); 38 | 39 | var packageTaskTest = {}; 40 | 41 | var mkdirpStub, 42 | rimrafStub, 43 | zipAPI, 44 | npmAPI; 45 | 46 | packageTaskTest.setUp = function(done) { 47 | 48 | zipAPI = { 49 | pipe: sinon.stub(), 50 | bulk: sinon.stub(), 51 | finalize: sinon.stub(), 52 | _normalizeEntryData: sinon.stub() 53 | }; 54 | 55 | npmAPI = { 56 | commands: { 57 | install: sinon.stub().callsArgAsync(2) 58 | }, 59 | config: { 60 | set: sinon.stub() 61 | }, 62 | load: sinon.stub() 63 | }; 64 | 65 | npmAPI.load.callsArgWithAsync(1, null, npmAPI); 66 | 67 | mockery.enable({ 68 | warnOnReplace: false, 69 | warnOnUnregistered: false, 70 | useCleanCache: true 71 | }); 72 | 73 | fsMock.reset(); 74 | mockery.registerMock('fs', fsMock); 75 | fsMock.setJSONContent('package.json', { 76 | 'name': 'some-npm-package', 77 | 'version': '1.1.1' 78 | }); 79 | 80 | mockery.registerMock('npm', npmAPI); 81 | 82 | mockery.registerMock('archiver', function(type) { 83 | return zipAPI; 84 | }); 85 | 86 | mkdirpStub = sinon.stub().callsArgAsync(1); 87 | mockery.registerMock('mkdirp', mkdirpStub); 88 | 89 | rimrafStub = sinon.stub().callsArgAsync(1); 90 | mockery.registerMock('rimraf', rimrafStub); 91 | 92 | mockery.registerMock('temporary', { 93 | Dir: function () { 94 | return { 95 | path: 'temp-dir' 96 | }; 97 | } 98 | }); 99 | 100 | fsMock.createWriteStream = sinon.stub().returns({ 101 | on: function(event, callback) { 102 | callback(); 103 | } 104 | }); 105 | 106 | var dateFacadeMock = { 107 | getFormattedTimestamp: sinon.stub().returns('2016-1-16-2-22-16') 108 | }; 109 | mockery.registerMock('./date_facade', dateFacadeMock); 110 | 111 | done(); 112 | }; 113 | 114 | packageTaskTest.tearDown = function(done) { 115 | mockery.disable(); 116 | done(); 117 | }; 118 | 119 | packageTaskTest.testDoneSucceed = function(test) { 120 | test.expect(10); 121 | 122 | var packageTask = require('../../utils/package_task'); 123 | 124 | var harnessParams = { 125 | options: {}, 126 | callback: function(harness) { 127 | test.equal(harness.status, true); 128 | test.equal(harness.output.length, 1); 129 | test.equal(harness.output[0], 'Created package at ./dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 130 | test.ok(npmAPI.commands.install.calledWith('temp-dir', './')); 131 | test.ok(zipAPI.bulk.calledWithMatch(sinon.match(function(value) { 132 | return value[0].cwd === 'temp-dir/node_modules/some-npm-package'; 133 | }))); 134 | test.ok(mkdirpStub.calledWith('./dist')); 135 | test.ok(rimrafStub.calledWith('temp-dir')); 136 | test.ok(fsMock.createWriteStream.calledWith('temp-dir/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 137 | test.ok(fsMock.createWriteStream.calledWith('./dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 138 | test.equal(harness.config['lambda_deploy.fake-target.package'], './dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 139 | test.done(); 140 | } 141 | }; 142 | gruntMock.execute(packageTask.getHandler, harnessParams); 143 | }; 144 | 145 | packageTaskTest.testDistFolder = function(test) { 146 | test.expect(10); 147 | 148 | var packageTask = require('../../utils/package_task'); 149 | 150 | var harnessParams = { 151 | options: { 152 | 'dist_folder': 'another/folder' 153 | }, 154 | callback: function(harness) { 155 | test.equal(harness.status, true); 156 | test.equal(harness.output.length, 1); 157 | test.equal(harness.output[0], 'Created package at ./another/folder/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 158 | test.ok(npmAPI.commands.install.calledWith('temp-dir', './')); 159 | test.ok(zipAPI.bulk.calledWithMatch(sinon.match(function(value) { 160 | return value[0].cwd === 'temp-dir/node_modules/some-npm-package'; 161 | }))); 162 | test.ok(mkdirpStub.calledWith('./another/folder')); 163 | test.ok(rimrafStub.calledWith('temp-dir')); 164 | test.ok(fsMock.createWriteStream.calledWith('temp-dir/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 165 | test.ok(fsMock.createWriteStream.calledWith('./another/folder/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 166 | test.equal(harness.config['lambda_deploy.fake-target.package'], './another/folder/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 167 | test.done(); 168 | } 169 | }; 170 | gruntMock.execute(packageTask.getHandler, harnessParams); 171 | }; 172 | 173 | packageTaskTest.testIncludeTime = function(test) { 174 | test.expect(10); 175 | 176 | var packageTask = require('../../utils/package_task'); 177 | 178 | var harnessParams = { 179 | options: { 180 | 'include_time': false 181 | }, 182 | callback: function(harness) { 183 | test.equal(harness.status, true); 184 | test.equal(harness.output.length, 1); 185 | test.equal(harness.output[0], 'Created package at ./dist/some-npm-package_1-1-1_latest.zip'); 186 | test.ok(npmAPI.commands.install.calledWith('temp-dir', './')); 187 | test.ok(zipAPI.bulk.calledWithMatch(sinon.match(function(value) { 188 | return value[0].cwd === 'temp-dir/node_modules/some-npm-package'; 189 | }))); 190 | test.ok(mkdirpStub.calledWith('./dist')); 191 | test.ok(rimrafStub.calledWith('temp-dir')); 192 | test.ok(fsMock.createWriteStream.calledWith('temp-dir/some-npm-package_1-1-1_latest.zip')); 193 | test.ok(fsMock.createWriteStream.calledWith('./dist/some-npm-package_1-1-1_latest.zip')); 194 | test.equal(harness.config['lambda_deploy.fake-target.package'], './dist/some-npm-package_1-1-1_latest.zip'); 195 | test.done(); 196 | } 197 | }; 198 | gruntMock.execute(packageTask.getHandler, harnessParams); 199 | }; 200 | 201 | packageTaskTest.testPackageFolder = function(test) { 202 | test.expect(10); 203 | 204 | var packageTask = require('../../utils/package_task'); 205 | 206 | var harnessParams = { 207 | options: { 208 | 'package_folder': './anotherfolder' 209 | }, 210 | callback: function(harness) { 211 | test.equal(harness.status, true); 212 | test.equal(harness.output.length, 1); 213 | test.equal(harness.output[0], 'Created package at ./dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 214 | test.ok(npmAPI.commands.install.calledWith('temp-dir', './anotherfolder')); 215 | test.ok(zipAPI.bulk.calledWithMatch(sinon.match(function(value) { 216 | return value[0].cwd === 'temp-dir/node_modules/some-npm-package'; 217 | }))); 218 | test.ok(mkdirpStub.calledWith('./dist')); 219 | test.ok(rimrafStub.calledWith('temp-dir')); 220 | test.ok(fsMock.createWriteStream.calledWith('temp-dir/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 221 | test.ok(fsMock.createWriteStream.calledWith('./dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 222 | test.equal(harness.config['lambda_deploy.fake-target.package'], './dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 223 | test.done(); 224 | } 225 | }; 226 | gruntMock.execute(packageTask.getHandler, harnessParams); 227 | }; 228 | 229 | packageTaskTest.testIncludeFiles = function(test) { 230 | test.expect(11); 231 | 232 | var packageTask = require('../../utils/package_task'); 233 | 234 | var harnessParams = { 235 | options: { 236 | 'include_files': [ 237 | 'foo/bar.txt' 238 | ] 239 | }, 240 | callback: function(harness) { 241 | test.equal(harness.status, true); 242 | test.equal(harness.output.length, 1); 243 | test.equal(harness.output[0], 'Created package at ./dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 244 | test.ok(npmAPI.commands.install.calledWith('temp-dir', './')); 245 | test.ok(zipAPI.bulk.calledWithMatch(sinon.match(function(value) { 246 | return value[0].cwd === 'temp-dir/node_modules/some-npm-package'; 247 | }))); 248 | test.ok(zipAPI.bulk.calledWithMatch(sinon.match(function(value) { 249 | return value[0].src[0] === 'foo/bar.txt'; 250 | }))); 251 | test.ok(mkdirpStub.calledWith('./dist')); 252 | test.ok(rimrafStub.calledWith('temp-dir')); 253 | test.ok(fsMock.createWriteStream.calledWith('temp-dir/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 254 | test.ok(fsMock.createWriteStream.calledWith('./dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip')); 255 | test.equal(harness.config['lambda_deploy.fake-target.package'], './dist/some-npm-package_1-1-1_2016-1-16-2-22-16.zip'); 256 | test.done(); 257 | } 258 | }; 259 | gruntMock.execute(packageTask.getHandler, harnessParams); 260 | }; 261 | 262 | packageTaskTest.testIncludeVersion = function(test) { 263 | test.expect(10); 264 | 265 | var packageTask = require('../../utils/package_task'); 266 | 267 | var harnessParams = { 268 | options: { 269 | 'include_version': false 270 | }, 271 | callback: function(harness) { 272 | test.equal(harness.status, true); 273 | test.equal(harness.output.length, 1); 274 | test.equal(harness.output[0], 'Created package at ./dist/some-npm-package_2016-1-16-2-22-16.zip'); 275 | test.ok(npmAPI.commands.install.calledWith('temp-dir', './')); 276 | test.ok(zipAPI.bulk.calledWithMatch(sinon.match(function(value) { 277 | return value[0].cwd === 'temp-dir/node_modules/some-npm-package'; 278 | }))); 279 | test.ok(mkdirpStub.calledWith('./dist')); 280 | test.ok(rimrafStub.calledWith('temp-dir')); 281 | test.ok(fsMock.createWriteStream.calledWith('temp-dir/some-npm-package_2016-1-16-2-22-16.zip')); 282 | test.ok(fsMock.createWriteStream.calledWith('./dist/some-npm-package_2016-1-16-2-22-16.zip')); 283 | test.equal(harness.config['lambda_deploy.fake-target.package'], './dist/some-npm-package_2016-1-16-2-22-16.zip'); 284 | test.done(); 285 | } 286 | }; 287 | gruntMock.execute(packageTask.getHandler, harnessParams); 288 | }; 289 | 290 | module.exports = packageTaskTest; -------------------------------------------------------------------------------- /test/utils/fs_mock.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * grunt-aws-lambda 5 | * https://github.com/Tim-B/grunt-aws-lambda 6 | * 7 | * Copyright (c) 2014 Tim-B 8 | * Licensed under the MIT license. 9 | */ 10 | 11 | var fsMock = {}; 12 | var files = {}; 13 | var writtenFiles = {}; 14 | 15 | var findFile = function (wanted) { 16 | for(var file in files) { 17 | if(wanted.indexOf(file, wanted.length - file.length) !== -1) { 18 | return files[file]; 19 | } 20 | } 21 | var exception = new Error(); 22 | exception.code = 'ENOENT'; 23 | throw exception; 24 | }; 25 | 26 | fsMock.readFileSync = function (file) { 27 | var content = findFile(file); 28 | return content; 29 | }; 30 | 31 | fsMock.createReadStream = function (path) { 32 | return { 33 | pipe: function (dir) { 34 | 35 | } 36 | }; 37 | }; 38 | 39 | fsMock.readFile = function(file, callback) { 40 | callback(null, findFile(file)); 41 | }; 42 | 43 | fsMock.setFileContent = function (suffix, content) { 44 | files[suffix] = content; 45 | }; 46 | 47 | fsMock.setJSONContent = function (suffix, json) { 48 | fsMock.setFileContent(suffix, JSON.stringify(json)); 49 | }; 50 | 51 | fsMock.reset = function() { 52 | files = {}; 53 | }; 54 | 55 | module.exports = fsMock; -------------------------------------------------------------------------------- /test/utils/grunt_mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-aws-lambda 3 | * https://github.com/Tim-B/grunt-aws-lambda 4 | * 5 | * Copyright (c) 2014 Tim-B 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 10 | 'use strict'; 11 | 12 | var gruntMock = {}; 13 | 14 | var fakeGrunt = function (harness) { 15 | this.log = { 16 | writeln: function(value) { 17 | harness.output.push(value); 18 | } 19 | }; 20 | this.config = { 21 | set: function(key, value) { 22 | harness.config[key] = value; 23 | }, 24 | requires: function(key) { 25 | 26 | }, 27 | get: function(key) { 28 | return harness.config[key]; 29 | } 30 | }; 31 | }; 32 | 33 | var fakeOptions = function (gruntFileOptions) { 34 | return function (baseOptions) { 35 | for(var key in gruntFileOptions) { 36 | baseOptions[key] = gruntFileOptions[key]; 37 | } 38 | return baseOptions; 39 | }; 40 | }; 41 | 42 | var fakeAsync = function(callback, harness) { 43 | return function() { 44 | return function(status) { 45 | harness.status = status; 46 | callback(harness); 47 | }; 48 | }; 49 | }; 50 | 51 | gruntMock.execute = function(handler, params) { 52 | 53 | var harness = { 54 | output: [], 55 | config: {}, 56 | status: null 57 | }; 58 | 59 | if(params.config) { 60 | harness.config = params.config; 61 | } 62 | 63 | var NewHandler = handler(new fakeGrunt(harness)); 64 | NewHandler.prototype.options = fakeOptions(params.options); 65 | NewHandler.prototype.async = fakeAsync(params.callback, harness); 66 | NewHandler.prototype.target = 'fake-target'; 67 | 68 | new NewHandler(); 69 | }; 70 | 71 | module.exports = gruntMock; -------------------------------------------------------------------------------- /test/utils/npm_mock.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-aws-lambda 3 | * https://github.com/Tim-B/grunt-aws-lambda 4 | * 5 | * Copyright (c) 2014 Tim-B 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | var npmMock = {}; 10 | 11 | var params = {}; 12 | 13 | npmMock.load = function(options, callback) { 14 | callback(null, npmMock); 15 | }; 16 | 17 | npmMock.commands = { 18 | install: function(location, folder, callback) { 19 | //params.location = location; 20 | //params.folder = folder; 21 | //callback(); 22 | } 23 | }; 24 | 25 | npmMock.config = { 26 | set: function(key, value) {} 27 | }; 28 | 29 | npmMock.reset = function() { 30 | params = {}; 31 | }; 32 | 33 | module.exports = npmMock; -------------------------------------------------------------------------------- /utils/arn_parser.js: -------------------------------------------------------------------------------- 1 | var arnParser = {}; 2 | 3 | /** 4 | * Parses Lambda ARNs to identify region and other components 5 | * See CreateFunction in the AWS Lambda API Reference for the ARN formats and regex 6 | * http://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html 7 | * 8 | * Acceptable ARN-like formats for Lambda include: 9 | * - Function name only "Thumbnail" 10 | * - Partial ARN "123456789012:Thumbnail" 11 | * - Full ARN "arn:aws:lambda:us-west-2:123456789012:function:ThumbNail" 12 | * 13 | * @param {string} arn - An ARN-like string specifying the Lambda function. 14 | */ 15 | arnParser.parse = function (arn) { 16 | if (!arn) { 17 | return; 18 | } 19 | var match = arn.match(/(arn:aws:lambda:)?([a-z]{2}-[a-z]+-\d{1}:)?(\d{12}:)?(function:)?([a-zA-Z0-9-_]+)/); 20 | if (!match) { 21 | return; 22 | } 23 | var functionInfo = { 24 | "region": match[2] ? match[2].replace(":", "") : undefined, 25 | "accountId": match[3] ? match[3].replace(":", "") : undefined, 26 | "functionName": match[5] 27 | }; 28 | return functionInfo; 29 | }; 30 | 31 | module.exports = arnParser; -------------------------------------------------------------------------------- /utils/date_facade.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * grunt-aws-lambda 5 | * https://github.com/Tim-B/grunt-aws-lambda 6 | * 7 | * Copyright (c) 2014 Tim-B 8 | * Licensed under the MIT license. 9 | */ 10 | 11 | var dateFacade = {}; 12 | 13 | dateFacade.getFormattedTimestamp = function (dateObject) { 14 | var time_components = [ 15 | dateObject.getFullYear(), 16 | dateObject.getMonth(), 17 | dateObject.getDate(), 18 | dateObject.getHours(), 19 | dateObject.getMinutes(), 20 | dateObject.getSeconds() 21 | ]; 22 | return time_components.join('-'); 23 | }; 24 | 25 | dateFacade.getHumanReadableTimestamp = function (dateObject) { 26 | return dateObject.toLocaleString(); 27 | }; 28 | 29 | module.exports = dateFacade; -------------------------------------------------------------------------------- /utils/deploy_task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * grunt-aws-lambda 5 | * https://github.com/Tim-B/grunt-aws-lambda 6 | * 7 | * Copyright (c) 2014 Tim-B 8 | * Licensed under the MIT license. 9 | */ 10 | 11 | var path = require('path'); 12 | var fs = require('fs'); 13 | var AWS = require('aws-sdk'); 14 | var Q = require('q'); 15 | var arnParser = require('./arn_parser'); 16 | var dateFacade = require('./date_facade'); 17 | 18 | var deployTask = {}; 19 | 20 | var proxy = require('proxy-agent'); 21 | 22 | deployTask.getHandler = function (grunt) { 23 | 24 | return function () { 25 | 26 | grunt.config.requires('lambda_deploy.' + this.target + '.package'); 27 | 28 | var options = this.options({ 29 | profile: null, 30 | RoleArn: null, 31 | accessKeyId: null, 32 | secretAccessKey: null, 33 | credentialsJSON: null, 34 | region: 'us-east-1', 35 | timeout: null, 36 | memory: null, 37 | handler: null, 38 | enableVersioning: false, 39 | aliases: null, 40 | enablePackageVersionAlias: false, 41 | subnetIds: null, 42 | securityGroupIds: null 43 | }); 44 | 45 | if (options.profile !== null) { 46 | var credentials = new AWS.SharedIniFileCredentials({profile: options.profile}); 47 | AWS.config.credentials = credentials; 48 | } 49 | 50 | //Adding proxy if exists 51 | if(process.env.https_proxy !== undefined) { 52 | AWS.config.update({ 53 | httpOptions: { agent: proxy(process.env.https_proxy) } 54 | }); 55 | } 56 | 57 | if (options.RoleArn !== null) { 58 | AWS.config.credentials = new AWS.EC2MetadataCredentials({ 59 | httpOptions: {timeout: 5000} // 5 second timeout 60 | }); 61 | AWS.config.credentials = new AWS.TemporaryCredentials({ 62 | RoleArn: options.RoleArn 63 | }); 64 | } 65 | 66 | if (options.accessKeyId !== null && options.secretAccessKey !== null) { 67 | AWS.config.update({accessKeyId: options.accessKeyId, secretAccessKey: options.secretAccessKey}); 68 | } 69 | 70 | if (options.credentialsJSON !== null) { 71 | AWS.config.loadFromPath(options.credentialsJSON); 72 | } 73 | 74 | AWS.config.update({region: options.region}); 75 | 76 | if (typeof options.aliases === 'string') { 77 | options.aliases = [options.aliases]; 78 | } 79 | 80 | var deploy_function = grunt.config.get('lambda_deploy.' + this.target + '.function'); 81 | var deploy_arn = grunt.config.get('lambda_deploy.' + this.target + '.arn'); 82 | var deploy_package = grunt.config.get('lambda_deploy.' + this.target + '.package'); 83 | var package_version = grunt.config.get('lambda_deploy.' + this.target + '.version'); 84 | var package_name = grunt.config.get('lambda_deploy.' + this.target + '.package_name'); 85 | var archive_name = grunt.config.get('lambda_deploy.' + this.target + '.archive_name'); 86 | 87 | if (deploy_arn === null && deploy_function === null) { 88 | grunt.fail.warn('You must specify either an arn or a function name.'); 89 | } 90 | 91 | if (deploy_arn) { 92 | deploy_function = deploy_arn; 93 | var functionInfo = arnParser.parse(deploy_arn); 94 | if (functionInfo && functionInfo.region) { 95 | options.region = functionInfo.region; 96 | } 97 | } 98 | 99 | var done = this.async(); 100 | 101 | var lambda = new AWS.Lambda({ 102 | apiVersion: '2015-03-31' 103 | }); 104 | 105 | var getDeploymentDescription = function () { 106 | var description = 'Deployed '; 107 | 108 | if (package_name) { 109 | description += 'package ' + package_name + ' '; 110 | } 111 | if (package_version) { 112 | description += 'version ' + package_version + ' '; 113 | } 114 | 115 | description += 'on ' + dateFacade.getHumanReadableTimestamp(new Date()); 116 | 117 | if (archive_name) { 118 | description += ' from artifact ' + archive_name; 119 | } 120 | 121 | return description; 122 | }; 123 | 124 | lambda.getFunction({FunctionName: deploy_function}, function (err, data) { 125 | 126 | if (err) { 127 | if (err.statusCode === 404) { 128 | grunt.fail.warn('Unable to find lambda function ' + deploy_function + ', verify the lambda function name and AWS region are correct.'); 129 | } else { 130 | grunt.log.error('AWS API request failed with ' + err.statusCode + ' - ' + err); 131 | grunt.fail.warn('Check your AWS credentials, region and permissions are correct.'); 132 | } 133 | } 134 | 135 | var current = data.Configuration; 136 | var configParams = {}; 137 | var version = '$LATEST'; 138 | 139 | 140 | if (options.timeout !== null) { 141 | configParams.Timeout = options.timeout; 142 | } 143 | 144 | if (options.memory !== null) { 145 | configParams.MemorySize = options.memory; 146 | } 147 | 148 | if (options.handler !== null) { 149 | configParams.Handler = options.handler; 150 | } 151 | 152 | if (options.subnetIds !== null && options.securityGroupIds !== null) { 153 | configParams.VpcConfig = { 154 | SubnetIds : options.subnetIds, 155 | SecurityGroupIds : options.securityGroupIds 156 | }; 157 | } 158 | 159 | var updateConfig = function (func_name, func_options) { 160 | var deferred = Q.defer(); 161 | if (Object.keys(func_options).length > 0) { 162 | func_options.FunctionName = func_name; 163 | lambda.updateFunctionConfiguration(func_options, function (err, data) { 164 | if (err) { 165 | grunt.fail.warn('Could not update config, check that values and permissions are valid'); 166 | deferred.reject(); 167 | } else { 168 | grunt.log.writeln('Config updated.'); 169 | deferred.resolve(); 170 | } 171 | }); 172 | } else { 173 | grunt.log.writeln('No config updates to make.'); 174 | deferred.resolve(); 175 | } 176 | return deferred.promise; 177 | }; 178 | 179 | var createVersion = function (func_name) { 180 | var deferred = Q.defer(); 181 | if (options.enableVersioning) { 182 | lambda.publishVersion({FunctionName: func_name, Description: getDeploymentDescription()}, function (err, data) { 183 | if (err) { 184 | grunt.fail.warn('Publishing version for function ' + func_name + ' failed with message ' + err.message); 185 | deferred.reject(); 186 | } else { 187 | version = data.Version; 188 | grunt.log.writeln('Version ' + version + ' published.'); 189 | deferred.resolve(); 190 | } 191 | }); 192 | } else { 193 | deferred.resolve(); 194 | } 195 | 196 | return deferred.promise; 197 | }; 198 | 199 | var createOrUpdateAlias = function (func_name, set_alias) { 200 | var deferred = Q.defer(); 201 | 202 | var params = { 203 | FunctionName: func_name, 204 | Name: set_alias 205 | }; 206 | 207 | 208 | lambda.getAlias(params, function (err, data) { 209 | params.FunctionVersion = version; 210 | params.Description = getDeploymentDescription(); 211 | var aliasFunction = 'updateAlias'; 212 | if (err) { 213 | if (err.statusCode === 404) { 214 | aliasFunction = 'createAlias'; 215 | } else { 216 | grunt.fail.warn('Listing aliases for ' + func_name + ' failed with message ' + err.message); 217 | deferred.reject(); 218 | return; 219 | } 220 | } 221 | lambda[aliasFunction](params, function (err, data) { 222 | if (err) { 223 | grunt.fail.warn(aliasFunction + ' for ' + func_name + ' failed with message ' + err.message); 224 | deferred.reject(); 225 | } else { 226 | grunt.log.writeln('Alias ' + set_alias + ' updated pointing to version ' + version + '.'); 227 | deferred.resolve(); 228 | } 229 | }); 230 | }); 231 | 232 | return deferred.promise; 233 | }; 234 | 235 | var setAliases = function (func_name) { 236 | if (options.aliases) { 237 | var promises = []; 238 | options.aliases.forEach(function (alias) { 239 | promises.push(createOrUpdateAlias(func_name, alias)); 240 | }); 241 | return Q.all(promises); 242 | } 243 | }; 244 | 245 | var setPackageVersionAlias = function (func_name) { 246 | if (options.enablePackageVersionAlias && package_version) { 247 | return createOrUpdateAlias(func_name, package_version.replace(/\./g, '-')); 248 | } 249 | }; 250 | 251 | grunt.log.writeln('Uploading...'); 252 | fs.readFile(deploy_package, function (err, data) { 253 | if (err) { 254 | grunt.fail.warn('Could not read package file (' + deploy_package + '), verify the lambda package ' + 255 | 'location is correct, and that you have already created the package using lambda_package.'); 256 | } 257 | 258 | var codeParams = { 259 | FunctionName: deploy_function, 260 | ZipFile: data 261 | }; 262 | 263 | lambda.updateFunctionCode(codeParams, function (err, data) { 264 | if (err) { 265 | grunt.fail.warn('Package upload failed, check you have lambda:UpdateFunctionCode permissions and that your package is not too big to upload.'); 266 | } 267 | 268 | grunt.log.writeln('Package deployed.'); 269 | 270 | updateConfig(deploy_function, configParams) 271 | .then(function () {return createVersion(deploy_function);}) 272 | .then(function () {return setAliases(deploy_function);}) 273 | .then(function () {return setPackageVersionAlias(deploy_function);}) 274 | .then(function () { 275 | done(true); 276 | }).catch(function (err) { 277 | grunt.fail.warn('Uncaught exception: ' + err.message); 278 | }); 279 | }); 280 | }); 281 | }); 282 | }; 283 | }; 284 | 285 | module.exports = deployTask; 286 | -------------------------------------------------------------------------------- /utils/invoke_task.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-aws-lambda 3 | * https://github.com/Tim-B/grunt-aws-lambda 4 | * 5 | * Copyright (c) 2014 Tim-B 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var path = require('path'); 12 | var fs = require('fs'); 13 | 14 | var invokeTask = {}; 15 | 16 | invokeTask.loadFunction = function(file_name) { 17 | return require(path.resolve(file_name)); 18 | }; 19 | 20 | invokeTask.getHandler = function (grunt) { 21 | 22 | return function () { 23 | 24 | var options = this.options({ 25 | 'package_folder': './', 26 | 'handler': 'handler', 27 | 'file_name': 'index.js', 28 | 'event': 'event.json', 29 | 'client_context': 'client_context.json', 30 | 'identity': 'identity.json' 31 | }); 32 | 33 | grunt.log.writeln(""); 34 | 35 | var done = this.async(); 36 | 37 | var clientContext = null; 38 | 39 | //since clientContext should be optional, skip if doesn't exist 40 | try { 41 | clientContext = JSON.parse(fs.readFileSync(path.resolve(options.client_context), "utf8")); 42 | } catch (e) { 43 | if (e.code !== 'ENOENT') { 44 | throw e; 45 | } 46 | } 47 | 48 | var identity = null; 49 | //since identity should be optional, skip if doesn't exist 50 | try { 51 | identity = JSON.parse(fs.readFileSync(path.resolve(options.identity), "utf8")); 52 | } catch (e) { 53 | if (e.code !== 'ENOENT') { 54 | throw e; 55 | } 56 | } 57 | 58 | var cwd; 59 | if (options.package_folder) { 60 | cwd = process.cwd(); 61 | process.chdir(path.resolve(options.package_folder)); 62 | } 63 | 64 | var context = { 65 | done: function (error, result) { 66 | if (error === null || typeof(error) === 'undefined') { 67 | context.succeed(result); 68 | } else { 69 | context.fail(error); 70 | } 71 | }, 72 | succeed: function (result) { 73 | if (cwd) { 74 | process.chdir(cwd); 75 | } 76 | grunt.log.writeln(""); 77 | grunt.log.writeln("Success! Message:"); 78 | grunt.log.writeln("------------------"); 79 | var msg = (typeof(result) === 'object') ? JSON.stringify(result) : result; 80 | grunt.log.writeln((typeof(result) !== 'undefined') ? msg : "Successful!"); 81 | done(true); 82 | }, 83 | fail: function (error) { 84 | if (cwd) { 85 | process.chdir(cwd); 86 | } 87 | grunt.log.writeln(""); 88 | grunt.log.writeln("Failure! Message:"); 89 | grunt.log.writeln("------------------"); 90 | var msg = (typeof(error) === 'object') ? JSON.stringify(error) : error; 91 | grunt.log.writeln((typeof(error) !== 'undefined') ? msg : "Error not provided."); 92 | done(false); 93 | }, 94 | awsRequestId: 'LAMBDA_INVOKE', 95 | logStreamName: 'LAMBDA_INVOKE', 96 | clientContext: clientContext, 97 | identity: identity 98 | }; 99 | 100 | var callback = function(error, object) { 101 | context.done(error, object); 102 | }; 103 | 104 | var lambda = invokeTask.loadFunction(options.file_name); 105 | var event = JSON.parse(fs.readFileSync(path.resolve(options.event), "utf8")); 106 | lambda[options.handler](event, context, callback); 107 | }; 108 | }; 109 | 110 | module.exports = invokeTask; -------------------------------------------------------------------------------- /utils/package_task.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * grunt-aws-lambda 5 | * https://github.com/Tim-B/grunt-aws-lambda 6 | * 7 | * Copyright (c) 2014 Tim-B 8 | * Licensed under the MIT license. 9 | */ 10 | 11 | var path = require('path'); 12 | var npm = require("npm"); 13 | var archive = require('archiver'); 14 | var fs = require('fs'); 15 | var tmp = require('temporary'); 16 | var mkdirp = require('mkdirp'); 17 | var rimraf = require('rimraf'); 18 | var dateFacade = require('./date_facade'); 19 | 20 | var packageTask = {}; 21 | 22 | packageTask.getHandler = function (grunt) { 23 | 24 | return function () { 25 | var task = this; 26 | 27 | var options = this.options({ 28 | 'dist_folder': 'dist', 29 | 'include_time': true, 30 | 'include_version': true, 31 | 'package_folder': './', 32 | 'include_files': [] 33 | }); 34 | 35 | var pkg = JSON.parse(fs.readFileSync(path.resolve(options.package_folder + '/package.json'), "utf8")); 36 | 37 | var dir = new tmp.Dir(); 38 | var done = this.async(); 39 | 40 | var time_string = 'latest'; 41 | 42 | if (options.include_time) { 43 | time_string = dateFacade.getFormattedTimestamp(new Date()); 44 | } 45 | 46 | var archive_name = pkg.name; 47 | 48 | if (options.include_version) { 49 | archive_name += '_' + pkg.version.replace(/\./g, '-'); 50 | } 51 | 52 | archive_name += '_' + time_string; 53 | 54 | npm.load([], function (err, npm) { 55 | 56 | npm.config.set('loglevel', 'silent'); 57 | 58 | var install_location = dir.path; 59 | var zip_path = install_location + '/' + archive_name + '.zip'; 60 | 61 | npm.commands.install(install_location, options.package_folder, function () { 62 | 63 | var output = fs.createWriteStream(zip_path); 64 | var zipArchive = archive('zip'); 65 | 66 | /* 67 | * Monkey patch to ensure permissions are always 777 68 | * Prevents issues on Windows for directories that don't have execute permissions 69 | * See https://github.com/Tim-B/grunt-aws-lambda/issues/6 70 | */ 71 | var old_normalizeEntryData = zipArchive._normalizeEntryData; 72 | zipArchive._normalizeEntryData = function (data, stats) { 73 | // 0777 file permission 74 | data.mode = 511; 75 | return old_normalizeEntryData.apply(zipArchive, [data, stats]); 76 | }; 77 | 78 | zipArchive.pipe(output); 79 | 80 | zipArchive.bulk([ 81 | { 82 | src: ['./**'], 83 | dot: true, 84 | expand: true, 85 | cwd: install_location + '/node_modules/' + pkg.name 86 | } 87 | ]); 88 | 89 | if (options.include_files.length) { 90 | zipArchive.bulk([ 91 | { 92 | src: options.include_files, 93 | dot: true, 94 | expand: true, 95 | cwd: options.package_folder 96 | } 97 | ]); 98 | } 99 | 100 | zipArchive.finalize(); 101 | 102 | output.on('close', function () { 103 | mkdirp('./' + options.dist_folder, function (err) { 104 | var dist_path = './' + options.dist_folder + '/' + archive_name + '.zip'; 105 | var dist_zip = fs.createWriteStream(dist_path); 106 | fs.createReadStream(zip_path).pipe(dist_zip); 107 | 108 | dist_zip.on('close', function () { 109 | rimraf(install_location, function () { 110 | grunt.config.set('lambda_deploy.' + task.target + '.package', dist_path); 111 | grunt.config.set('lambda_deploy.' + task.target + '.version', pkg.version); 112 | grunt.config.set('lambda_deploy.' + task.target + '.archive_name', archive_name); 113 | grunt.config.set('lambda_deploy.' + task.target + '.package_name', pkg.name); 114 | grunt.log.writeln('Created package at ' + dist_path); 115 | done(true); 116 | }); 117 | }); 118 | }); 119 | }); 120 | }); 121 | }); 122 | }; 123 | }; 124 | 125 | module.exports = packageTask; --------------------------------------------------------------------------------