diff --git a/.gitignore b/.gitignore index 54f8012af..c1351c17d 100755 --- a/.gitignore +++ b/.gitignore @@ -55,4 +55,4 @@ jest # DotNet [Bb]in/ -[Oo]bj/ \ No newline at end of file +[Oo]bj/ diff --git a/docker-compose.yml b/docker-compose.yml index 4c721f1af..cbc760b01 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,7 +56,7 @@ services: volumes: - ./tmp/serverless-integration-test-aws-scala-sbt:/app aws-csharp: - image: microsoft/dotnet:1.0.4-sdk + image: microsoft/dotnet:2.0-sdk volumes: - ./tmp/serverless-integration-test-aws-csharp:/app aws-fsharp: @@ -99,7 +99,7 @@ services: - ./tmp/serverless-integration-test-spotinst-ruby:/app spotinst-java8: image: maven:3-jdk-8 - volumes: + volumes: - ./tmp/serverless-integration-test-spotinst-java8:/app webtasks-nodejs: image: node:6.10.3 diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 724cacd4a..c5a6f0dac 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -12,6 +12,32 @@ layout: Doc # API Gateway +- [Lambda Proxy Integration](#lambda-proxy-integration) + - [Simple HTTP Endpoint](#simple-http-endpoint) + - [Example "LAMBDA-PROXY" event (default)](#example-lambda-proxy-event-default) + - [HTTP Endpoint with Extended Options](#http-endpoint-with-extended-options) + - [Enabling CORS](#enabling-cors) + - [HTTP Endpoints with `AWS_IAM` Authorizers](#http-endpoints-with-awsiam-authorizers) + - [HTTP Endpoints with Custom Authorizers](#http-endpoints-with-custom-authorizers) + - [Catching Exceptions In Your Lambda Function](#catching-exceptions-in-your-lambda-function) + - [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api) + - [Request Parameters](#request-parameters) +- [Lambda Integration](#lambda-integration) + - [Example "LAMBDA" event (before customization)](#example-lambda-event-before-customization) + - [Request templates](#request-templates) + - [Default Request Templates](#default-request-templates) + - [Custom Request Templates](#custom-request-templates) + - [Pass Through Behavior](#pass-through-behavior) + - [Responses](#responses) + - [Custom Response Headers](#custom-response-headers) + - [Custom Response Templates](#custom-response-templates) + - [Status codes](#status-codes) + - [Available Status Codes](#available-status-codes) + - [Using Status Codes](#using-status-codes) + - [Custom Status Codes](#custom-status-codes) +- [Setting an HTTP Proxy on API Gateway](#setting-an-http-proxy-on-api-gateway) +- [Share API Gateway and API Resources](#share-api-gateway-and-api-resources) + _Are you looking for tutorials on using API Gateway? Check out the following resources:_ > - [Add a custom domain for your API Gateway](https://serverless.com/blog/serverless-api-gateway-domain/) @@ -634,33 +660,7 @@ See the [api gateway documentation](https://docs.aws.amazon.com/apigateway/lates **Notes:** - A missing/empty request Content-Type is considered to be the API Gateway default (`application/json`) - - [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/apigateway) -- [API Gateway](#api-gateway) - - [Lambda Proxy Integration](#lambda-proxy-integration) - - [Simple HTTP Endpoint](#simple-http-endpoint) - - [Example "LAMBDA-PROXY" event (default)](#example-lambda-proxy-event-default) - - [HTTP Endpoint with Extended Options](#http-endpoint-with-extended-options) - - [Enabling CORS](#enabling-cors) - - [HTTP Endpoints with `AWS_IAM` Authorizers](#http-endpoints-with-awsiam-authorizers) - - [HTTP Endpoints with Custom Authorizers](#http-endpoints-with-custom-authorizers) - - [Catching Exceptions In Your Lambda Function](#catching-exceptions-in-your-lambda-function) - - [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api) - - [Request Parameters](#request-parameters) - - [Lambda Integration](#lambda-integration) - - [Example "LAMBDA" event (before customization)](#example-lambda-event-before-customization) - - [Request templates](#request-templates) - - [Default Request Templates](#default-request-templates) - - [Custom Request Templates](#custom-request-templates) - - [Pass Through Behavior](#pass-through-behavior) - - [Responses](#responses) - - [Custom Response Headers](#custom-response-headers) - - [Custom Response Templates](#custom-response-templates) - - [Status codes](#status-codes) - - [Available Status Codes](#available-status-codes) - - [Using Status Codes](#using-status-codes) - - [Custom Status Codes](#custom-status-codes) - - [Setting an HTTP Proxy on API Gateway](#setting-an-http-proxy-on-api-gateway) - - [Share API Gateway and API Resources](#share-api-gateway-and-api-resources) +- API Gateway docs refer to "WHEN_NO_TEMPLATE" (singular), but this will fail during creation as the actual value should be "WHEN_NO_TEMPLATES" (plural) ### Responses @@ -862,7 +862,7 @@ As your application grows, you will likely need to break it out into multiple, s ```yml service: service-name -provider: +provider: name: aws apiGateway: restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework @@ -873,13 +873,14 @@ functions: ``` + If your application has many nested paths, you might also want to break them out into smaller services. ```yml service: service-a -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx + restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx functions: @@ -893,10 +894,10 @@ functions: ```yml service: service-b -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx - restApiRootResourceId: xxxxxxxxxx + restApiId: xxxxxxxxxx + restApiRootResourceId: xxxxxxxxxx functions: create: @@ -911,13 +912,13 @@ The above example services both reference the same parent path `/posts`. However ```yml service: service-a -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx + restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx restApiResources: /posts: xxxxxxxxxx - + functions: ... @@ -925,10 +926,10 @@ functions: ```yml service: service-b -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx - restApiRootResourceId: xxxxxxxxxx + restApiId: xxxxxxxxxx + restApiRootResourceId: xxxxxxxxxx restApiResources: /posts: xxxxxxxxxx @@ -942,14 +943,14 @@ You can define more than one path resource, but by default, Serverless will gene ```yml service: service-a -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx + restApiId: xxxxxxxxxx # restApiRootResourceId: xxxxxxxxxx # Optional restApiResources: /posts: xxxxxxxxxx /categories: xxxxxxxxx - + functions: listPosts: diff --git a/docs/providers/aws/examples/hello-world/csharp/Handler.cs b/docs/providers/aws/examples/hello-world/csharp/Handler.cs deleted file mode 100644 index cf4ff9549..000000000 --- a/docs/providers/aws/examples/hello-world/csharp/Handler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Amazon.Lambda.Core; -using System; - -[assembly:LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] - -namespace AwsDotnetCsharp -{ - public class Handler - { - public Response Hello(Request request) - { - return new Response("Go Serverless v1.0! Your function executed successfully!", request); - } - } - - public class Response - { - public string Message {get; set;} - public Request Request {get; set;} - - public Response(string message, Request request){ - Message = message; - Request = request; - } - } - - public class Request - { - public string Key1 {get; set;} - public string Key2 {get; set;} - public string Key3 {get; set;} - - public Request(string key1, string key2, string key3){ - Key1 = key1; - Key2 = key2; - Key3 = key3; - } - } -} diff --git a/docs/providers/aws/examples/hello-world/csharp/README.md b/docs/providers/aws/examples/hello-world/csharp/README.md index f230f7f88..371c7dc36 100644 --- a/docs/providers/aws/examples/hello-world/csharp/README.md +++ b/docs/providers/aws/examples/hello-world/csharp/README.md @@ -35,7 +35,7 @@ Using the `create` command we can specify one of the available [templates](https The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. -## 2. Build using .NET CLI tools and create zip package +## 2. Build using .NET Core 2.X CLI tools and create zip package ``` # Linux or Mac OS diff --git a/docs/providers/aws/examples/hello-world/csharp/csharp.csproj b/docs/providers/aws/examples/hello-world/csharp/csharp.csproj deleted file mode 100644 index c0d2e4c69..000000000 --- a/docs/providers/aws/examples/hello-world/csharp/csharp.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp1.0 - CsharpHandlers - csharp - - - - - - - - - - - - diff --git a/docs/providers/aws/examples/hello-world/csharp/serverless.yml b/docs/providers/aws/examples/hello-world/csharp/serverless.yml deleted file mode 100644 index 3e622e1f5..000000000 --- a/docs/providers/aws/examples/hello-world/csharp/serverless.yml +++ /dev/null @@ -1,85 +0,0 @@ -# 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-csharp # 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: dotnetcore1.0 - -# 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 - -# you can add packaging information here -package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - -functions: - hello: - handler: CsharpHandlers::AwsDotnetCsharp.Handler::Hello - -# 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 - -# 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" diff --git a/docs/providers/aws/examples/hello-world/fsharp/README.md b/docs/providers/aws/examples/hello-world/fsharp/README.md index b973a9c46..f1e14c97d 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/README.md +++ b/docs/providers/aws/examples/hello-world/fsharp/README.md @@ -5,6 +5,10 @@ description: Create a F# Hello World Lambda function layout: Doc --> + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/fsharp/) + + # Hello World F# Example Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). diff --git a/docs/providers/aws/examples/hello-world/go/README.md b/docs/providers/aws/examples/hello-world/go/README.md index 1720c009b..05a233a33 100644 --- a/docs/providers/aws/examples/hello-world/go/README.md +++ b/docs/providers/aws/examples/hello-world/go/README.md @@ -30,7 +30,6 @@ You should also have [go](https://golang.org/doc/install) and [make](https://www It is always good practice to organize your `go` projects within [GOPATH](https://golang.org/doc/code.html#GOPATH), to maximize the benefits of go tooling. ## 1. Create a service -There are two templates for `go`: The Serverless Framework includes starter templates for various languages and providers. There are two templates for `go`. @@ -50,7 +49,7 @@ sls create --template aws-go --path myService sls create --template aws-go-dep --path myService ``` -Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-nodejs with the `--template` or shorthand `-t` flag. +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-go-dep with the `--template` or shorthand `-t` flag. The `--path` or shorthand `-p` is the location to be created with the template service files. diff --git a/docs/providers/aws/guide/iam.md b/docs/providers/aws/guide/iam.md index 19aacd6f3..9a0164cce 100644 --- a/docs/providers/aws/guide/iam.md +++ b/docs/providers/aws/guide/iam.md @@ -45,7 +45,17 @@ provider: - "/*" ``` +Alongside `provider.iamRoleStatements` managed policies can also be added to this service-wide Role, define managed policies in `provider.iamManagedPolicies`. These will also be merged into the generated IAM Role so you can use `Join`, `Ref` or any other CloudFormation method or feature here too. +```yml +service: new-service +provider: + name: aws + iamManagedPolicies: + - 'some:aws:arn:xxx:*:*' + - 'someOther:aws:arn:xxx:*:*' + - { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] } +``` ## Custom IAM Roles **WARNING:** You need to take care of the overall role setup as soon as you define custom roles. diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index a10c2767d..3660e307c 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -62,6 +62,8 @@ provider: rateLimit: 100 stackTags: # Optional CF stack tags key: value + iamManagedPolicies: # Optional IAM Managed Policies, which allows to include the policies into IAM Role + - arn:aws:iam:*****:policy/some-managed-policy iamRoleStatements: # IAM role statements so that services can be accessed in the AWS account - Effect: 'Allow' Action: @@ -103,6 +105,8 @@ package: # Optional deployment packaging configuration - .git/** - .travis.yml excludeDevDependencies: false # Config if Serverless should automatically exclude dev dependencies in the deployment package. Defaults to true + artifact: path/to/my-artifact.zip # Own package that should be used. You must provide this file. + individually: true # Enables individual packaging for each function. If true you must provide package for each function. Defaults to false functions: @@ -127,6 +131,15 @@ functions: subnetIds: - subnetId1 - subnetId2 + package: + include: # Specify the directories and files which should be included in the deployment package for this specific function. + - src/** + - handler.js + exclude: # Specify the directories and files which should be excluded in the deployment package for this specific function. + - .git/** + - .travis.yml + artifact: path/to/my-artifact.zip # Own package that should be use for this specific function. You must provide this file. + individually: true # Enables individual packaging for specific function. If true you must provide package for each function. Defaults to false events: # The Events that trigger this Function - http: # This creates an API Gateway HTTP endpoint which can be used to trigger this function. Learn more in "events/apigateway" path: users/create # Path for this endpoint diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index 24d33dab1..57e2975e9 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -116,6 +116,7 @@ provider: Action: - Update:Replace - Update:Delete + Resource: "*" Condition: StringEquals: ResourceType: diff --git a/docs/providers/azure/guide/intro.md b/docs/providers/azure/guide/intro.md index f212424a9..f33a846c7 100644 --- a/docs/providers/azure/guide/intro.md +++ b/docs/providers/azure/guide/intro.md @@ -78,7 +78,7 @@ functions: # Your "Functions" x-azure-settings: name: req methods: - - POST + - post route: /users/create usersDelete: events: @@ -86,7 +86,7 @@ functions: # Your "Functions" x-azure-settings: name: req methods: - - DELETE + - delete route: /users/delete ``` diff --git a/docs/providers/kubeless/events/pubsub.md b/docs/providers/kubeless/events/pubsub.md index d3ef2065b..cd2f707ae 100644 --- a/docs/providers/kubeless/events/pubsub.md +++ b/docs/providers/kubeless/events/pubsub.md @@ -49,4 +49,4 @@ serverless logs -f hello hello world! ``` -You can install the Kubeless CLI tool following the [../guide/installation](installation guide). +You can install the Kubeless CLI tool following the [installation guide](../guide/installation.md). diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.js index 951d11ace..8d6c94556 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.js @@ -2,6 +2,7 @@ /* eslint-disable no-use-before-define */ +const _ = require('lodash'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); @@ -9,6 +10,8 @@ const BbPromise = require('bluebird'); const filesize = require('filesize'); const normalizeFiles = require('../../lib/normalizeFiles'); +const NUM_CONCURRENT_UPLOADS = 3; + module.exports = { uploadArtifacts() { return BbPromise.bind(this) @@ -76,36 +79,36 @@ module.exports = { }, uploadFunctions() { - let shouldUploadService = false; this.serverless.cli.log('Uploading artifacts...'); + const functionNames = this.serverless.service.getAllFunctions(); - return BbPromise.map(functionNames, (name) => { - const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(name); - const functionObject = this.serverless.service.getFunction(name); - functionObject.package = functionObject.package || {}; - let artifactFilePath = functionObject.package.artifact || - this.serverless.service.package.artifact; - if (!artifactFilePath || - (this.serverless.service.artifact && !functionObject.package.artifact)) { - if (this.serverless.service.package.individually || functionObject.package.individually) { - const artifactFileName = functionArtifactFileName; - artifactFilePath = path.join(this.packagePath, artifactFileName); - return this.uploadZipFile(artifactFilePath); + const artifactFilePaths = _.uniq( + _.map(functionNames, (name) => { + const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(name); + const functionObject = this.serverless.service.getFunction(name); + functionObject.package = functionObject.package || {}; + const artifactFilePath = functionObject.package.artifact || + this.serverless.service.package.artifact; + + if (!artifactFilePath || + (this.serverless.service.artifact && !functionObject.package.artifact)) { + if (this.serverless.service.package.individually || functionObject.package.individually) { + const artifactFileName = functionArtifactFileName; + return path.join(this.packagePath, artifactFileName); + } + return this.provider.naming.getServiceArtifactName(); } - shouldUploadService = true; - return BbPromise.resolve(); - } + + return artifactFilePath; + }) + ); + + return BbPromise.map(artifactFilePaths, (artifactFilePath) => { + const stats = fs.statSync(artifactFilePath); + this.serverless.cli.log(`Uploading service .zip file to S3 (${filesize(stats.size)})...`); return this.uploadZipFile(artifactFilePath); - }, { concurrency: 3 }).then(() => { - if (shouldUploadService) { - const artifactFileName = this.provider.naming.getServiceArtifactName(); - const artifactFilePath = path.join(this.packagePath, artifactFileName); - const stats = fs.statSync(artifactFilePath); - this.serverless.cli.log(`Uploading service .zip file to S3 (${filesize(stats.size)})...`); - return this.uploadZipFile(artifactFilePath); - } - return BbPromise.resolve(); - }); + }, { concurrency: NUM_CONCURRENT_UPLOADS } + ); }, }; diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index 7c907b345..3168988df 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -225,19 +225,45 @@ describe('uploadArtifacts', () => { }); describe('#uploadFunctions()', () => { + let uploadZipFileStub; + + beforeEach(() => { + sinon.stub(fs, 'statSync').returns({ size: 1024 }); + uploadZipFileStub = sinon.stub(awsDeploy, 'uploadZipFile').resolves(); + }); + + afterEach(() => { + fs.statSync.restore(); + uploadZipFileStub.restore(); + }); + it('should upload the service artifact file to the S3 bucket', () => { awsDeploy.serverless.config.servicePath = 'some/path'; awsDeploy.serverless.service.service = 'new-service'; - sinon.stub(fs, 'statSync').returns({ size: 0 }); + return awsDeploy.uploadFunctions().then(() => { + expect(uploadZipFileStub.calledOnce).to.be.equal(true); + expect(uploadZipFileStub.args[0][0]).to.be.equal('new-service.zip'); + }); + }); - const uploadZipFileStub = sinon - .stub(awsDeploy, 'uploadZipFile').resolves(); + it('should upload a single .zip file to the S3 bucket when not packaging individually', () => { + awsDeploy.serverless.service.functions = { + first: { + package: { + artifact: 'artifact.zip', + }, + }, + second: { + package: { + artifact: 'artifact.zip', + }, + }, + }; return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledOnce).to.be.equal(true); - fs.statSync.restore(); - awsDeploy.uploadZipFile.restore(); + expect(uploadZipFileStub.args[0][0]).to.be.equal('artifact.zip'); }); }); @@ -256,16 +282,12 @@ describe('uploadArtifacts', () => { }, }; - const uploadZipFileStub = sinon - .stub(awsDeploy, 'uploadZipFile').resolves(); - return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); expect(uploadZipFileStub.args[0][0]) .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.functions.second.package.artifact); - uploadZipFileStub.restore(); }); }); @@ -284,19 +306,12 @@ describe('uploadArtifacts', () => { }, }; - const uploadZipFileStub = sinon - .stub(awsDeploy, 'uploadZipFile').resolves(); - const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 }); - return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); expect(uploadZipFileStub.args[0][0]) .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.package.artifact); - }).finally(() => { - uploadZipFileStub.restore(); - statSyncStub.restore(); }); }); @@ -304,16 +319,11 @@ describe('uploadArtifacts', () => { awsDeploy.serverless.config.servicePath = 'some/path'; awsDeploy.serverless.service.service = 'new-service'; - const statSyncStub = sinon.stub(fs, 'statSync').returns({ size: 1024 }); - const uploadZipFileStub = sinon.stub(awsDeploy, 'uploadZipFile').resolves(); sinon.spy(awsDeploy.serverless.cli, 'log'); return awsDeploy.uploadFunctions().then(() => { const expected = 'Uploading service .zip file to S3 (1 KB)...'; expect(awsDeploy.serverless.cli.log.calledWithExactly(expected)).to.be.equal(true); - }).finally(() => { - statSyncStub.restore(); - uploadZipFileStub.restore(); }); }); }); diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index a17e2dffd..12508e3d1 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -7,6 +7,7 @@ const path = require('path'); module.exports = { mergeIamTemplates() { this.validateStatements(this.serverless.service.provider.iamRoleStatements); + this.validateManagedPolicies(this.serverless.service.provider.iamManagedPolicies); return this.merge(); }, @@ -120,6 +121,20 @@ module.exports = { .Statement.concat(this.serverless.service.provider.iamRoleStatements); } + if (this.serverless.service.provider.iamManagedPolicies) { + // add iam managed policies + const iamManagedPolicies = this.serverless.service.provider.iamManagedPolicies; + const resource = this.serverless.service.provider.compiledCloudFormationTemplate + .Resources[this.provider.naming.getRoleLogicalId()].Properties; + if (iamManagedPolicies.length > 0) { + if (!_.has(resource, 'ManagedPolicyArns') || _.isEmpty(resource.ManagedPolicyArns)) { + resource.ManagedPolicyArns = []; + } + resource.ManagedPolicyArns = resource.ManagedPolicyArns + .concat(iamManagedPolicies); + } + } + // check if one of the functions contains vpc configuration const vpcConfigProvided = []; this.serverless.service.getAllFunctions().forEach((functionName) => { @@ -173,4 +188,14 @@ module.exports = { throw new this.serverless.classes.Error(errorMessage); } }, + + validateManagedPolicies(iamManagedPolicies) { + // Verify that iamManagedPolicies (if present) is an array + if (!iamManagedPolicies) { + return; + } + if (!_.isArray(iamManagedPolicies)) { + throw new this.serverless.classes.Error('iamManagedPolicies should be an array of arns'); + } + }, }; diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js index 140798925..373f0aa32 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js @@ -155,6 +155,22 @@ describe('#mergeIamTemplates()', () => { }); }); + it('should add managed policy arns', () => { + awsPackage.serverless.service.provider.iamManagedPolicies = [ + 'some:aws:arn:xxx:*:*', + 'someOther:aws:arn:xxx:*:*', + { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] }, + ]; + return awsPackage.mergeIamTemplates() + .then(() => { + expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate + .Resources[awsPackage.provider.naming.getRoleLogicalId()] + .Properties + .ManagedPolicyArns + ).to.deep.equal(awsPackage.serverless.service.provider.iamManagedPolicies); + }); + }); + it('should throw error if custom IAM policy statements is not an array', () => { awsPackage.serverless.service.provider.iamRoleStatements = { policy: 'some_value', @@ -222,6 +238,12 @@ describe('#mergeIamTemplates()', () => { .to.throw(/statement 0 is missing.*Resource; statement 2 is missing.*Effect, Action/); }); + it('should throw error if managed policies is not an array', () => { + awsPackage.serverless.service.provider.iamManagedPolicies = 'a string'; + expect(() => awsPackage.mergeIamTemplates()) + .to.throw('iamManagedPolicies should be an array of arns'); + }); + it('should add a CloudWatch LogGroup resource', () => { const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName); return awsPackage.mergeIamTemplates().then(() => { diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index 84368002a..a5d60a8ba 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -34,6 +34,7 @@ const validTemplates = [ 'google-nodejs', 'kubeless-python', 'kubeless-nodejs', + 'openwhisk-java-maven', 'openwhisk-nodejs', 'openwhisk-php', 'openwhisk-python', diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index 4ea33d8b8..731b566ba 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -143,7 +143,6 @@ describe('Create', () => { expect(dirContent).to.include('aws-csharp.csproj'); expect(dirContent).to.include('build.cmd'); expect(dirContent).to.include('build.sh'); - expect(dirContent).to.include('global.json'); }); }); @@ -264,9 +263,9 @@ describe('Create', () => { expect(dirContent).to.include('gradlew.bat'); expect(dirContent).to.include('package.json'); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); + 'gradle-wrapper.jar')); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); + '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', @@ -291,17 +290,17 @@ describe('Create', () => { expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); + 'gradle-wrapper.jar')); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); + 'gradle-wrapper.properties')); expect(dirContent).to.include(path.join('src', 'main', 'resources', - 'log4j.properties')); + 'log4j.properties')); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Handler.java')); + 'com', 'serverless', 'Handler.java')); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'ApiGatewayResponse.java')); + 'com', 'serverless', 'ApiGatewayResponse.java')); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Response.java')); + 'com', 'serverless', 'Response.java')); expect(dirContent).to.include(path.join('.gitignore')); }); }); @@ -319,17 +318,17 @@ describe('Create', () => { expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); + 'gradle-wrapper.jar')); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); + 'gradle-wrapper.properties')); expect(dirContent).to.include(path.join('src', 'main', 'resources', - 'log4j.properties')); + 'log4j.properties')); expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'Handler.groovy')); + 'com', 'serverless', 'Handler.groovy')); expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'ApiGatewayResponse.groovy')); + 'com', 'serverless', 'ApiGatewayResponse.groovy')); expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'Response.groovy')); + 'com', 'serverless', 'Response.groovy')); expect(dirContent).to.include('.gitignore'); }); }); @@ -354,6 +353,23 @@ describe('Create', () => { }); }); + it('should generate scaffolding for "openwhisk-java-maven" template', () => { + process.chdir(tmpDir); + create.options.template = 'openwhisk-java-maven'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + expect(dirContent).to.include('pom.xml'); + expect(dirContent).to.include(path.join('src', 'main', 'java', + 'com', 'example', 'FunctionApp.java')); + expect(dirContent).to.include(path.join('src', 'test', 'java', + 'com', 'example', 'FunctionAppTest.java')); + expect(dirContent).to.include('.gitignore'); + expect(dirContent).to.include('serverless.yml'); + }); + }); + it('should generate scaffolding for "openwhisk-nodejs" template', () => { process.chdir(tmpDir); create.options.template = 'openwhisk-nodejs'; @@ -509,7 +525,7 @@ describe('Create', () => { expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('pom.xml'); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Handler.java')); + 'com', 'serverless', 'Handler.java')); expect(dirContent).to.include('.gitignore'); }); }); @@ -608,7 +624,7 @@ describe('Create', () => { }); it('should create a custom renamed service in the directory if using ' + - 'the "path" and "name" option', () => { + 'the "path" and "name" option', () => { process.chdir(tmpDir); create.options.path = 'my-new-service'; @@ -640,7 +656,7 @@ describe('Create', () => { create.options.template = 'aws-nodejs'; create.options.path = ''; create.serverless.utils.copyDirContentsSync(path.join(create.serverless.config.serverlessPath, - 'plugins', 'create', 'templates', create.options.template), tmpDir); + 'plugins', 'create', 'templates', create.options.template), tmpDir); const dirContent = fs.readdirSync(tmpDir); @@ -749,7 +765,7 @@ describe('Create', () => { return create.create().then(() => { const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include(path.join('hello', 'main.go')); diff --git a/lib/plugins/create/templates/aws-csharp/.vs/aws-csharp/v15/.suo b/lib/plugins/create/templates/aws-csharp/.vs/aws-csharp/v15/.suo deleted file mode 100644 index 9c228fb45..000000000 Binary files a/lib/plugins/create/templates/aws-csharp/.vs/aws-csharp/v15/.suo and /dev/null differ diff --git a/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj b/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj index efa84bbf5..9cedd48b6 100644 --- a/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj +++ b/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj @@ -1,7 +1,8 @@  - netcoreapp1.0 + netcoreapp2.0 + true CsharpHandlers aws-csharp @@ -12,7 +13,7 @@ - + diff --git a/lib/plugins/create/templates/aws-csharp/build.cmd b/lib/plugins/create/templates/aws-csharp/build.cmd index f33ae0254..9bd8efb21 100644 --- a/lib/plugins/create/templates/aws-csharp/build.cmd +++ b/lib/plugins/create/templates/aws-csharp/build.cmd @@ -1,2 +1,2 @@ dotnet restore -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip \ No newline at end of file +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-csharp/build.sh b/lib/plugins/create/templates/aws-csharp/build.sh index 892b0f289..f05233ca5 100755 --- a/lib/plugins/create/templates/aws-csharp/build.sh +++ b/lib/plugins/create/templates/aws-csharp/build.sh @@ -1,10 +1,11 @@ #!/bin/bash -#install zip -apt-get -qq update -apt-get -qq -y install zip +#install zip on debian OS, since microsoft/dotnet container doesn't have zip by default +if [ -f /etc/debian_version ] +then + apt -qq update + apt -qq -y install zip +fi dotnet restore - -#create deployment package -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-csharp/global.json b/lib/plugins/create/templates/aws-csharp/global.json deleted file mode 100644 index 8af244a46..000000000 --- a/lib/plugins/create/templates/aws-csharp/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "1.0.4" - } -} diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index 12e2196fe..8f2c5f370 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -19,7 +19,7 @@ service: aws-csharp # NOTE: update this with your service name provider: name: aws - runtime: dotnetcore1.0 + runtime: dotnetcore2.0 # you can overwrite defaults here # stage: dev @@ -47,7 +47,7 @@ provider: # you can add packaging information here package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip + artifact: bin/release/netcoreapp2.0/deploy-package.zip # exclude: # - exclude-me.js # - exclude-me-dir/** diff --git a/lib/plugins/create/templates/aws-fsharp/build.sh b/lib/plugins/create/templates/aws-fsharp/build.sh index 3f92f87ce..f05233ca5 100644 --- a/lib/plugins/create/templates/aws-fsharp/build.sh +++ b/lib/plugins/create/templates/aws-fsharp/build.sh @@ -1,15 +1,11 @@ #!/bin/bash -isMacOs=`uname -a | grep Darwin` - -#install zip -if [ -z "$isMacOs" ] +#install zip on debian OS, since microsoft/dotnet container doesn't have zip by default +if [ -f /etc/debian_version ] then - apt-get -qq update - apt-get -qq -y install zip + apt -qq update + apt -qq -y install zip fi dotnet restore - -#create deployment package dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/openwhisk-java-maven/.gitignore b/lib/plugins/create/templates/openwhisk-java-maven/.gitignore new file mode 100644 index 000000000..186e57ceb --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/.gitignore @@ -0,0 +1,8 @@ +target +.vscode +.sts4-cache +.project +.classpath +.settings +*.iml +.idea diff --git a/lib/plugins/create/templates/openwhisk-java-maven/pom.xml b/lib/plugins/create/templates/openwhisk-java-maven/pom.xml new file mode 100644 index 000000000..95c0ac0ff --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + com.example + demo-function + 1.0-SNAPSHOT + https://openwhisk.apache.org/ + + UTF-8 + 1.8 + 1.8 + 2.8.2 + + + + com.google.code.gson + gson + ${gson.version} + + + junit + junit + 4.12 + test + + + + demo-function + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + + + + + diff --git a/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml b/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml new file mode 100644 index 000000000..d3541cdf8 --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml @@ -0,0 +1,49 @@ +# 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: openwhisk-java-maven # NOTE: update this with your service name + +# Please ensure the serverless-openwhisk provider plugin is installed globally. +# $ npm install -g serverless-openwhisk +# ...before installing project dependencies to register this provider. +# $ npm install +provider: + name: openwhisk + runtime: java + +# you can add packaging information here +package: + artifact: target/demo-function.jar + +functions: + demo: + handler: com.example.FunctionApp + +# extend the framework using plugins listed here: +# https://github.com/serverless/plugins +plugins: + - "serverless-openwhisk" + +# you can define custom triggers and trigger feeds using the resources section. +# +#resources: +# triggers: +# my_trigger: +# parameters: +# hello: world +# alarm_trigger: +# parameters: +# hello: world +# feed: /whisk.system/alarms/alarm +# feed_parameters: +# cron: '*/8 * * * * *' diff --git a/lib/plugins/create/templates/openwhisk-java-maven/src/main/java/com/example/FunctionApp.java b/lib/plugins/create/templates/openwhisk-java-maven/src/main/java/com/example/FunctionApp.java new file mode 100644 index 000000000..f216f79c9 --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/src/main/java/com/example/FunctionApp.java @@ -0,0 +1,31 @@ +package com.example; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.gson.JsonObject; + +/** + * Hello FunctionApp + */ +public class FunctionApp { + public static JsonObject main(JsonObject args) { + JsonObject response = new JsonObject(); + response.addProperty("greetings", "Hello! Welcome to OpenWhisk"); + return response; + } +} diff --git a/lib/plugins/create/templates/openwhisk-java-maven/src/test/java/com/example/FunctionAppTest.java b/lib/plugins/create/templates/openwhisk-java-maven/src/test/java/com/example/FunctionAppTest.java new file mode 100644 index 000000000..20229769e --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/src/test/java/com/example/FunctionAppTest.java @@ -0,0 +1,39 @@ +package com.example; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static org.junit.Assert.*; + +import com.google.gson.JsonObject; + +import org.junit.Test; + +/** + * Unit test for simple function. + */ +public class FunctionAppTest { + @Test + public void testFunction() { + JsonObject args = new JsonObject(); + JsonObject response = FunctionApp.main(args); + assertNotNull(response); + String greetings = response.getAsJsonPrimitive("greetings").getAsString(); + assertNotNull(greetings); + assertEquals("Hello! Welcome to OpenWhisk", greetings); + } +} diff --git a/tests/templates/test_all_templates b/tests/templates/test_all_templates index 88980ab50..d207639bc 100755 --- a/tests/templates/test_all_templates +++ b/tests/templates/test_all_templates @@ -8,7 +8,7 @@ function integration-test { $DIR/integration-test-template $@ } -integration-test aws-csharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' +integration-test aws-csharp './build.sh' integration-test aws-fsharp './build.sh' integration-test aws-go 'cd /go/src/app && make build' integration-test aws-go-dep 'cd /go/src/app && make build'