Skip to content

Commit

Permalink
Merge pull request pulumi#732 from pulumi/zephyrz73/serverless-raw-co…
Browse files Browse the repository at this point in the history
…nversion

1. Change the netcore version to 3.1
2. Converted the project aws-ts-serverless-raw into aws-py-serverless-raw
  • Loading branch information
zephyrz73 committed Jun 30, 2020
2 parents 5c9cd25 + 07d80a0 commit f2c2f77
Show file tree
Hide file tree
Showing 12 changed files with 395 additions and 5 deletions.
2 changes: 2 additions & 0 deletions aws-py-serverless-raw/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pyc
venv/
9 changes: 9 additions & 0 deletions aws-py-serverless-raw/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: serverless-raw
runtime: python
description: Basic example of a serverless AWS application
template:
config:
aws:region:
description: The AWS region to deploy into
default: us-east-2

84 changes: 84 additions & 0 deletions aws-py-serverless-raw/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Serverless C# App

This example deploys a complete serverless C# application using raw `aws.apigateway.RestApi`, `aws.lambda_.Function` and
`aws.dynamodb.Table` resources from `pulumi_aws`. Although this doesn't feature any of the higher-level abstractions
from the `pulumi_cloud` package, it demonstrates that you can program the raw resources directly available in AWS
to accomplish all of the same things this higher-level package offers.

The deployed Lambda function is a simple C# application, highlighting the ability to manage existing application code
in a Pulumi application, even if your Pulumi code is written in a different language like JavaScript or Python.

The Lambda function is a C# application using .NET Core 3.1 (a similar approach works for any other language supported by
AWS Lambda).

## Deploying and running the Pulumi App
1. Install dependencies. In this example we will install them in a virtual environment named `venv`.
```bash
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
```

1. Create a new stack:

```bash
$ pulumi stack init dev
```

1. Build the C# application.

```bash
dotnet publish app
```

1. Set the AWS region:

```bash
$ pulumi config set aws:region us-east-2
```

1. Optionally, set AWS Lambda provisioned concurrency:

```bash
$ pulumi config set provisionedConcurrency 1
```

1. Run `pulumi up` to preview and deploy changes:

```
$ pulumi up
Previewing update (dev):
...
Updating (dev):
...
Resources:
+ 10 created
Duration: 1m 20s
```

1. Check the deployed GraphQL endpoint:

```
$ curl $(pulumi stack output endpoint)/hello
{"Path":"/hello","Count":0}
```

1. See the logs

```
$ pulumi logs -f
2018-03-21T18:24:52.670-07:00[ mylambda-d719650] START RequestId: d1e95652-2d6f-11e8-93f6-2921c8ae65e7 Version: $LATEST
2018-03-21T18:24:56.171-07:00[ mylambda-d719650] Getting count for '/hello'
2018-03-21T18:25:01.327-07:00[ mylambda-d719650] Got count 0 for '/hello'
2018-03-21T18:25:02.267-07:00[ mylambda-d719650] END RequestId: d1e95652-2d6f-11e8-93f6-2921c8ae65e7
2018-03-21T18:25:02.267-07:00[ mylambda-d719650] REPORT RequestId: d1e95652-2d6f-11e8-93f6-2921c8ae65e7 Duration: 9540.93 ms Billed Duration: 9600 ms Memory Size: 128 MB Max Memory Used: 37 MB
```

## Clean up

1. Run `pulumi destroy` to tear down all resources.

1. To delete the stack itself, run `pulumi stack rm`. Note that this command deletes all deployment history from the Pulumi Console.
160 changes: 160 additions & 0 deletions aws-py-serverless-raw/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
"""Copyright 2016-2019, Pulumi Corporation. All rights reserved."""
import pulumi
import pulumi_aws as aws
import pulumi_aws.config
from pulumi import Output
import json

# The location of the built dotnet3.1 application to deploy
dotnet_application_publish_folder = "./app/bin/Debug/netcoreapp3.1/publish"
dotnet_application_entry_point = "app::app.Functions::GetAsync"
# The stage name to use for the API Gateway URL
custom_stage_name = "api"

#################
## DynamoDB Table
#################

# A DynamoDB table with a single primary key
counter_table = aws.dynamodb.Table("counterTable",
attributes=[
{
"name": "Id",
"type": "S",
},
],
hash_key="Id",
read_capacity=1,
write_capacity=1,
)

##################
## Lambda Function
##################

# Give our Lambda access to the Dynamo DB table, CloudWatch Logs and Metrics
# Python package does not have assumeRolePolicyForPrinciple
instance_assume_role_policy = aws.iam.get_policy_document(statements=[{
"actions": ["sts:AssumeRole"],
"principals": [{
"identifiers": ["lambda.amazonaws.com"],
"type": "Service",
}],
}])

role = aws.iam.Role("mylambda-role",
assume_role_policy=instance_assume_role_policy.json,
)

policy = aws.iam.RolePolicy("mylambda-policy",
role=role,
policy=Output.from_input({
"Version": "2012-10-17",
"Statement": [{
"Action": ["dynamodb:UpdateItem", "dynamodb:PutItem", "dynamodb:GetItem",
"dynamodb:DescribeTable"],
"Resource": counter_table.arn,
"Effect": "Allow",
}, {
"Action": ["logs:*", "cloudwatch:*"],
"Resource": "*",
"Effect": "Allow",
}],
}),
)

# Read the config of whether to provision fixed concurrency for Lambda
config = pulumi.Config()
provisioned_concurrent_executions = config.get_float('provisionedConcurrency')

# Create a Lambda function, using code from the `./app` folder.

lambda_func = aws.lambda_.Function("mylambda",
opts=pulumi.ResourceOptions(depends_on=[policy]),
runtime="dotnetcore3.1",
code=pulumi.AssetArchive({
".": pulumi.FileArchive(dotnet_application_publish_folder),
}),
timeout=300,
handler=dotnet_application_entry_point,
role=role.arn,
publish=bool(provisioned_concurrent_executions),
# Versioning required for provisioned concurrency
environment={
"variables": {
"COUNTER_TABLE": counter_table.name,
},
},
)

if provisioned_concurrent_executions:
concurrency = aws.lambda_.ProvisionedConcurrencyConfig("concurrency",
function_name=lambda_func.name,
qualifier=lambda_func.version,
provisioned_concurrent_executions=provisioned_concurrent_executions,
)


#####################
## APIGateway RestAPI
######################

# Create the Swagger spec for a proxy which forwards all HTTP requests through to the Lambda function.
def swagger_spec(lambda_arn):
swagger_spec_returns = {
"swagger": "2.0",
"info": {"title": "api", "version": "1.0"},
"paths": {
"/{proxy+}": swagger_route_handler(lambda_arn),
},
}
return json.dumps(swagger_spec_returns)


# Create a single Swagger spec route handler for a Lambda function.
def swagger_route_handler(lambda_arn):
region = pulumi_aws.config.region
uri_string = 'arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{lambdaArn}/invocations'.format(
region=region, lambdaArn=lambda_arn)
return ({
"x-amazon-apigateway-any-method": {
"x-amazon-apigateway-integration": {
"uri": uri_string,
"passthroughBehavior": "when_no_match",
"httpMethod": "POST",
"type": "aws_proxy",
},
},
})


# Create the API Gateway Rest API, using a swagger spec.
rest_api = aws.apigateway.RestApi("api",
body=lambda_func.arn.apply(lambda lambda_arn: swagger_spec(lambda_arn)),
)

# Create a deployment of the Rest API.
deployment = aws.apigateway.Deployment("api-deployment",
rest_api=rest_api,
# Note: Set to empty to avoid creating an implicit stage, we'll create it
# explicitly below instead.
stage_name="")

# Create a stage, which is an addressable instance of the Rest API. Set it to point at the latest deployment.
stage = aws.apigateway.Stage("api-stage",
rest_api=rest_api,
deployment=deployment,
stage_name=custom_stage_name,
)

# Give permissions from API Gateway to invoke the Lambda
invoke_permission = aws.lambda_.Permission("api-lambda-permission",
action="lambda:invokeFunction",
function=lambda_func,
principal="apigateway.amazonaws.com",
source_arn=deployment.execution_arn.apply(
lambda execution_arn: execution_arn + "*/*"),
)

# Export the https endpoint of the running Rest API
pulumi.export("endpoint", deployment.invoke_url.apply(lambda url: url + custom_stage_name))
81 changes: 81 additions & 0 deletions aws-py-serverless-raw/app/Functions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;

using Amazon;
using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DataModel;

using Newtonsoft.Json;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace app
{
public class Counter
{
public string Id { get; set; }
public Int32 Count { get; set; }
}

public class Functions
{
// This const is the name of the environment variable that the serverless.template will use to set
// the name of the DynamoDB table used to store blog posts.
const string TABLENAME_ENVIRONMENT_VARIABLE_LOOKUP = "COUNTER_TABLE";

IDynamoDBContext DDBContext { get; set; }

/// <summary>
/// Default constructor that Lambda will invoke.
/// </summary>
public Functions()
{
// Check to see if a table name was passed in through environment variables and if so
// add the table mapping.
var tableName = System.Environment.GetEnvironmentVariable(TABLENAME_ENVIRONMENT_VARIABLE_LOOKUP);
if(!string.IsNullOrEmpty(tableName))
{
AWSConfigsDynamoDB.Context.TypeMappings[typeof(Counter)] = new Amazon.Util.TypeMapping(typeof(Counter), tableName);
}

var config = new DynamoDBContextConfig { Conversion = DynamoDBEntryConversion.V2 };
this.DDBContext = new DynamoDBContext(new AmazonDynamoDBClient(), config);
}

/// <summary>
/// A Lambda function that returns a simple JSON object.
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public async Task<APIGatewayProxyResponse> GetAsync(APIGatewayProxyRequest request, ILambdaContext context)
{
context.Logger.LogLine($"Getting count for '{request.Path}'");

var counter = await DDBContext.LoadAsync<Counter>(request.Path);
if (counter == null) {
counter = new Counter { Id = request.Path, Count = 1 };
}
var count = counter.Count++;
await DDBContext.SaveAsync<Counter>(counter);

context.Logger.LogLine($"Got count {count} for '{request.Path}'");

var response = new APIGatewayProxyResponse
{
StatusCode = (int)HttpStatusCode.OK,
Body = JsonConvert.SerializeObject(new { Path = request.Path, Count = count }),
Headers = new Dictionary<string, string> { { "Content-Type", "application/json" } }
};

return response;
}

}
}
22 changes: 22 additions & 0 deletions aws-py-serverless-raw/app/app.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.3.5" />

<PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
<PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
<PackageReference Include="Amazon.Lambda.APIGatewayEvents" Version="1.1.2" />

<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
</ItemGroup>

<ItemGroup>
<DotNetCliToolReference Include="Amazon.Lambda.Tools" Version="2.1.1" />
</ItemGroup>

</Project>
3 changes: 3 additions & 0 deletions aws-py-serverless-raw/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
grpcio>=1.9.1,!=1.30.0
pulumi>=2.0.0,<3.0.0
pulumi-aws>=2.0.0,<3.0.0
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/**/obj
/**/bin
/**/node_modules
2 changes: 1 addition & 1 deletion aws-ts-serverless-raw/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ to accomplish all of the same things this higher-level package offers.
The deployed Lambda function is a simple C# application, highlighting the ability to manage existing application code
in a Pulumi application, even if your Pulumi code is written in a different language like JavaScript or Python.

The Lambda function is a C# application using .NET Core 2.1 (a similar approach works for any other language supported by
The Lambda function is a C# application using .NET Core 3.1 (a similar approach works for any other language supported by
AWS Lambda).

## Deploying and running the Pulumi App
Expand Down
2 changes: 1 addition & 1 deletion aws-ts-serverless-raw/app/app.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup>

Expand Down
Loading

0 comments on commit f2c2f77

Please sign in to comment.