diff --git a/README.md b/README.md index 14f75373f..03f42434d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Serverless is an MIT open-source project, actively maintained by a full-time, ve ## Contents + + * [Quick Start](#quick-start) * [Examples](https://github.com/serverless/examples) * [Services](#services) @@ -129,7 +131,7 @@ The following are services you can instantly install and use by running `serverl ## Features -* Supports Node.js, Python, Java & Scala. +* Supports Node.js, Python, Java, Scala, C#, F#, Groovy, Kotlin, PHP & Swift. * Manages the lifecycle of your serverless architecture (build, deploy, update, delete). * Safely deploy functions, events and their required resources together via provider resource managers (e.g., AWS CloudFormation). * Functions can be grouped ("serverless services") for easy management of code, resources & processes, across large projects & teams. @@ -152,40 +154,60 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p --> | Plugin | Author | |:-------|:------:| -| **[API GW binary support](https://github.com/maciejtreder/serverless-apigw-binary)**
Serverless plugin to enable binary support in AWS API Gateway. | [Maciej Treder](https://github.com/maciejtreder) | | **[Raml Serverless](https://github.com/andrewcurioso/raml-serverless)**
Serverless plugin to work with RAML API spec documents | [andrewcurioso](http://github.com/andrewcurioso) | | **[Serverless Alexa Plugin](https://github.com/rajington/serverless-alexa-plugin)**
Serverless plugin to support Alexa Lambda events | [rajington](http://github.com/rajington) | | **[Serverless Api Stage](https://github.com/leftclickben/serverless-api-stage)**
Serverless API Stage plugin, enables stage variables and logging for AWS API Gateway. | [leftclickben](http://github.com/leftclickben) | | **[Serverless Apig S3](https://github.com/sdd/serverless-apig-s3)**
Serve static front-end content from S3 via the API Gatewy and deploy client bundle to S3. | [sdd](http://github.com/sdd) | +| **[Serverless Apigateway Plugin](https://github.com/GFG/serverless-apigateway-plugin)**
Configure the AWS api gateway: Binary support, Headers and Body template mappings | [GFG](http://github.com/GFG) | +| **[Serverless Apigw Binary](https://github.com/maciejtreder/serverless-apigw-binary)**
Plugin to enable binary support in AWS API Gateway. | [maciejtreder](http://github.com/maciejtreder) | +| **[Serverless Apigwy Binary](https://github.com/ryanmurakami/serverless-apigwy-binary)**
Serverless plugin for configuring API Gateway to return binary responses | [ryanmurakami](http://github.com/ryanmurakami) | | **[Serverless Aws Alias](https://github.com/HyperBrain/serverless-aws-alias)**
This plugin enables use of AWS aliases on Lambda functions. | [HyperBrain](http://github.com/HyperBrain) | | **[Serverless Aws Documentation](https://github.com/9cookies/serverless-aws-documentation)**
Serverless plugin to add documentation and models to the serverless generated API Gateway | [9cookies](http://github.com/9cookies) | | **[Serverless Build Plugin](https://github.com/nfour/serverless-build-plugin)**
A Node.js focused build plugin for serverless. | [nfour](http://github.com/nfour) | +| **[Serverless Cf Vars](https://gitlab.com/kabo/serverless-cf-vars)**
Enables use of AWS pseudo functions and Fn::Sub string substitution | [kabo](http://github.com/kabo) | | **[Serverless Cljs Plugin](https://github.com/nervous-systems/serverless-cljs-plugin)**
Enables Clojurescript as an implementation language for Lambda handlers | [nervous-systems](http://github.com/nervous-systems) | | **[Serverless Coffeescript](https://github.com/duanefields/serverless-coffeescript)**
A Serverless plugin to compile your CoffeeScript into JavaScript at deployment | [duanefields](http://github.com/duanefields) | | **[Serverless Command Line Event Args](https://github.com/horike37/serverless-command-line-event-args)**
This module is Serverless Framework plugin. Event JSON passes to your Lambda function in commandline. | [horike37](http://github.com/horike37) | | **[Serverless Crypt](https://github.com/marcy-terui/serverless-crypt)**
Securing the secrets on Serverless Framework by AWS KMS encryption. | [marcy-terui](http://github.com/marcy-terui) | +| **[Serverless Custom Packaging Plugin](https://github.com/hypoport/serverless-custom-packaging-plugin)**
Plugin to package your sourcecode using a custom target path inside the zip. | [hypoport](http://github.com/hypoport) | | **[Serverless Dir Config Plugin](https://github.com/economysizegeek/serverless-dir-config-plugin)**
EXPERIMENTAL - Serverless plugin to load function and resource definitions from a directory. | [economysizegeek](http://github.com/economysizegeek) | +| **[Serverless Domain Manager](https://github.com/amplify-education/serverless-domain-manager)**
Serverless plugin for managing custom domains with API Gateways. | [amplify-education](http://github.com/amplify-education) | | **[Serverless Dotenv](https://github.com/Jimdo/serverless-dotenv)**
Fetch environment variables and write it to a .env file | [Jimdo](http://github.com/Jimdo) | | **[Serverless Dotnet](https://github.com/fruffin/serverless-dotnet)**
A serverless plugin to run 'dotnet' commands as part of the deploy process | [fruffin](http://github.com/fruffin) | | **[Serverless Dynalite](https://github.com/sdd/serverless-dynalite)**
Run dynalite locally (no JVM, all JS) to simulate DynamoDB. Watch serverless.yml for table config updates. | [sdd](http://github.com/sdd) | +| **[Serverless Dynamodb Autoscaling](https://github.com/sbstjn/serverless-dynamodb-autoscaling)**
Configure Amazon DynamoDB's native Auto Scaling for your table capacities. | [sbstjn](http://github.com/sbstjn) | | **[Serverless Dynamodb Local](https://github.com/99xt/serverless-dynamodb-local)**
Serverless Dynamodb Local Plugin - Allows to run dynamodb locally for serverless | [99xt](http://github.com/99xt) | | **[Serverless Dynamodb Ttl](https://github.com/Jimdo/serverless-dynamodb-ttl)**
Configure DynamoDB TTL in serverless.yml (until CloudFormation supports this). | [Jimdo](http://github.com/Jimdo) | | **[Serverless Enable Api Logs](https://github.com/paulSambolin/serverless-enable-api-logs)**
Enables Coudwatch logging for API Gateway events | [paulSambolin](http://github.com/paulSambolin) | +| **[Serverless Env Generator](https://github.com/DieProduktMacher/serverless-env-generator)**
Manage environment variables with YAML and load them with dotenv. Supports variable encryption with KMS, multiple stages and custom profiles. | [DieProduktMacher](http://github.com/DieProduktMacher) | | **[Serverless Event Constant Inputs](https://github.com/dittto/serverless-event-constant-inputs)**
Allows you to add constant inputs to events in Serverless 1.0. For more info see [constant values in Cloudwatch](https://aws.amazon.com/blogs/compute/simply-serverless-use-constant-values-in-cloudwatch-event-triggered-lambda-functions/) | [dittto](http://github.com/dittto) | | **[Serverless Export Env](https://github.com/arabold/serverless-export-env)**
Export environment variables into a .env file with automatic AWS CloudFormation reference resolution. | [arabold](http://github.com/arabold) | +| **[Serverless Gulp](https://github.com/rhythminme/serverless-gulp)**
A thin task wrapper around @goserverless that allows you to automate build, test and deploy tasks using gulp | [rhythminme](http://github.com/rhythminme) | | **[Serverless Hooks Plugin](https://github.com/uswitch/serverless-hooks-plugin)**
Run arbitrary commands on any lifecycle event in serverless | [uswitch](http://github.com/uswitch) | | **[Serverless Jest Plugin](https://github.com/SC5/serverless-jest-plugin)**
A Serverless Plugin for the Serverless Framework which adds support for test-driven development using Jest | [SC5](http://github.com/SC5) | +| **[Serverless Kms Secrets](https://github.com/SC5/serverless-kms-secrets)**
Allows to easily encrypt and decrypt secrets using KMS from the serverless CLI | [SC5](http://github.com/SC5) | +| **[Serverless Kubeless](https://github.com/serverless/serverless-kubeless)**
Serverless plugin for deploying functions to Kubeless. | [serverless](http://github.com/serverless) | +| **[Serverless Local Dev Server](https://github.com/DieProduktMacher/serverless-local-dev-server)**
Speeds up development of Alexa Skills, Chatbots and APIs by exposing your functions as local HTTP endpoints and mapping received events. | [DieProduktMacher](http://github.com/DieProduktMacher) | +| **[Serverless Log Forwarding](https://github.com/amplify-education/serverless-log-forwarding)**
Serverless plugin for forwarding CloudWatch logs to another Lambda function. | [amplify-education](http://github.com/amplify-education) | | **[Serverless Mocha Plugin](https://github.com/SC5/serverless-mocha-plugin)**
A Serverless Plugin for the Serverless Framework which adds support for test-driven development using Mocha | [SC5](http://github.com/SC5) | +| **[Serverless Nested Stack](https://github.com/jagdish-176/serverless-nested-stack)**
A plugin to Workaround for Cloudformation 200 resource limit | [jagdish-176](http://github.com/jagdish-176) | | **[Serverless Offline](https://github.com/dherault/serverless-offline)**
Emulate AWS λ and API Gateway locally when developing your Serverless project | [dherault](http://github.com/dherault) | | **[Serverless Offline Scheduler](https://github.com/ajmath/serverless-offline-scheduler)**
Runs scheduled functions offline while integrating with serverless-offline | [ajmath](http://github.com/ajmath) | | **[Serverless Package Python Functions](https://github.com/ubaniabalogun/serverless-package-python-functions)**
Packaging Python Lambda functions with only the dependencies/requirements they need. | [ubaniabalogun](http://github.com/ubaniabalogun) | | **[Serverless Parameters](https://github.com/svdgraaf/serverless-parameters)**
Add parameters to the generated cloudformation templates | [svdgraaf](http://github.com/svdgraaf) | | **[Serverless Plugin Aws Alerts](https://github.com/ACloudGuru/serverless-plugin-aws-alerts)**
A Serverless plugin to easily add CloudWatch alarms to functions | [ACloudGuru](http://github.com/ACloudGuru) | +| **[Serverless Plugin Aws Resolvers](https://github.com/DopplerLabs/serverless-plugin-aws-resolvers)**
Resolves variables from ESS, RDS, or Kinesis for serverless services | [DopplerLabs](http://github.com/DopplerLabs) | +| **[Serverless Plugin Bespoken](https://github.com/bespoken/serverless-plugin-bespoken)**
Creates a local server and a proxy so you don't have to deploy anytime you want to test your code | [bespoken](http://github.com/bespoken) | | **[Serverless Plugin Bind Deployment Id](https://github.com/jacob-meacham/serverless-plugin-bind-deployment-id)**
A Serverless plugin to bind the randomly generated deployment resource to your custom resources | [jacob-meacham](http://github.com/jacob-meacham) | +| **[Serverless Plugin Browserifier](https://github.com/digitalmaas/serverless-plugin-browserifier)**
Reduce the size and speed up your Node.js based lambda's using browserify. | [digitalmaas](http://github.com/digitalmaas) | | **[Serverless Plugin Browserify](https://github.com/doapp-ryanp/serverless-plugin-browserify)**
Speed up your node based lambda's | [doapp-ryanp](http://github.com/doapp-ryanp) | | **[Serverless Plugin Cfauthorizer](https://github.com/SC5/serverless-plugin-cfauthorizer)**
This plugin allows you to define your own API Gateway Authorizers as the Serverless CloudFormation resources and apply them to HTTP endpoints. | [SC5](http://github.com/SC5) | | **[Serverless Plugin Cloudwatch Sumologic](https://github.com/ACloudGuru/serverless-plugin-cloudwatch-sumologic)**
Plugin which auto-subscribes a log delivery lambda function to lambda log groups created by serverless | [ACloudGuru](http://github.com/ACloudGuru) | +| **[Serverless Plugin Common Excludes](https://github.com/dougmoscrop/serverless-plugin-common-excludes)**
Adds commonly excluded files to package.excludes | [dougmoscrop](http://github.com/dougmoscrop) | +| **[Serverless Plugin Custom Domain](https://github.com/dougmoscrop/serverless-plugin-custom-domain)**
Reliably sets a BasePathMapping to an API Gateway Custom Domain | [dougmoscrop](http://github.com/dougmoscrop) | +| **[Serverless Plugin Deploy Environment](https://github.com/DopplerLabs/serverless-plugin-deploy-environment)**
Plugin to manage deployment environment across stages | [DopplerLabs](http://github.com/DopplerLabs) | | **[Serverless Plugin Diff](https://github.com/nicka/serverless-plugin-diff)**
Compares your local AWS CloudFormation templates against deployed ones. | [nicka](http://github.com/nicka) | +| **[Serverless Plugin Elastic Beanstalk](https://github.com/rawphp/serverless-plugin-elastic-beanstalk)**
A serverless plugin to deploy applications to AWS ElasticBeanstalk. | [rawphp](http://github.com/rawphp) | | **[Serverless Plugin Encode Env Var Objects](https://github.com/yonomi/serverless-plugin-encode-env-var-objects)**
Serverless plugin to encode any environment variable objects. | [yonomi](http://github.com/yonomi) | | **[Serverless Plugin External Sns Events](https://github.com/silvermine/serverless-plugin-external-sns-events)**
Add ability for functions to use existing or external SNS topics as an event source | [silvermine](http://github.com/silvermine) | | **[Serverless Plugin Git Variables](https://github.com/jacob-meacham/serverless-plugin-git-variables)**
A Serverless plugin to expose git variables (branch name, HEAD description, full commit hash) to your serverless services | [jacob-meacham](http://github.com/jacob-meacham) | @@ -193,12 +215,16 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p | **[Serverless Plugin Include Dependencies](https://github.com/dougmoscrop/serverless-plugin-include-dependencies)**
This is a Serverless plugin that should make your deployed functions smaller. | [dougmoscrop](http://github.com/dougmoscrop) | | **[Serverless Plugin Iopipe](https://github.com/iopipe/serverless-plugin-iopipe)**
See inside your Lambda functions with high fidelity metrics and monitoring. | [iopipe](http://github.com/iopipe) | | **[Serverless Plugin Lambda Dead Letter](https://github.com/gmetzker/serverless-plugin-lambda-dead-letter)**
A Serverless plugin that can configure a lambda with a dead letter queue or topic | [gmetzker](http://github.com/gmetzker) | +| **[Serverless Plugin Log Subscription](https://github.com/dougmoscrop/serverless-plugin-log-subscription)**
Adds a CloudWatch LogSubscription for functions | [dougmoscrop](http://github.com/dougmoscrop) | | **[Serverless Plugin Multiple Responses](https://github.com/silvermine/serverless-plugin-multiple-responses)**
Enable multiple content-types for Response template | [silvermine](http://github.com/silvermine) | +| **[Serverless Plugin Offline Kinesis Events](https://github.com/DopplerLabs/serverless-plugin-offline-kinesis-events)**
Plugin that works with serverless-offline to allow offline testing of serverless functions that are triggered by Kinesis events. | [DopplerLabs](http://github.com/DopplerLabs) | | **[Serverless Plugin Optimize](https://github.com/FidelLimited/serverless-plugin-optimize)**
Bundle with Browserify, transpile with Babel to ES5 and minify with Uglify your Serverless functions. | [FidelLimited](http://github.com/FidelLimited) | | **[Serverless Plugin Package Dotenv File](https://github.com/ACloudGuru/serverless-plugin-package-dotenv-file)**
A Serverless plugin to copy a .env file into the serverless package | [ACloudGuru](http://github.com/ACloudGuru) | | **[Serverless Plugin Scripts](https://github.com/mvila/serverless-plugin-scripts)**
Add scripting capabilities to the Serverless Framework | [mvila](http://github.com/mvila) | | **[Serverless Plugin Select](https://github.com/FidelLimited/serverless-plugin-select)**
Select which functions are to be deployed based on region and stage. | [FidelLimited](http://github.com/FidelLimited) | | **[Serverless Plugin Simulate](https://github.com/gertjvr/serverless-plugin-simulate)**
Simulate AWS Lambda and API Gateway locally using Docker | [gertjvr](http://github.com/gertjvr) | +| **[Serverless Plugin Split Stacks](https://github.com/dougmoscrop/serverless-plugin-split-stacks)**
Migrate certain resources to nested stacks | [dougmoscrop](http://github.com/dougmoscrop) | +| **[Serverless Plugin Stack Config](https://github.com/rawphp/serverless-plugin-stack-config)**
A serverless plugin to manage configurations for a stack across micro-services. | [rawphp](http://github.com/rawphp) | | **[Serverless Plugin Stack Outputs](https://github.com/svdgraaf/serverless-plugin-stack-outputs)**
Displays stack outputs for your serverless stacks when `sls info` is ran | [svdgraaf](http://github.com/svdgraaf) | | **[Serverless Plugin Stage Variables](https://github.com/svdgraaf/serverless-plugin-stage-variables)**
Add stage variables for Serverless 1.x to ApiGateway, so you can use variables in your Lambda's | [svdgraaf](http://github.com/svdgraaf) | | **[Serverless Plugin Subscription Filter](https://github.com/tsub/serverless-plugin-subscription-filter)**
A serverless plugin to register AWS CloudWatchLogs subscription filter | [tsub](http://github.com/tsub) | @@ -212,14 +238,20 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p | **[Serverless Python Requirements](https://github.com/UnitedIncome/serverless-python-requirements)**
Serverless plugin to bundle Python packages | [UnitedIncome](http://github.com/UnitedIncome) | | **[Serverless Resources Env](https://github.com/rurri/serverless-resources-env)**
After Deploy, this plugin fetches cloudformation resource identifiers and sets them on AWS lambdas, and creates local .-env file | [rurri](http://github.com/rurri) | | **[Serverless Run Function Plugin](https://github.com/lithin/serverless-run-function-plugin)**
Run serverless function locally | [lithin](http://github.com/lithin) | +| **[Serverless S3 Remover](https://github.com/sinofseven/serverless-s3-remover)**
A serverless plugin to make s3 buckets empty before deleting cloudformation stack when ```sls remove``` | [sinofseven](http://github.com/sinofseven) | +| **[Serverless S3 Sync](https://github.com/k1LoW/serverless-s3-sync)**
A plugin to sync local directories and S3 prefixes for Serverless Framework, | [k1LoW](http://github.com/k1LoW) | +| **[Serverless S3bucket Sync](https://github.com/sbstjn/serverless-s3bucket-sync)**
Sync a local folder with a S3 bucket after sls deploy | [sbstjn](http://github.com/sbstjn) | | **[Serverless Sam](https://github.com/SAPessi/serverless-sam)**
Exports an AWS SAM template for a service created with the Serverless Framework. | [SAPessi](http://github.com/SAPessi) | | **[Serverless Scriptable Plugin](https://github.com/weixu365/serverless-scriptable-plugin)**
Customize Serverless behavior without writing a plugin. | [weixu365](http://github.com/weixu365) | +| **[Serverless Sentry](https://github.com/arabold/serverless-sentry-plugin)**
Automatic monitoring of memory usage, execution timeouts and forwarding of Lambda errors to Sentry (https://sentry.io). | [arabold](http://github.com/arabold) | +| **[Serverless Shell](https://github.com/UnitedIncome/serverless-shell)**
Drop to a runtime shell with all the environment variables set that you'd have in lambda. | [UnitedIncome](http://github.com/UnitedIncome) | | **[Serverless Sqs Alarms Plugin](https://github.com/sbstjn/serverless-sqs-alarms-plugin)**
Wrapper to setup CloudWatch Alarms on SQS queue length | [sbstjn](http://github.com/sbstjn) | | **[Serverless Sqs Fifo](https://github.com/vortarian/serverless-sqs-fifo)**
A serverless plugin to handle creation of sqs fifo queue's in aws (stop-gap) | [vortarian](http://github.com/vortarian) | +| **[Serverless Stack Output](https://github.com/sbstjn/serverless-stack-output)**
Store output from your AWS CloudFormation Stack in JSON/YAML/TOML files, or to pass it to a JavaScript function for further processing. | [sbstjn](http://github.com/sbstjn) | | **[Serverless Step Functions](https://github.com/horike37/serverless-step-functions)**
AWS Step Functions with Serverless Framework. | [horike37](http://github.com/horike37) | | **[Serverless Subscription Filter](https://github.com/blackevil245/serverless-subscription-filter)**
Serverless plugin to register subscription filter for Lambda logs. Register and pipe the logs of one lambda to another to process. | [blackevil245](http://github.com/blackevil245) | | **[Serverless Vpc Discovery](https://github.com/amplify-education/serverless-vpc-discovery)**
Serverless plugin for discovering VPC / Subnet / Security Group configuration by name. | [amplify-education](http://github.com/amplify-education) | -| **[Serverless Webpack](https://github.com/elastic-coders/serverless-webpack)**
Serverless plugin to bundle your lambdas with Webpack | [elastic-coders](http://github.com/elastic-coders) | +| **[Serverless Webpack](https://github.com/serverless-heaven/serverless-webpack)**
Serverless plugin to bundle your lambdas with Webpack | [serverless-heaven](http://github.com/serverless-heaven) | | **[Serverless Wsgi](https://github.com/logandk/serverless-wsgi)**
Serverless plugin to deploy WSGI applications (Flask/Django/Pyramid etc.) and bundle Python packages | [logandk](http://github.com/logandk) | @@ -230,6 +262,7 @@ This table is generated from https://github.com/serverless/examples/blob/master/ --> | Project Name | Author | |:-------------|:------:| +| **[Jwtauthorizr](https://github.com/serverlessbuch/jwtAuthorizr)**
Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT | [serverlessbuch](http://github.com/serverlessbuch) | | **[Serverless Graphql Api](https://github.com/boazdejong/serverless-graphql-api)**
Serverless GraphQL API using Lambda and DynamoDB | [boazdejong](http://github.com/boazdejong) | | **[Serverless Screenshot](https://github.com/svdgraaf/serverless-screenshot)**
Serverless Screenshot Service using PhantomJS | [svdgraaf](http://github.com/svdgraaf) | | **[Serverless Postgraphql](https://github.com/rentrop/serverless-postgraphql)**
GraphQL endpoint for PostgreSQL using postgraphql | [rentrop](http://github.com/rentrop) | @@ -275,6 +308,14 @@ This table is generated from https://github.com/serverless/examples/blob/master/ | **[Aws Api Gateway Serverless Project Written In Go](https://github.com/yunspace/serverless-golang)**
A serverless project that contains an API Gateway endpoint powered by a Lambda function written in golang and built using [eawsy/aws-lambda-go-shim](https://github.com/eawsy/aws-lambda-go-shim). | [yunspace](http://github.com/yunspace) | | **[Video Preview And Analysis Service](https://github.com/laardee/video-preview-and-analysis-service)**
An event-driven service that generates labels using Amazon Rekognition and creates preview GIF animation from a video file. | [laardee](http://github.com/laardee) | | **[Serverless Es6/7 Crud Api](https://github.com/AnomalyInnovations/serverless-stack-demo-api)**
[Serverless Stack](http://serverless-stack.com) examples of backend CRUD APIs (DynamoDB + Lambda + API Gateway + Cognito User Pool authorizer) for [React.js single-page app](http://demo.serverless-stack.com) | [AnomalyInnovations](http://github.com/AnomalyInnovations) | +| **[Sqs Worker With Aws Lambda And Cloudwatch Alarms](https://github.com/sbstjn/sqs-worker-serverless)**
Process messages stored in SQS with an [auto-scaled AWS Lambda worker](https://sbstjn.com/serverless-sqs-worker-with-aws-lambda.html) function. | [sbstjn](http://github.com/sbstjn) | +| **[Aws Lambda Power Tuning (Powered By Step Functions)](https://github.com/alexcasalboni/aws-lambda-power-tuning)**
Build a [Step Functions](https://aws.amazon.com/step-functions/) state machine to optimize your AWS Lambda Function memory/power configuration. | [alexcasalboni](http://github.com/alexcasalboni) | +| **[Amazon Kinesis Streams Fan Out Via Kinesis Analytics](https://github.com/alexcasalboni/kinesis-streams-fan-out-kinesis-analytics)**
Use [Amazon Kinesis Analytics](https://aws.amazon.com/kinesis/analytics/) to fan-out your Kinesis Streams and avoid read throttling. | [alexcasalboni](http://github.com/alexcasalboni) | +| **[Grants Api Serverless](https://github.com/comicrelief/grants-api-serverless)**
ES6 API to consume data from an external API, ingest into Elasticsearch and return a queryable endpoint on top of Elasticsearch | [comicrelief](http://github.com/comicrelief) | +| **[Honeylambda](https://github.com/0x4D31/honeyLambda)**
a simple, serverless application designed to create and monitor URL {honey}tokens, on top of AWS Lambda and Amazon API Gateway | [0x4D31](http://github.com/0x4D31) | +| **[Stack Overflow Monitor](https://github.com/picsoung/stackoverflowmonitor)**
Monitor Stack Overflow questions and post them in a Slack channel | [picsoung](http://github.com/picsoung) | +| **[React & Stripe Serverless Ecommerce](https://github.com/patrick-michelberger/serverless-shop)**
Serverless E-Commerce App with AWS Lambda, Stripe and React | [patrick-michelberger](http://github.com/patrick-michelberger) | +| **[Serverless + Medium Text To Speech](https://github.com/RafalWilinski/serverless-medium-text-to-speech)**
Serverless-based, text-to-speech service for Medium articles | [RafalWilinski](http://github.com/RafalWilinski) | ## Contributing diff --git a/docker-compose.yml b/docker-compose.yml index d16161c14..5997745a6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,6 +39,10 @@ services: image: maven:3-jdk-8 volumes: - ./tmp/serverless-integration-test-aws-kotlin-jvm-maven:/app + aws-kotlin-nodejs-gradle: + image: pgoudreau/docker-maven-node + volumes: + - ./tmp/serverless-integration-test-aws-kotlin-nodejs-gradle:/app aws-groovy-gradle: image: java:8 volumes: diff --git a/docs/getting-started.md b/docs/getting-started.md index 4ef15a86f..7ceb0feac 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -23,7 +23,7 @@ npm install -g serverless serverless login ``` -*Serverless follows the [Semantic Versioning](http://semver.org) schema. You can read more about that in our dedicated [versioning file](http://bit.ly/2eP05Iw).* +Next up, it's time to choose where you'd like your serverless service to run. ## Choose your compute provider diff --git a/docs/providers/aws/cli-reference/create.md b/docs/providers/aws/cli-reference/create.md index 268ace7ed..e88221851 100644 --- a/docs/providers/aws/cli-reference/create.md +++ b/docs/providers/aws/cli-reference/create.md @@ -46,6 +46,7 @@ Most commonly used templates: - aws-python - aws-python3 - aws-kotlin-jvm-maven +- aws-kotlin-nodejs-gradle - aws-groovy-gradle - aws-java-maven - aws-java-gradle diff --git a/docs/providers/aws/cli-reference/deploy-function.md b/docs/providers/aws/cli-reference/deploy-function.md index f7bee94af..1b4486288 100644 --- a/docs/providers/aws/cli-reference/deploy-function.md +++ b/docs/providers/aws/cli-reference/deploy-function.md @@ -18,16 +18,16 @@ The `sls deploy function` command deploys an individual function without AWS Clo serverless deploy function -f functionName ``` -**Note:** Because this command is only deploying the function code, function -properties such as environment variables and events will **not** be deployed. -Those properties are deployed via CloudFormation, which does not execute with -this command. +**Note:** This command **now** deploys both function configuration and code by +default. Just as before, this puts your function in an inconsistent state that +is out of sync with your CloudFormation stack. Use this for faster development +cycles and not production deployments ## Options - `--function` or `-f` The name of the function which should be deployed - `--stage` or `-s` The stage in your service that you want to deploy to. - `--region` or `-r` The region in that stage that you want to deploy to. -- `--update-config` or `-u` Pushes Lambda-level configuration changes e.g. timeout or memorySize +- `--update-config` or `-u` Pushes ONLY Lambda-level configuration changes e.g. timeout or memorySize ## Examples @@ -43,8 +43,8 @@ serverless deploy function --function helloWorld serverless deploy function --function helloWorld --stage dev --region us-east-1 ``` -### Deployment with configuration change +### Deploy only configuration changes ```bash serverless deploy function --function helloWorld --update-config -``` \ No newline at end of file +``` diff --git a/docs/providers/aws/cli-reference/invoke-local.md b/docs/providers/aws/cli-reference/invoke-local.md index e52daead2..20a618a98 100644 --- a/docs/providers/aws/cli-reference/invoke-local.md +++ b/docs/providers/aws/cli-reference/invoke-local.md @@ -96,7 +96,9 @@ This example will pass the json context in the `lib/context.json` file (relative ### Limitations -Currently, `invoke local` only supports the NodeJs and Python runtimes. +Currently, `invoke local` only supports the NodeJs, Python & Java runtimes. + +**Note:** In order to get correct output when using Java runtime, your Response class must implement `toString()` method. ## Resource permissions diff --git a/docs/providers/aws/cli-reference/invoke.md b/docs/providers/aws/cli-reference/invoke.md index db3f2d2cc..515df23af 100644 --- a/docs/providers/aws/cli-reference/invoke.md +++ b/docs/providers/aws/cli-reference/invoke.md @@ -35,7 +35,7 @@ serverless invoke [local] --function functionName # Invoke Local -Invokes a function locally for testing and logs the output. You can only invoke Node.js runtime locally at the moment. Keep in mind that we mock the `context` with simple mock data. +Invokes a function locally for testing and logs the output. Keep in mind that we mock the `context` with simple mock data. ```bash serverless invoke local --function functionName diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index d6e18d93c..20a27a027 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -56,6 +56,7 @@ Here are the available runtimes for AWS Lambda: * aws-python * aws-python3 * aws-kotlin-jvm-maven +* aws-kotlin-nodejs-gradle * aws-groovy-gradle * aws-java-gradle * aws-java-maven diff --git a/docs/providers/azure/guide/quick-start.md b/docs/providers/azure/guide/quick-start.md index d77a97a18..2e51d831b 100644 --- a/docs/providers/azure/guide/quick-start.md +++ b/docs/providers/azure/guide/quick-start.md @@ -13,8 +13,9 @@ layout: Doc 1. Node.js `v6.5.0` or later. *(this is the runtime version supported by Azure Functions)* 2. Serverless CLI `v1.9.0` or later. You can run `npm install -g serverless` to install it. -3. An Azure account. If you don't already have one, you can sign up for a [free trial](https://azure.microsoft.com/en-us/free/) that includes $200 of free credit. -4. **Set-up your [Provider Credentials](./credentials.md)**. +3. Azure plugin that allows you to work with Azure Functions `npm install -g serverless-azure-functions` +4. An Azure account. If you don't already have one, you can sign up for a [free trial](https://azure.microsoft.com/en-us/free/) that includes $200 of free credit. +5. **Set-up your [Provider Credentials](./credentials.md)**. ## Create a new service diff --git a/lib/classes/CLI.js b/lib/classes/CLI.js index 4a186a1a5..cb177428b 100644 --- a/lib/classes/CLI.js +++ b/lib/classes/CLI.js @@ -140,6 +140,7 @@ class CLI { this.consoleLog(chalk.yellow.underline('Commands')); this.consoleLog(chalk.dim('* You can run commands with "serverless" or the shortcut "sls"')); this.consoleLog(chalk.dim('* Pass "--verbose" to this command to get in-depth plugin info')); + this.consoleLog(chalk.dim('* Pass "--no-color" to disable CLI colors')); this.consoleLog(chalk.dim('* Pass "--help" after any for contextual help')); this.consoleLog(''); diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 5e0b3f7d4..75fd3d89e 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -67,8 +67,10 @@ class PluginManager { } // don't load plugins twice - const loadedPlugins = this.plugins.map(plugin => plugin.constructor.name); - if (_.includes(loadedPlugins, Plugin.name)) return; + if (this.plugins.some(plugin => plugin instanceof Plugin)) { + this.serverless.cli.log(`WARNING: duplicate plugin ${Plugin.name} was not loaded\n`); + return; + } this.loadCommands(pluginInstance); this.loadHooks(pluginInstance); diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 9fec8833b..95864844c 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -546,6 +546,28 @@ describe('PluginManager', () => { expect(pluginManager.plugins.length).to.equal(1); }); + it('should load two plugins that happen to have the same class name', () => { + function getFirst() { + return class PluginMock { + }; + } + + function getSecond() { + return class PluginMock { + }; + } + + const first = getFirst(); + const second = getSecond(); + + pluginManager.addPlugin(first); + pluginManager.addPlugin(second); + + expect(pluginManager.plugins[0]).to.be.instanceof(first); + expect(pluginManager.plugins[1]).to.be.instanceof(second); + expect(pluginManager.plugins.length).to.equal(2); + }); + it('should load the plugin commands', () => { pluginManager.addPlugin(SynchronousPluginMock); diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 5e49ac054..7e8c0a888 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -21,7 +21,7 @@ class Variables { this.cfRefSyntax = RegExp(/^cf:/g); this.s3RefSyntax = RegExp(/^s3:(.+?)\/(.+)$/); this.stringRefSynax = RegExp(/('.*')|(".*")/g); - this.ssmRefSyntax = RegExp(/^ssm:([a-zA-Z0-9_.-/]+)[~]?(true|false)?/); + this.ssmRefSyntax = RegExp(/^ssm:([a-zA-Z0-9_.\-/]+)[~]?(true|false)?/); } loadVariableSyntax() { diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index 3efcd7eb6..ac281e9f7 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -946,21 +946,23 @@ describe('Variables', () => { }); it('should get variable from Ssm using regular-style param', () => { + const param = 'Param-01_valid.chars'; + const value = 'MockValue'; const awsResponseMock = { Parameter: { - Value: 'MockValue', + Value: value, }, }; const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - return serverless.variables.getValueFromSsm('ssm:param').then(value => { - expect(value).to.be.equal('MockValue'); + return serverless.variables.getValueFromSsm(`ssm:${param}`).then(resolved => { + expect(resolved).to.be.equal(value); expect(ssmStub.calledOnce).to.be.equal(true); expect(ssmStub.calledWithExactly( 'SSM', 'getParameter', { - Name: 'param', + Name: param, WithDecryption: false, }, serverless.variables.options.stage, @@ -970,21 +972,23 @@ describe('Variables', () => { }); it('should get variable from Ssm using path-style param', () => { + const param = '/path/to/Param-01_valid.chars'; + const value = 'MockValue'; const awsResponseMock = { Parameter: { - Value: 'MockValue', + Value: value, }, }; const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - return serverless.variables.getValueFromSsm('ssm:/some/path/to/param').then(value => { - expect(value).to.be.equal('MockValue'); + return serverless.variables.getValueFromSsm(`ssm:${param}`).then(resolved => { + expect(resolved).to.be.equal(value); expect(ssmStub.calledOnce).to.be.equal(true); expect(ssmStub.calledWithExactly( 'SSM', 'getParameter', { - Name: '/some/path/to/param', + Name: param, WithDecryption: false, }, serverless.variables.options.stage, @@ -994,21 +998,23 @@ describe('Variables', () => { }); it('should get encrypted variable from Ssm using extended syntax', () => { + const param = '/path/to/Param-01_valid.chars'; + const value = 'MockValue'; const awsResponseMock = { Parameter: { - Value: 'MockValue', + Value: value, }, }; const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - return serverless.variables.getValueFromSsm('ssm:/some/path/to/param~true').then(value => { - expect(value).to.be.equal('MockValue'); + return serverless.variables.getValueFromSsm(`ssm:${param}~true`).then(resolved => { + expect(resolved).to.be.equal(value); expect(ssmStub.calledOnce).to.be.equal(true); expect(ssmStub.calledWithExactly( 'SSM', 'getParameter', { - Name: '/some/path/to/param', + Name: param, WithDecryption: true, }, serverless.variables.options.stage, @@ -1018,21 +1024,23 @@ describe('Variables', () => { }); it('should get unencrypted variable from Ssm using extended syntax', () => { + const param = '/path/to/Param-01_valid.chars'; + const value = 'MockValue'; const awsResponseMock = { Parameter: { - Value: 'MockValue', + Value: value, }, }; const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - return serverless.variables.getValueFromSsm('ssm:/some/path/to/param~false').then(value => { - expect(value).to.be.equal('MockValue'); + return serverless.variables.getValueFromSsm(`ssm:${param}~false`).then(resolved => { + expect(resolved).to.be.equal(value); expect(ssmStub.calledOnce).to.be.equal(true); expect(ssmStub.calledWithExactly( 'SSM', 'getParameter', { - Name: '/some/path/to/param', + Name: param, WithDecryption: false, }, serverless.variables.options.stage, @@ -1042,28 +1050,29 @@ describe('Variables', () => { }); it('should ignore bad values for extended syntax', () => { + const param = '/path/to/Param-01_valid.chars'; + const value = 'MockValue'; const awsResponseMock = { Parameter: { - Value: 'MockValue', + Value: value, }, }; const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - return serverless.variables.getValueFromSsm('ssm:/some/path/to/param~badvalue') - .then(value => { - expect(value).to.be.equal('MockValue'); - expect(ssmStub.calledOnce).to.be.equal(true); - expect(ssmStub.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: '/some/path/to/param', - WithDecryption: false, - }, - serverless.variables.options.stage, - serverless.variables.options.region - )).to.be.equal(true); - }); + return serverless.variables.getValueFromSsm(`ssm:${param}~badvalue`).then(resolved => { + expect(resolved).to.be.equal(value); + expect(ssmStub.calledOnce).to.be.equal(true); + expect(ssmStub.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + serverless.variables.options.stage, + serverless.variables.options.region + )).to.be.equal(true); + }); }); it('should return undefined if SSM parameter does not exist', () => { diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.js index 380fcea1e..a6b21e770 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.js @@ -44,7 +44,7 @@ module.exports = { } return this.provider.request('S3', - 'putObject', + 'upload', params, this.options.stage, this.options.region); @@ -73,7 +73,7 @@ module.exports = { } return this.provider.request('S3', - 'putObject', + 'upload', params, this.options.stage, this.options.region); @@ -83,7 +83,7 @@ module.exports = { let shouldUploadService = false; this.serverless.cli.log('Uploading artifacts...'); const functionNames = this.serverless.service.getAllFunctions(); - const uploadPromises = functionNames.map(name => { + return BbPromise.map(functionNames, (name) => { const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(name); const functionObject = this.serverless.service.getFunction(name); functionObject.package = functionObject.package || {}; @@ -100,9 +100,7 @@ module.exports = { return BbPromise.resolve(); } return this.uploadZipFile(artifactFilePath); - }); - - return BbPromise.all(uploadPromises).then(() => { + }, { concurrency: 3 }).then(() => { if (shouldUploadService) { const artifactFileName = this.provider.naming.getServiceArtifactName(); const artifactFilePath = path.join(this.packagePath, artifactFileName); diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index 059495288..7af45d186 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -70,13 +70,13 @@ describe('uploadArtifacts', () => { describe('#uploadCloudFormationFile()', () => { let normalizeCloudFormationTemplateStub; - let putObjectStub; + let uploadStub; beforeEach(() => { normalizeCloudFormationTemplateStub = sinon .stub(normalizeFiles, 'normalizeCloudFormationTemplate') .returns(); - putObjectStub = sinon + uploadStub = sinon .stub(awsDeploy.provider, 'request') .resolves(); }); @@ -91,10 +91,10 @@ describe('uploadArtifacts', () => { return awsDeploy.uploadCloudFormationFile().then(() => { expect(normalizeCloudFormationTemplateStub.calledOnce).to.equal(true); - expect(putObjectStub.calledOnce).to.equal(true); - expect(putObjectStub.calledWithExactly( + expect(uploadStub.calledOnce).to.equal(true); + expect(uploadStub.calledWithExactly( 'S3', - 'putObject', + 'upload', { Bucket: awsDeploy.bucketName, Key: `${awsDeploy.serverless.service.package @@ -121,10 +121,10 @@ describe('uploadArtifacts', () => { return awsDeploy.uploadCloudFormationFile().then(() => { expect(normalizeCloudFormationTemplateStub.calledOnce).to.equal(true); - expect(putObjectStub.calledOnce).to.be.equal(true); - expect(putObjectStub.calledWithExactly( + expect(uploadStub.calledOnce).to.be.equal(true); + expect(uploadStub.calledWithExactly( 'S3', - 'putObject', + 'upload', { Bucket: awsDeploy.bucketName, Key: `${awsDeploy.serverless.service.package @@ -147,13 +147,13 @@ describe('uploadArtifacts', () => { describe('#uploadZipFile()', () => { let readFileSyncStub; - let putObjectStub; + let uploadStub; beforeEach(() => { readFileSyncStub = sinon .stub(fs, 'readFileSync') .returns(); - putObjectStub = sinon + uploadStub = sinon .stub(awsDeploy.provider, 'request') .resolves(); }); @@ -175,11 +175,11 @@ describe('uploadArtifacts', () => { serverless.utils.writeFileSync(artifactFilePath, 'artifact.zip file content'); return awsDeploy.uploadZipFile(artifactFilePath).then(() => { - expect(putObjectStub.calledOnce).to.be.equal(true); + expect(uploadStub.calledOnce).to.be.equal(true); expect(readFileSyncStub.calledOnce).to.equal(true); - expect(putObjectStub.calledWithExactly( + expect(uploadStub.calledWithExactly( 'S3', - 'putObject', + 'upload', { Bucket: awsDeploy.bucketName, Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/artifact.zip`, @@ -207,11 +207,11 @@ describe('uploadArtifacts', () => { }; return awsDeploy.uploadZipFile(artifactFilePath).then(() => { - expect(putObjectStub.calledOnce).to.be.equal(true); + expect(uploadStub.calledOnce).to.be.equal(true); expect(readFileSyncStub.calledOnce).to.equal(true); - expect(putObjectStub.calledWithExactly( + expect(uploadStub.calledWithExactly( 'S3', - 'putObject', + 'upload', { Bucket: awsDeploy.bucketName, Key: `${awsDeploy.serverless.service.package.artifactDirectoryName}/artifact.zip`, diff --git a/lib/plugins/aws/invokeLocal/.gitignore b/lib/plugins/aws/invokeLocal/.gitignore index 0d20b6487..376be9d3e 100644 --- a/lib/plugins/aws/invokeLocal/.gitignore +++ b/lib/plugins/aws/invokeLocal/.gitignore @@ -1 +1,5 @@ *.pyc +java/target +java/.project +java/.settings +java/.classpath diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index 76ad4c4e1..9fafc1c48 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -3,6 +3,7 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); const os = require('os'); +const fs = BbPromise.promisifyAll(require('fs')); const path = require('path'); const validate = require('../lib/validate'); const chalk = require('chalk'); @@ -137,8 +138,17 @@ class AwsInvokeLocal { this.options.context); } + if (runtime === 'java8') { + return this.invokeLocalJava( + 'java', + handler, + this.serverless.service.package.artifact, + this.options.data, + this.options.context); + } + throw new this.serverless.classes - .Error('You can only invoke Node.js & Python functions locally.'); + .Error('You can only invoke Node.js, Python & Java functions locally.'); } invokeLocalPython(runtime, handlerPath, handlerName, event, context) { @@ -163,6 +173,68 @@ class AwsInvokeLocal { }); } + callJavaBridge(artifactPath, className, input) { + return new BbPromise((resolve) => fs.statAsync(artifactPath).then(() => { + const java = spawn('java', [ + `-DartifactPath=${artifactPath}`, + `-DclassName=${className}`, + '-jar', + path.join(__dirname, 'java', 'target', 'invoke-bridge-1.0.jar'), + ]); + + this.serverless.cli.log([ + 'In order to get human-readable output,', + ' please implement "toString()" method of your "ApiGatewayResponse" object.', + ].join('')); + + java.stdout.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stderr.on('data', (buf) => this.serverless.cli.consoleLog(buf.toString())); + java.stdin.write(input); + java.stdin.end(); + java.on('close', () => resolve()); + }).catch(() => { + throw new Error(`Artifact ${artifactPath} doesn't exists, please compile it first.`); + })); + } + + invokeLocalJava(runtime, className, artifactPath, event, customContext) { + const timeout = Number(this.options.functionObj.timeout) + || Number(this.serverless.service.provider.timeout) + || 6; + const context = { + name: this.options.functionObj.name, + version: 'LATEST', + logGroupName: this.provider.naming.getLogGroupName(this.options.functionObj.name), + timeout, + }; + const input = JSON.stringify({ + event: event || {}, + context: customContext || context, + }); + + const javaBridgePath = path.join(__dirname, 'java'); + const executablePath = path.join(javaBridgePath, 'target'); + + return new BbPromise(resolve => fs.statAsync(executablePath) + .then(() => this.callJavaBridge(artifactPath, className, input)) + .then(resolve) + .catch(() => { + const mvn = spawn('mvn', [ + 'package', + '-f', + path.join(javaBridgePath, 'pom.xml'), + ]); + + this.serverless.cli + .log('Building Java bridge, first invocation might take a bit longer.'); + + mvn.stderr.on('data', (buf) => this.serverless.cli.consoleLog(`mvn - ${buf.toString()}`)); + mvn.stdin.end(); + + mvn.on('close', () => this.callJavaBridge(artifactPath, className, input).then(resolve)); + })); + } + invokeLocalNodeJs(handlerPath, handlerName, event, customContext) { let lambda; diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index 4222be2c6..8e73d67ec 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -3,6 +3,9 @@ const expect = require('chai').expect; const sinon = require('sinon'); const path = require('path'); +const mockRequire = require('mock-require'); +const EventEmitter = require('events'); +const fse = require('fs-extra'); const AwsInvokeLocal = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); @@ -276,12 +279,15 @@ describe('AwsInvokeLocal', () => { describe('#invokeLocal()', () => { let invokeLocalNodeJsStub; let invokeLocalPythonStub; + let invokeLocalJavaStub; beforeEach(() => { invokeLocalNodeJsStub = sinon.stub(awsInvokeLocal, 'invokeLocalNodeJs').resolves(); invokeLocalPythonStub = sinon.stub(awsInvokeLocal, 'invokeLocalPython').resolves(); + invokeLocalJavaStub = + sinon.stub(awsInvokeLocal, 'invokeLocalJava').resolves(); awsInvokeLocal.serverless.service.service = 'new-service'; awsInvokeLocal.options = { @@ -298,6 +304,7 @@ describe('AwsInvokeLocal', () => { afterEach(() => { awsInvokeLocal.invokeLocalNodeJs.restore(); awsInvokeLocal.invokeLocalPython.restore(); + awsInvokeLocal.invokeLocalJava.restore(); }); it('should call invokeLocalNodeJs when no runtime is set', () => awsInvokeLocal.invokeLocal() @@ -352,12 +359,26 @@ describe('AwsInvokeLocal', () => { {}, undefined )).to.be.equal(true); - delete awsInvokeLocal.options.functionObj.runtime; }); }); - it('throw error when using runtime other than Node.js or Python', () => { + it('should call invokeLocalJava when java8 runtime is set', () => { awsInvokeLocal.options.functionObj.runtime = 'java8'; + return awsInvokeLocal.invokeLocal() + .then(() => { + expect(invokeLocalJavaStub.calledOnce).to.be.equal(true); + expect(invokeLocalJavaStub.calledWithExactly( + 'java', + 'handler.hello', + undefined, + {}, + undefined + )).to.be.equal(true); + }); + }); + + it('throw error when using runtime other than Node.js or Python', () => { + awsInvokeLocal.options.functionObj.runtime = 'invalid-runtime'; expect(() => awsInvokeLocal.invokeLocal()).to.throw(Error); delete awsInvokeLocal.options.functionObj.runtime; }); @@ -507,4 +528,183 @@ describe('AwsInvokeLocal', () => { }); }); }); + + describe('#callJavaBridge()', () => { + let awsInvokeLocalMocked; + let writeChildStub; + let endChildStub; + + beforeEach(() => { + writeChildStub = sinon.stub(); + endChildStub = sinon.stub(); + + mockRequire('child_process', { + spawn: () => ({ + stderr: new EventEmitter().on('data', () => {}), + stdout: new EventEmitter().on('data', () => {}), + stdin: { + write: writeChildStub, + end: endChildStub, + }, + on: (key, callback) => callback(), + }), + }); + + // Remove Node.js internal "require cache" contents and re-require ./index.js + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + + const AwsInvokeLocalMocked = require('./index'); // eslint-disable-line global-require + + serverless.setProvider('aws', new AwsProvider(serverless)); + awsInvokeLocalMocked = new AwsInvokeLocalMocked(serverless, options); + + awsInvokeLocalMocked.options = { + stage: 'dev', + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + }, + data: {}, + }; + }); + + afterEach(() => { + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + }); + + it('spawns java process with correct arguments', () => + awsInvokeLocalMocked.callJavaBridge( + __dirname, + 'com.serverless.Handler', + '{}' + ).then(() => { + expect(writeChildStub.calledOnce).to.be.equal(true); + expect(endChildStub.calledOnce).to.be.equal(true); + expect(writeChildStub.calledWithExactly('{}')).to.be.equal(true); + }) + ); + }); + + describe('#invokeLocalJava()', () => { + const bridgePath = path.join(__dirname, 'java', 'target'); + let callJavaBridgeStub; + + beforeEach(() => { + fse.mkdirsSync(bridgePath); + callJavaBridgeStub = sinon.stub(awsInvokeLocal, 'callJavaBridge').resolves(); + awsInvokeLocal.options = { + stage: 'dev', + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + }, + data: {}, + }; + }); + + afterEach(() => { + awsInvokeLocal.callJavaBridge.restore(); + fse.removeSync(bridgePath); + }); + + it('should invoke callJavaBridge when bridge is built', () => + awsInvokeLocal.invokeLocalJava( + 'java', + 'com.serverless.Handler', + __dirname, + {} + ).then(() => { + expect(callJavaBridgeStub.calledOnce).to.be.equal(true); + expect(callJavaBridgeStub.calledWithExactly( + __dirname, + 'com.serverless.Handler', + JSON.stringify({ + event: {}, + context: { + name: 'hello', + version: 'LATEST', + logGroupName: '/aws/lambda/hello', + timeout: 4, + }, + }) + )).to.be.equal(true); + }) + ); + + describe('when attempting to build the Java bridge', () => { + let awsInvokeLocalMocked; + let callJavaBridgeMockedStub; + + beforeEach(() => { + mockRequire('child_process', { + spawn: () => ({ + stderr: new EventEmitter().on('data', () => {}), + stdout: new EventEmitter().on('data', () => {}), + stdin: { + write: () => {}, + end: () => {}, + }, + on: (key, callback) => callback(), + }), + }); + + // Remove Node.js internal "require cache" contents and re-require ./index.js + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + + const AwsInvokeLocalMocked = require('./index'); // eslint-disable-line global-require + + serverless.setProvider('aws', new AwsProvider(serverless)); + awsInvokeLocalMocked = new AwsInvokeLocalMocked(serverless, options); + callJavaBridgeMockedStub = sinon.stub(awsInvokeLocalMocked, 'callJavaBridge').resolves(); + + awsInvokeLocalMocked.options = { + stage: 'dev', + function: 'first', + functionObj: { + handler: 'handler.hello', + name: 'hello', + timeout: 4, + }, + data: {}, + }; + }); + + afterEach(() => { + awsInvokeLocalMocked.callJavaBridge.restore(); + delete require.cache[require.resolve('./index')]; + delete require.cache[require.resolve('child_process')]; + }); + + it('if it\'s not present yet', () => + awsInvokeLocalMocked.invokeLocalJava( + 'java', + 'com.serverless.Handler', + __dirname, + {} + ).then(() => { + expect(callJavaBridgeMockedStub.calledOnce).to.be.equal(true); + expect(callJavaBridgeMockedStub.calledWithExactly( + __dirname, + 'com.serverless.Handler', + JSON.stringify({ + event: {}, + context: { + name: 'hello', + version: 'LATEST', + logGroupName: '/aws/lambda/hello', + timeout: 4, + }, + }) + )).to.be.equal(true); + }) + ); + }); + }); }); diff --git a/lib/plugins/aws/invokeLocal/java/MANIFEST.mf b/lib/plugins/aws/invokeLocal/java/MANIFEST.mf new file mode 100644 index 000000000..3cc01de12 --- /dev/null +++ b/lib/plugins/aws/invokeLocal/java/MANIFEST.mf @@ -0,0 +1,2 @@ +Manifest-version: 1.0 +Main-Class: com.serverless.InvokeBridge diff --git a/lib/plugins/aws/invokeLocal/java/pom.xml b/lib/plugins/aws/invokeLocal/java/pom.xml new file mode 100644 index 000000000..c47be034b --- /dev/null +++ b/lib/plugins/aws/invokeLocal/java/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + com.serverless + invoke-bridge + 1.0 + + + 1.8 + 1.8 + + + + + com.amazonaws + aws-lambda-java-core + 1.1.0 + + + com.amazonaws + aws-lambda-java-log4j + 1.0.0 + + + com.fasterxml.jackson.core + jackson-core + 2.8.5 + + + com.fasterxml.jackson.core + jackson-databind + 2.8.5 + + + com.fasterxml.jackson.core + jackson-annotations + 2.8.5 + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + false + + + + package + + shade + + + + + com.serverless.InvokeBridge + + + + + + + + + diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java new file mode 100644 index 000000000..b0c93945c --- /dev/null +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/Context.java @@ -0,0 +1,86 @@ +package com.serverless; + +import com.amazonaws.services.lambda.runtime.Client; +import com.amazonaws.services.lambda.runtime.ClientContext; +import com.amazonaws.services.lambda.runtime.CognitoIdentity; +import com.amazonaws.services.lambda.runtime.LambdaLogger; + +import java.util.Map; + +public class Context implements com.amazonaws.services.lambda.runtime.Context { + private String name; + private String version; + private String logGroupName; + private long endTime; + + Context(String name, String version, String logGroupName, int timeout) { + this.name = name; + this.version = version; + this.logGroupName = logGroupName; + this.endTime = System.currentTimeMillis() + (timeout * 1000); + } + + public String getAwsRequestId() { + return "1234567890"; + } + + public String getLogGroupName() { + return this.logGroupName; + } + + public String getLogStreamName() { + return "LogStream_" + this.name; + } + + public String getFunctionName() { + return this.name; + } + + public String getFunctionVersion() { + return this.version; + } + + public String getInvokedFunctionArn() { + return "arn:aws:lambda:serverless:" + this.name; + } + + public CognitoIdentity getIdentity() { + return new CognitoIdentity() { + public String getIdentityId() { + return "1"; + } + + public String getIdentityPoolId() { + return "1"; + } + }; + } + + public ClientContext getClientContext() { + return new ClientContext() { + public Client getClient() { + return null; + } + + public Map getCustom() { + return null; + } + + public Map getEnvironment() { + return System.getenv(); + } + }; + } + + public int getRemainingTimeInMillis() { + return Math.max(0, (int) (this.endTime - System.currentTimeMillis())); + } + + public int getMemoryLimitInMB() { + return 1024; + } + + public LambdaLogger getLogger() { + return System.out::println; + } +} diff --git a/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java new file mode 100644 index 000000000..ab9629426 --- /dev/null +++ b/lib/plugins/aws/invokeLocal/java/src/main/java/com/serverless/InvokeBridge.java @@ -0,0 +1,88 @@ +package com.serverless; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.HashMap; + +public class InvokeBridge { + private File artifact; + private String className; + private Object instance; + private Class clazz; + + private InvokeBridge() { + this.artifact = new File(new File("."), System.getProperty("artifactPath")); + this.className = System.getProperty("className"); + + try { + HashMap parsedInput = parseInput(getInput()); + HashMap eventMap = (HashMap) parsedInput.get("event"); + + this.instance = this.getInstance(); + + System.out.println(this.invoke(eventMap, this.getContext(parsedInput)).toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Context getContext(HashMap parsedInput) { + HashMap contextMap = (HashMap) parsedInput.get("context"); + + String name = (String) contextMap.getOrDefault("name", "functionName"); + String version = (String) contextMap.getOrDefault("version", "LATEST"); + String logGroupName = (String) contextMap.getOrDefault("logGroupName", "logGroup"); + int timeout = Integer.parseInt(String.valueOf(contextMap.getOrDefault("timeout", 5))); + + return new Context(name, version, logGroupName, timeout); + } + + private Object getInstance() throws Exception { + URL[] urls = {this.artifact.toURI().toURL()}; + URLClassLoader child = new URLClassLoader(urls, this.getClass().getClassLoader()); + + this.clazz = Class.forName(this.className, true, child); + + return this.clazz.newInstance(); + } + + private Object invoke(HashMap event, Context context) throws Exception { + Method[] methods = this.clazz.getDeclaredMethods(); + + return methods[1].invoke(this.instance, event, context); + } + + private HashMap parseInput(String input) throws IOException { + TypeReference> typeRef = new TypeReference>() {}; + ObjectMapper mapper = new ObjectMapper(); + + JsonNode jsonNode = mapper.readTree(input); + + return mapper.convertValue(jsonNode, typeRef); + } + + private String getInput() throws IOException { + BufferedReader streamReader = new BufferedReader(new InputStreamReader(System.in, "UTF-8")); + StringBuilder inputStringBuilder = new StringBuilder(); + String inputStr; + + while ((inputStr = streamReader.readLine()) != null) { + inputStringBuilder.append(inputStr); + } + + return inputStringBuilder.toString(); + } + + public static void main(String[] args) { + new InvokeBridge(); + } +} diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index d552a1732..0bb965e98 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -17,6 +17,7 @@ const validTemplates = [ 'aws-java-maven', 'aws-java-gradle', 'aws-kotlin-jvm-maven', + 'aws-kotlin-nodejs-gradle', 'aws-scala-sbt', 'aws-csharp', 'aws-fsharp', diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index cf3591dc1..00567485e 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -228,6 +228,34 @@ describe('Create', () => { }); }); + it('should generate scaffolding for "aws-kotlin-nodejs-gradle" template', () => { + process.chdir(tmpDir); + create.options.template = 'aws-kotlin-nodejs-gradle'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include('build.gradle'); + expect(dirContent).to.include('gradlew'); + expect(dirContent).to.include('gradlew.bat'); + expect(dirContent).to.include('package.json'); + expect(dirContent).to.include(path.join('gradle', 'wrapper', + 'gradle-wrapper.jar')); + expect(dirContent).to.include(path.join('gradle', 'wrapper', + 'gradle-wrapper.properties')); + expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', + 'Handler.kt')); + expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', + 'ApiGatewayResponse.kt')); + expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', + 'Response.kt')); + expect(dirContent).to.include(path.join('src', 'test', 'kotlin', '.gitkeep')); + expect(dirContent).to.include(path.join('.gitignore')); + }); + }); + it('should generate scaffolding for "aws-java-gradle" template', () => { process.chdir(tmpDir); create.options.template = 'aws-java-gradle'; diff --git a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java index 80a6f78e8..6d6c670e5 100644 --- a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java +++ b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java @@ -3,6 +3,7 @@ package com.serverless; import java.util.Collections; import java.util.Map; +import org.apache.log4j.BasicConfigurator; import org.apache.log4j.Logger; import com.amazonaws.services.lambda.runtime.Context; @@ -14,6 +15,8 @@ public class Handler implements RequestHandler, ApiGatewayRe @Override public ApiGatewayResponse handleRequest(Map input, Context context) { + BasicConfigurator.configure(); + LOG.info("received: " + input); Response responseBody = new Response("Go Serverless v1.x! Your function executed successfully!", input); return ApiGatewayResponse.builder() diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/build.gradle b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/build.gradle new file mode 100644 index 000000000..907c2cf6c --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/build.gradle @@ -0,0 +1,31 @@ +group 'serverless-kotlin-node' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.1.1' + repositories { + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +apply plugin: 'kotlin2js' + +repositories { + mavenCentral() +} + +dependencies { + compile ( + "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version" + ) + +} + +compileKotlin2Js.kotlinOptions { + moduleKind = "commonjs" + outputFile = "build/index.js" +} \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gitignore b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gitignore new file mode 100644 index 000000000..edbd15784 --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gitignore @@ -0,0 +1,15 @@ +*.class +target +/bin/ +/.settings/ +.project +.classpath +.gradle +build/ + +# Serverless directories +.serverless% + +# package directories +node_modules +jspm_packages diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradle/wrapper/gradle-wrapper.jar b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..a63555747 Binary files /dev/null and b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradle/wrapper/gradle-wrapper.properties b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..be3292d1f --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Sep 09 11:10:27 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-bin.zip diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradlew b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradlew new file mode 100755 index 000000000..4453ccea3 --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradlew.bat b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradlew.bat new file mode 100755 index 000000000..e95643d6a --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/package.json b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/package.json new file mode 100644 index 000000000..192e915c2 --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "kotlin": "^1.1.4" + } +} diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml new file mode 100644 index 000000000..c590ec2e2 --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -0,0 +1,94 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: aws-kotlin-nodejs-gradle # NOTE: update this with your service name + +# You can pin your service to only deploy with a specific Serverless version +# Check out our docs for more details +# frameworkVersion: "=X.X.X" + +provider: + name: aws + runtime: nodejs6.10 + +# you can overwrite defaults here +# stage: dev +# region: us-east-1 + +# you can add statements to the Lambda function's IAM Role here +# iamRoleStatements: +# - Effect: "Allow" +# Action: +# - "s3:ListBucket" +# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } +# - Effect: "Allow" +# Action: +# - "s3:PutObject" +# Resource: +# Fn::Join: +# - "" +# - - "arn:aws:s3:::" +# - "Ref" : "ServerlessDeploymentBucket" +# - "/*" + +# you can define service wide environment variables here +# environment: +# variable1: value1 + +functions: + hello: + handler: build/index.Handler + +# The following are a few example events you can configure +# NOTE: Please make sure to change your handler code to work with those events +# Check the event documentation for details +# events: +# - http: +# path: users/create +# method: get +# - s3: ${env:BUCKET} +# - schedule: rate(10 minutes) +# - sns: greeter-topic +# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 +# - alexaSkill +# - iot: +# sql: "SELECT * FROM 'some_topic'" +# - cloudwatchEvent: +# event: +# source: +# - "aws.ec2" +# detail-type: +# - "EC2 Instance State-change Notification" +# detail: +# state: +# - pending +# - cloudwatchLog: '/aws/lambda/hello' +# - cognitoUserPool: +# pool: MyUserPool +# trigger: PreSignUp + +# Define function environment variables here +# environment: +# variable2: value2 + +# you can add CloudFormation resource templates here +#resources: +# Resources: +# NewResource: +# Type: AWS::S3::Bucket +# Properties: +# BucketName: my-new-bucket +# Outputs: +# NewOutput: +# Description: "Description for the output" +# Value: "Some output value" \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/ApiGatewayResponse.kt b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/ApiGatewayResponse.kt new file mode 100644 index 000000000..ce3a5f365 --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/ApiGatewayResponse.kt @@ -0,0 +1,31 @@ +class ApiGatewayResponse( + val statusCode: Int = 200, + var body: String? = null, + val headers: dynamic, + val isBase64Encoded: Boolean = false +) { + companion object { + inline fun build(block: Builder.() -> Unit) = Builder().apply(block).build() + } + + class Builder { + var statusCode: Int = 200 + var rawBody: String? = null + var headers: dynamic = object{} + var objectBody: Response? = null + var binaryBody: ByteArray? = null + var base64Encoded: Boolean = false + + fun build(): ApiGatewayResponse { + var body: String? = null + + when { + rawBody != null -> body = rawBody as String + objectBody != null -> body = objectBody.toString() + binaryBody != null -> body = binaryBody.toString() + } + + return ApiGatewayResponse(statusCode, body, headers, base64Encoded) + } + } +} diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/Handler.kt b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/Handler.kt new file mode 100644 index 000000000..bdd4344dc --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/Handler.kt @@ -0,0 +1,14 @@ +@JsName("Handler") +public fun Handler(input: dynamic = {}, context: Any, callback: (Any?, ApiGatewayResponse) -> ApiGatewayResponse): Any { + println("Received: " + js("JSON.stringify(input)")); + + val responseBody: Response = Response("Go Serverless v1.x! Your Kotlin function executed successfully!", input); + val responseHeaders: dynamic = object{} + responseHeaders["X-Powered-By"] = "AWS Lambda & serverless" + + return callback(null, ApiGatewayResponse.build { + statusCode = 201 + objectBody = responseBody + headers = responseHeaders + }) +} diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/Response.kt b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/Response.kt new file mode 100644 index 000000000..a4809294c --- /dev/null +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/main/kotlin/com/serverless/Response.kt @@ -0,0 +1,12 @@ +class Response(message: String, input: dynamic) { + val message: String = message + get + val input: dynamic = input + get + + override fun toString(): String { + val stringified = js("JSON.stringify(this.input)") + + return "{\"$message\": ${stringified} }"; + } +} diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/test/kotlin/.gitkeep b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/src/test/kotlin/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml index f732c5a9c..62e5090fb 100644 --- a/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-ecma-script/serverless.yml @@ -24,4 +24,3 @@ functions: - http: method: get path: second - integration: lambda diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts b/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts index 280d14b94..679db546b 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts +++ b/lib/plugins/create/templates/aws-nodejs-typescript/handler.ts @@ -1,3 +1,11 @@ -export const hello = (event, context, cb) => cb(null, - { message: 'Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!', event } -); \ No newline at end of file +export const hello = (event, context, cb) => { + const response = { + statusCode: 200, + body: JSON.stringify({ + message: 'Go Serverless Webpack (Typescript) v1.0! Your function executed successfully!', + input: event, + }), + }; + + cb(null, response); +} diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/package.json b/lib/plugins/create/templates/aws-nodejs-typescript/package.json index dd30cbaae..ce7044b11 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/package.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/package.json @@ -7,10 +7,10 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { - "serverless-webpack": "^2.2.0", - "ts-loader": "^2.3.1", - "typescript": "^2.4.2", - "webpack": "^3.3.0" + "serverless-webpack": "^3.0.0", + "ts-loader": "^2.3.7", + "typescript": "^2.5.2", + "webpack": "^3.6.0" }, "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml index ec6923fd5..bb8a2f566 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml @@ -10,13 +10,9 @@ provider: runtime: nodejs6.10 functions: - # Example with LAMBDA-PROXY integration - # Invoking locally: - # sls webpack invoke -f hello hello: handler: handler.hello events: - http: method: get path: hello - integration: lambda diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js index 58534724e..b15fcd6d4 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js @@ -1,9 +1,17 @@ const path = require('path'); -// eslint-disable-next-line import/no-unresolved const slsw = require('serverless-webpack'); module.exports = { entry: slsw.lib.entries, + resolve: { + extensions: [ + '.js', + '.jsx', + '.json', + '.ts', + '.tsx' + ] + }, output: { libraryTarget: 'commonjs', path: path.join(__dirname, '.webpack'), diff --git a/lib/utils/log/serverlessLog.js b/lib/utils/log/serverlessLog.js new file mode 100644 index 000000000..94ba61795 --- /dev/null +++ b/lib/utils/log/serverlessLog.js @@ -0,0 +1,11 @@ +'use strict'; + +/* eslint-disable no-console */ + +const chalk = require('chalk'); + +const log = function (message) { + console.log(`Serverless: ${chalk.yellow(message)}`); +}; + +module.exports = log; diff --git a/lib/utils/yamlAstParser.js b/lib/utils/yamlAstParser.js index ff6d4435e..7d91f55f9 100644 --- a/lib/utils/yamlAstParser.js +++ b/lib/utils/yamlAstParser.js @@ -5,6 +5,8 @@ const BbPromise = require('bluebird'); const fs = BbPromise.promisifyAll(require('fs')); const _ = require('lodash'); const os = require('os'); +const chalk = require('chalk'); +const log = require('./log/serverlessLog'); const findKeyChain = (astContent) => { let content = astContent; @@ -24,6 +26,16 @@ const parseAST = (ymlAstContent, astObject) => { let newAstObject = astObject || {}; if (ymlAstContent.mappings && _.isArray(ymlAstContent.mappings)) { _.forEach(ymlAstContent.mappings, (v) => { + if (!v.value) { + const errorMessage = [ + 'Serverless: ', + `${chalk.red('Your serverless.yml has an invalid value with key:')} `, + `${chalk.red(`"${v.key.value}"`)}`, + ].join(''); + log(errorMessage); + return; + } + if (v.key.kind === 0 && v.value.kind === 0) { newAstObject[findKeyChain(v)] = v.value; } else if (v.key.kind === 0 && v.value.kind === 2) { diff --git a/lib/utils/yamlAstParser.test.js b/lib/utils/yamlAstParser.test.js index 0d3cfa332..9e04b4a0e 100644 --- a/lib/utils/yamlAstParser.test.js +++ b/lib/utils/yamlAstParser.test.js @@ -20,7 +20,7 @@ describe('#yamlAstParser', () => { it('should add a top level object and item into the yaml file', () => { const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - writeFileSync(yamlFilePath, 'serveice: test-service'); + writeFileSync(yamlFilePath, 'service: test-service'); return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel', 'foo')) .to.be.fulfilled.then(() => { const yaml = readFileSync(yamlFilePath, 'utf8'); @@ -44,7 +44,7 @@ describe('#yamlAstParser', () => { it('should add a multiple level object and item into the yaml file', () => { const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - writeFileSync(yamlFilePath, 'serveice: test-service'); + writeFileSync(yamlFilePath, 'service: test-service'); return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel.second.third', 'foo')) .to.be.fulfilled.then(() => { const yaml = readFileSync(yamlFilePath, 'utf8'); @@ -90,6 +90,18 @@ describe('#yamlAstParser', () => { expect(yaml.toplevel).to.be.deep.equal(['foo']); }); }); + + it('should survive with invalid yaml', () => { + const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); + + writeFileSync(yamlFilePath, 'service:'); + return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel', 'foo')) + .to.be.fulfilled.then(() => { + const yaml = readFileSync(yamlFilePath, 'utf8'); + expect(yaml).to.have.property('toplevel'); + expect(yaml.toplevel).to.be.deep.equal(['foo']); + }); + }); }); describe('#removeExistingArrayItem()', () => { diff --git a/tests/templates/test_all_templates b/tests/templates/test_all_templates index 2a78e6c9f..66e47499b 100755 --- a/tests/templates/test_all_templates +++ b/tests/templates/test_all_templates @@ -18,6 +18,7 @@ integration-test aws-nodejs integration-test aws-python integration-test aws-python3 integration-test aws-kotlin-jvm-maven +integration-test aws-kotlin-nodejs-gradle integration-test aws-nodejs-typescript integration-test aws-nodejs-ecma-script integration-test google-nodejs