diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..ceb5e889b Binary files /dev/null and b/.DS_Store differ diff --git a/aws-py-serverless-raw/.DS_Store b/aws-py-serverless-raw/.DS_Store new file mode 100644 index 000000000..46bff2b4a Binary files /dev/null and b/aws-py-serverless-raw/.DS_Store differ diff --git a/aws-py-serverless-raw/.gitignore b/aws-py-serverless-raw/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/aws-py-serverless-raw/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/aws-py-serverless-raw/Pulumi.yaml b/aws-py-serverless-raw/Pulumi.yaml new file mode 100644 index 000000000..b831f16b7 --- /dev/null +++ b/aws-py-serverless-raw/Pulumi.yaml @@ -0,0 +1,6 @@ +name: aws-py-serverless-raw +runtime: + name: python + options: + virtualenv: venv +description: A minimal Python Pulumi program diff --git a/aws-py-serverless-raw/__main__.py b/aws-py-serverless-raw/__main__.py new file mode 100644 index 000000000..f48edc563 --- /dev/null +++ b/aws-py-serverless-raw/__main__.py @@ -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=1 + ) + + +##################### +## 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)) diff --git a/aws-py-serverless-raw/app/Functions.cs b/aws-py-serverless-raw/app/Functions.cs new file mode 100644 index 000000000..1e6717c1b --- /dev/null +++ b/aws-py-serverless-raw/app/Functions.cs @@ -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; } + + /// + /// Default constructor that Lambda will invoke. + /// + 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); + } + + /// + /// A Lambda function that returns a simple JSON object. + /// + /// + /// + public async Task GetAsync(APIGatewayProxyRequest request, ILambdaContext context) + { + context.Logger.LogLine($"Getting count for '{request.Path}'"); + + var counter = await DDBContext.LoadAsync(request.Path); + if (counter == null) { + counter = new Counter { Id = request.Path, Count = 1 }; + } + var count = counter.Count++; + await DDBContext.SaveAsync(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 { { "Content-Type", "application/json" } } + }; + + return response; + } + + } +} diff --git a/aws-py-serverless-raw/app/app.csproj b/aws-py-serverless-raw/app/app.csproj new file mode 100644 index 000000000..b18223b69 --- /dev/null +++ b/aws-py-serverless-raw/app/app.csproj @@ -0,0 +1,22 @@ + + + + netcoreapp3.1 + true + + + + + + + + + + + + + + + + + diff --git a/aws-py-serverless-raw/requirements.txt b/aws-py-serverless-raw/requirements.txt new file mode 100644 index 000000000..e1180f4ab --- /dev/null +++ b/aws-py-serverless-raw/requirements.txt @@ -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 diff --git a/aws-ts-serverless-raw/.DS_Store b/aws-ts-serverless-raw/.DS_Store new file mode 100644 index 000000000..b782cafbf Binary files /dev/null and b/aws-ts-serverless-raw/.DS_Store differ diff --git a/aws-ts-serverless-raw/app/.gitignore b/aws-ts-serverless-raw/app/.gitignore deleted file mode 100644 index 0181616c5..000000000 --- a/aws-ts-serverless-raw/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/**/obj -/**/bin diff --git a/aws-ts-serverless-raw/app/app.csproj b/aws-ts-serverless-raw/app/app.csproj index 97d54097e..b18223b69 100644 --- a/aws-ts-serverless-raw/app/app.csproj +++ b/aws-ts-serverless-raw/app/app.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + netcoreapp3.1 true diff --git a/aws-ts-serverless-raw/index.ts b/aws-ts-serverless-raw/index.ts index 75b3c8c1e..c9779107e 100644 --- a/aws-ts-serverless-raw/index.ts +++ b/aws-ts-serverless-raw/index.ts @@ -3,8 +3,8 @@ import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; -// The location of the built dotnet2.1 application to deploy -const dotNetApplicationPublishFolder = "./app/bin/Debug/netcoreapp2.1/publish"; +// The location of the built dotnet3.1 application to deploy +const dotNetApplicationPublishFolder = "./app/bin/Debug/netcoreapp3.1/publish"; const dotNetApplicationEntryPoint = "app::app.Functions::GetAsync"; // The stage name to use for the API Gateway URL const stageName = "api"; @@ -54,7 +54,7 @@ const provisionedConcurrentExecutions = config.getNumber("provisionedConcurrency // Create a Lambda function, using code from the `./app` folder. const lambda = new aws.lambda.Function("mylambda", { - runtime: aws.lambda.DotnetCore2d1Runtime, + runtime: aws.lambda.DotnetCore3d1Runtime, code: new pulumi.asset.AssetArchive({ ".": new pulumi.asset.FileArchive(dotNetApplicationPublishFolder), }),