C# and AWS Lambdas, Part 3 – Pulumi IaC for Web API and an API Gateway

Full source code available here.

In this the third in a series of posts on using .NET in AWS lambdas I build on the previous where I connected a Http Gateway to a lambda running a Web API application. In that post I built the infrastructure by hand, i.e. via the GUI, pointing and clicking.

In this post 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 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 setup all the components listed above.

using Pulumi;
using Aws = Pulumi.Aws;

class MyStack : Stack
{
    public MyStack()
    {

        var lambdaRole = new Aws.Iam.Role("PulumiWebApiGateway_LambdaRole", new Aws.Iam.RoleArgs
        {
            AssumeRolePolicy = 
@"{
    ""Version"": ""2012-10-17"",
    ""Statement"": [
        {
        ""Action"": ""sts:AssumeRole"",
        ""Principal"": {
            ""Service"": ""lambda.amazonaws.com""
        },
        ""Effect"": ""Allow"",
        ""Sid"": """"
        }
    ]
}",
        });

        var lambdaPolicyAttachment = new Aws.Iam.PolicyAttachment("PulumiWebApiGateway_LambdaPolicyAttachment", new Aws.Iam.PolicyAttachmentArgs
        {
            Roles =
            {
                lambdaRole.Name
            },
            PolicyArn = Aws.Iam.ManagedPolicy.AWSLambdaBasicExecutionRole.ToString(), 
        });

        var lambdaFunction = new Aws.Lambda.Function("PulumiWebApiGateway_LambdaFunction", new Aws.Lambda.FunctionArgs
        {
            Handler = "WebAPILambda::WebAPILambda.LambdaEntryPoint::FunctionHandlerAsync",
            MemorySize = 128,
            Publish = false,
            ReservedConcurrentExecutions = -1,
            Role = lambdaRole.Arn,
            Runtime = Aws.Lambda.Runtime.DotnetCore3d1,
            Timeout = 4,
            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.
        });
        System.Console.WriteLine(Aws.Iam.ManagedPolicy.AWSLambdaBasicExecutionRole.ToString());

        var httpApiGateway = new Pulumi.Aws.ApiGatewayV2.Api("PulumiWebApiGateway_ApiGateway", new Pulumi.Aws.ApiGatewayV2.ApiArgs
        {
            ProtocolType = "HTTP",
            RouteSelectionExpression = "${request.method} ${request.path}",
        });

        var httpApiGateway_LambdaIntegration = new Pulumi.Aws.ApiGatewayV2.Integration("PulumiWebApiGateway_ApiGatewayIntegration", new Pulumi.Aws.ApiGatewayV2.IntegrationArgs
        {
            ApiId = httpApiGateway.Id,
            IntegrationType = "AWS_PROXY",
            IntegrationMethod = "POST",
            IntegrationUri = lambdaFunction.Arn,
            PayloadFormatVersion = "2.0",
            TimeoutMilliseconds = 30000,
        });

        var httpApiGatewayRoute = new Pulumi.Aws.ApiGatewayV2.Route("PulumiWebApiGateway_ApiGatewayRoute", new Pulumi.Aws.ApiGatewayV2.RouteArgs
        {
            ApiId = httpApiGateway.Id,
            RouteKey = "$default",
            Target = httpApiGateway_LambdaIntegration.Id.Apply(id => $"integrations/{id}"),
        });

        var httpApiGatewayStage = new Pulumi.Aws.ApiGatewayV2.Stage("PulumiWebApiGateway_ApiGatewayStage", new Pulumi.Aws.ApiGatewayV2.StageArgs
        {
            ApiId = httpApiGateway.Id,
            AutoDeploy = true,
            Name = "$default",
        });

        var lambdaPermissionsForApiGateway = new Aws.Lambda.Permission("PulumiWebApiGateway_LambdaPermission", new Aws.Lambda.PermissionArgs
        {
            Action = "lambda:InvokeFunction",
            Function = lambdaFunction.Name,
            Principal = "apigateway.amazonaws.com",
            SourceArn = Output.Format($"{httpApiGateway.ExecutionArn}/*") // note it's the ExecutionArn.
            // SourceArn = httpApiGateway.ExecutionArn.Apply(arn => $"{arn}/*") // this is another way of doing the same thing
        });

        this.ApiEndpoint = httpApiGateway.ApiEndpoint.Apply(endpoint =>  $"{endpoint}/api/values");
    }

    [Output]
    public Output<string> ApiEndpoint { get; set; }
}

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.

Leave a Reply

Your email address will not be published. Required fields are marked *