C# and AWS Lambdas, Part 3 – Pulumi IaC for Web API and an API Gateway
Want to learn more about AWS Lambda and .NET? Check out my A Cloud Guru course on ASP.NET Web API and Lambda.
Full source code available here.
- Part 1 - Hello World
- Part 2 - Web API and an API Gateway
- Part 3 - Pulumi IaC for Web API and an API Gateway
- Part 4 - Storing the Zip in S3, Setup with Pulumi IaC
- Part 5 - Updating the Zip in S3 and Updating the Running Lambda, with Pulumi IaC
- Part 6 - .NET 5 inside a Container inside a Lambda
- Part 7 - .NET 5 Web API inside a Container inside a Lambda, with API Gateway in front
- Part 8 - .NET 6, inside a Container, inside a Lambda
I will show how to build everything with Pulumi. I’ve written a few posts on Pulimi before, you can find them here.
As with all IaC, the hard part isn’t the IaC itself, it’s knowing what to do on the platform you are deploying to. In the previous post, I worked out all the required steps and components and got everything working. But the UI takes care of some things that you have to explicitly do in IaC and this is where the challenge is as you get familiar with the platform you are working on.
What’s needed
When you add up all the components needed, it feels like a lot, but eighty lines of C# code sets it all up.
- IAM role for the Lambda.
- Policy attachment for the above role letting it execute the Lambda.
- The Lambda function. This is where the Web API code runs.
- A Http Gateway.
- An integration between the Http Gateway and the Lambda.
- A route on the Http Gateway that forwards to the integration.
- A stage on the Http Gateway.
- Permissions for the Http Gateway to execute the Lambda.
That last one was not obvious, thank you to Piers Karsenbarg at Pulumi for his assistance.
The code
You need to have Pulumi installed, check their site for instructions.
Here is the stack to set up all the components listed above.
1using Pulumi;
2using Aws = Pulumi.Aws;
3
4class MyStack : Stack
5{
6 public MyStack()
7 {
8
9 var lambdaRole = new Aws.Iam.Role("PulumiWebApiGateway_LambdaRole", new Aws.Iam.RoleArgs
10 {
11 AssumeRolePolicy =
12@"{
13 ""Version"": ""2012-10-17"",
14 ""Statement"": [
15 {
16 ""Action"": ""sts:AssumeRole"",
17 ""Principal"": {
18 ""Service"": ""lambda.amazonaws.com""
19 },
20 ""Effect"": ""Allow"",
21 ""Sid"": """"
22 }
23 ]
24}",
25 });
26
27 var lambdaPolicyAttachment = new Aws.Iam.PolicyAttachment("PulumiWebApiGateway_LambdaPolicyAttachment", new Aws.Iam.PolicyAttachmentArgs
28 {
29 Roles =
30 {
31 lambdaRole.Name
32 },
33 PolicyArn = Aws.Iam.ManagedPolicy.AWSLambdaBasicExecutionRole.ToString(),
34 });
35
36 var lambdaFunction = new Aws.Lambda.Function("PulumiWebApiGateway_LambdaFunction", new Aws.Lambda.FunctionArgs
37 {
38 Handler = "WebAPILambda::WebAPILambda.LambdaEntryPoint::FunctionHandlerAsync",
39 MemorySize = 128,
40 Publish = false,
41 ReservedConcurrentExecutions = -1,
42 Role = lambdaRole.Arn,
43 Runtime = Aws.Lambda.Runtime.DotnetCore3d1,
44 Timeout = 4,
45 Code = new FileArchive("WebAPILambda.zip"), // I put the zip file in the same dir as this code for this demo, but you should not do this.
46 });
47 System.Console.WriteLine(Aws.Iam.ManagedPolicy.AWSLambdaBasicExecutionRole.ToString());
48
49 var httpApiGateway = new Pulumi.Aws.ApiGatewayV2.Api("PulumiWebApiGateway_ApiGateway", new Pulumi.Aws.ApiGatewayV2.ApiArgs
50 {
51 ProtocolType = "HTTP",
52 RouteSelectionExpression = "${request.method} ${request.path}",
53 });
54
55 var httpApiGateway_LambdaIntegration = new Pulumi.Aws.ApiGatewayV2.Integration("PulumiWebApiGateway_ApiGatewayIntegration", new Pulumi.Aws.ApiGatewayV2.IntegrationArgs
56 {
57 ApiId = httpApiGateway.Id,
58 IntegrationType = "AWS_PROXY",
59 IntegrationMethod = "POST",
60 IntegrationUri = lambdaFunction.Arn,
61 PayloadFormatVersion = "2.0",
62 TimeoutMilliseconds = 30000,
63 });
64
65 var httpApiGatewayRoute = new Pulumi.Aws.ApiGatewayV2.Route("PulumiWebApiGateway_ApiGatewayRoute", new Pulumi.Aws.ApiGatewayV2.RouteArgs
66 {
67 ApiId = httpApiGateway.Id,
68 RouteKey = "$default",
69 Target = httpApiGateway_LambdaIntegration.Id.Apply(id => $"integrations/{id}"),
70 });
71
72 var httpApiGatewayStage = new Pulumi.Aws.ApiGatewayV2.Stage("PulumiWebApiGateway_ApiGatewayStage", new Pulumi.Aws.ApiGatewayV2.StageArgs
73 {
74 ApiId = httpApiGateway.Id,
75 AutoDeploy = true,
76 Name = "$default",
77 });
78
79 var lambdaPermissionsForApiGateway = new Aws.Lambda.Permission("PulumiWebApiGateway_LambdaPermission", new Aws.Lambda.PermissionArgs
80 {
81 Action = "lambda:InvokeFunction",
82 Function = lambdaFunction.Name,
83 Principal = "apigateway.amazonaws.com",
84 SourceArn = Output.Format($"{httpApiGateway.ExecutionArn}/*") // note it's the ExecutionArn.
85 // SourceArn = httpApiGateway.ExecutionArn.Apply(arn => $"{arn}/*") // this is another way of doing the same thing
86 });
87
88 this.ApiEndpoint = httpApiGateway.ApiEndpoint.Apply(endpoint => $"{endpoint}/api/values");
89 }
90
91 [Output]
92 public Output<string> ApiEndpoint { get; set; }
93}
Run -
pulumi up
This is will show you what is going to be deployed, if it looks ok select yes.
After a short wait, you should see something like this the below image indicating that everything has been setup
Now you can click on the APIEndpoint URL and it should execute your Lambda and return the hello world message.
Full source code available here.