<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>lambda on Barney Parker</title><link>https://barneyparker.com/tags/lambda/</link><description>Recent content in lambda on Barney Parker</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright><lastBuildDate>Tue, 23 Jun 2020 14:29:42 +0000</lastBuildDate><atom:link href="https://barneyparker.com/tags/lambda/index.xml" rel="self" type="application/rss+xml"/><item><title>Installing NPM Dependencies with Terraform</title><link>https://barneyparker.com/posts/installing-npm-dependencies-with-terraform/</link><pubDate>Tue, 23 Jun 2020 14:29:42 +0000</pubDate><guid>https://barneyparker.com/posts/installing-npm-dependencies-with-terraform/</guid><description>&lt;p>Terraform has the capability to create a zip file containing Function code, and for simpler functions that&amp;rsquo;s fine. For more complex functions it quickly becomes necessary to install dependencies to make avoid the need to write every line of code ourselves.&lt;/p>
&lt;p>The problem with Terraform is that it only wants to do things that have a Provider resource. &lt;code>npm install&lt;/code>ing dependencies needs to be done outside of this, and while we &lt;em>could&lt;/em> manually there is no way to guarantee we will remember to do it every time we apply.&lt;/p>
&lt;p>In order to keep this inside out Terraform workflow we need to abuse some resources a little.&lt;/p>
&lt;h2 id="triggering-dependency-installation">Triggering Dependency Installation&lt;/h2>
&lt;p>To convince Terraform to install our dependencies, we need to know&lt;/p>
&lt;ul>
&lt;li>Has &lt;code>package.json&lt;/code> changed?&lt;/li>
&lt;li>Has &lt;code>package-lock.json&lt;/code> changed?&lt;/li>
&lt;li>Do we have all the files we expected?&lt;/li>
&lt;/ul>
&lt;p>We also need to store something in the state file that we can compare to in the future.&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;null_resource&amp;#34; &amp;#34;lambda_dependencies&amp;#34;&lt;/span> {
&lt;span class="k">provisioner&lt;/span> &lt;span class="s2">&amp;#34;local-exec&amp;#34;&lt;/span> {
&lt;span class="n"> command&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;cd ${path.module}/src &amp;amp;&amp;amp; npm install&amp;#34;&lt;/span>
}
&lt;span class="n"> triggers&lt;/span> &lt;span class="o">=&lt;/span> {
&lt;span class="n"> index&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">sha256&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;${path.module}/src/index.js&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="n"> package&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">sha256&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;${path.module}/src/package.json&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="n"> lock&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">sha256&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">file&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;${path.module}/src/package-lock.json&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="n"> node&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">sha256&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">join&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;&amp;#34;,fileset(path.module, &amp;#34;src/**/*.js&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
}
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>a &lt;code>null_resource&lt;/code> doesn&amp;rsquo;t do anything in itself, but it acts as a container that we can use to create one resource based on a set of trigger values.&lt;/p>
&lt;p>&lt;code>local-exec&lt;/code> allows us to run a system command, which in our case is to move in to the Lambda Function source directory and execute &lt;code>npm install&lt;/code>.&lt;/p>
&lt;p>We can set &lt;code>triggers&lt;/code> based on our requirement, i.e. has &lt;code>index.js&lt;/code>, &lt;code>package.json&lt;/code> or &lt;code>package-lock.json&lt;/code> changed by checking their sha256 signatures. We can also get a list of every .js file in the source directory, join them in to a single string and then get the sha256 of that string.&lt;/p>
&lt;p>These trigger values will be stored in the state file, so even if we run this on another machine we&amp;rsquo;re going to be able to tell if anything doesn&amp;rsquo;t match between the previous deployment and this one. If something doesn&amp;rsquo;t match, then the local-exec command will be executed, and our dependencies installed!&lt;/p>
&lt;h2 id="building-the-bundle">Building the Bundle&lt;/h2>
&lt;p>So far so good, but we need to enforce Terraform ordering in the dependency graph. If we just add an &lt;code>archive_file&lt;/code> data resource there&amp;rsquo;s no guarantee it will wait for the dependencies to be installed before creating the bundle.&lt;/p>
&lt;p>To fix this we add an intermediate resource called a &lt;code>null_data_source&lt;/code>. These resources take a bunch of inputs which can be computed form the outputs of other resources or variables. Until all of the inputs are satisfied Terraform wont allow anything depending on the &lt;code>null_data_source&lt;/code> to be created or modified.&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">data&lt;/span> &lt;span class="s2">&amp;#34;null_data_source&amp;#34; &amp;#34;wait_for_lambda_exporter&amp;#34;&lt;/span> {
&lt;span class="n"> inputs&lt;/span> &lt;span class="o">=&lt;/span> {
&lt;span class="n"> lambda_dependency_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${null_resource.lambda_dependencies.id}&amp;#34;&lt;/span>
&lt;span class="n"> source_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${path.module}/src/&amp;#34;&lt;/span>
}
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>lambda_dependency_id&lt;/code> input wont be known to Terraform until the previous &lt;code>null_resource&lt;/code> has completed - which means our &lt;code>npm install&lt;/code> has either been completed, or nothing had changed and it wasn&amp;rsquo;t required.&lt;/p>
&lt;p>The &lt;code>source_dir&lt;/code> input can be pre-computed by Terraform, and we will use this value to tell the &lt;code>archive_file&lt;/code> what to add to the bundle.&lt;/p>
&lt;p>Finally we create our &lt;code>archive_file&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">data&lt;/span> &lt;span class="s2">&amp;#34;archive_file&amp;#34; &amp;#34;lambda&amp;#34;&lt;/span> {
&lt;span class="n"> output_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${path.module}/lambda-bundle.zip&amp;#34;&lt;/span>
&lt;span class="n"> source_dir&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${data.null_data_source.wait_for_lambda_exporter.outputs[&amp;#34;source_dir&amp;#34;]}&amp;#34;&lt;/span>
&lt;span class="n"> type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;zip&amp;#34;&lt;/span>
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>So now we&amp;rsquo;re creating our &lt;code>archive_file.lambda&lt;/code> from the path given to us by &lt;code>null_data_source.wait_for_lambda_exporter&lt;/code>, which wont give us an answer until &lt;code>null_resource.lambda_dependencies&lt;/code> is satisfied, which ensures the correct dependency ordering is maintained!&lt;/p>
&lt;h2 id="less-than-ideal">Less than ideal&lt;/h2>
&lt;p>There is one minor issue with this method. On the first run there is no node modules, so the null_resource.lambda_dependencies.trigger.node value gets an initial sha256 value, and that causes npm install to be executed. This will then change the sha256 that would be computed to the src directory, and a second run would also create a new bundle. subsequent runs wont do this, but in an ideal world a single run should be all that is needed to converge our required to actual states.&lt;/p>
&lt;p>Its certainly not perfect, but if you can tolerate this one caveat, its a relatively simple way to include node dependencies without having to do anything more elaborate than a normal deployment.&lt;/p></description></item><item><title>HTTP APIs with Simple Lambda Functions</title><link>https://barneyparker.com/posts/http-apis-with-simple-lambda-functions/</link><pubDate>Fri, 19 Jun 2020 07:56:34 +0000</pubDate><guid>https://barneyparker.com/posts/http-apis-with-simple-lambda-functions/</guid><description>&lt;p>API Gateway has always been somewhat of a beast to configure, and while its incredibly flexible its also far more hassle than most people want or need. Terraform was also completely the wrong tool for deploying these because of the sheer number of resources required and the need to deploy APIs to a stage just didn&amp;rsquo;t quite fit with the way Terraform works.&lt;/p>
&lt;p>The new HTTP APIs, whilst almost completely un-google-able, are an amazingly simple was to expose Lambda Functions to the outside world. They&amp;rsquo;re easy to deploy, simple to define, have great 404 handling, built in JWT authentication, and an easy CORS config. Apart from the (currently!) missing service integrations, there&amp;rsquo;s literally nothing to dislike about them!&lt;/p>
&lt;h2 id="about-the-default-route">About The $default Route&lt;/h2>
&lt;p>HTTP APIs allow you to add a special route called &lt;code>$default&lt;/code> which will essentially capture anything that doesn&amp;rsquo;t already have a route handler attached to it. It sounds like an ideal fallback route, but there is one caveat in that it will also capture CORS &lt;code>OPTIONS&lt;/code> requests in some situations, which is definitely not what what I was expecting to happen with it.&lt;/p>
&lt;p>The documentation doesn&amp;rsquo;t state this particularly clearly, but instead of using a $default route, instead you should use a &lt;code>/{proxy+}&lt;/code> route to capture anything without a handler, which still allows the CORS requests to hit the HTTP APIs built-in handler without you needing to write any code.&lt;/p>
&lt;h2 id="defining-the-api">Defining the API&lt;/h2>
&lt;p>Creating an API is pretty simple and allows you to create the CORS config at the same time:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_apigatewayv2_api&amp;#34; &amp;#34;api&amp;#34;&lt;/span> {
&lt;span class="n"> name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;v2-http-api&amp;#34;&lt;/span>
&lt;span class="n"> protocol_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;HTTP&amp;#34;&lt;/span>
&lt;span class="k">cors_configuration&lt;/span> {
&lt;span class="n"> allow_credentials&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">false&lt;/span>
&lt;span class="n"> allow_headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;*&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="n"> allow_methods&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;*&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="n"> allow_origins&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;*&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="n"> expose_headers&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="s2">&amp;#34;*&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>
&lt;span class="n"> max_age&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">3600&lt;/span>
}
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>This resource creates an HTTP type API in API Gateway, and adds an extremely open CORS policy. You would likely want to tie this down a little more, but this policy makes the API open everywhere which is easy for testing.&lt;/p>
&lt;h2 id="the-stage-with-logging">The Stage (With Logging)&lt;/h2>
&lt;p>Its good to be able to get request log files, which in AWS terms means a CloudWatch Log Group. In out case that needs to be attached to the API Stage. Incidentally HTTP APIs can be configured to &amp;ldquo;auto-deploy&amp;rdquo;, i.e. you don&amp;rsquo;t have to tell it to update the stage, just updating the API routes is enough.&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_cloudwatch_log_group&amp;#34; &amp;#34;api_logs&amp;#34;&lt;/span> {
&lt;span class="n"> name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/api/logs&amp;#34;&lt;/span>
&lt;span class="n"> retention_in_days&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">30&lt;/span>
}
&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_apigatewayv2_stage&amp;#34; &amp;#34;stage&amp;#34;&lt;/span> {
&lt;span class="n"> api_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_apigatewayv2_api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">id&lt;/span>
&lt;span class="n"> name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$default&amp;#34;&lt;/span>
&lt;span class="n"> auto_deploy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">true&lt;/span>
&lt;span class="k">access_log_settings&lt;/span> {
&lt;span class="n"> destination_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_cloudwatch_log_group&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api_logs&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">arn&lt;/span>
&lt;span class="n"> format&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">jsonencode&lt;/span>&lt;span class="p">(&lt;/span>
{
&lt;span class="n"> httpMethod&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.httpMethod&amp;#34;&lt;/span>
&lt;span class="n"> ip&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.identity.sourceIp&amp;#34;&lt;/span>
&lt;span class="n"> protocol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.protocol&amp;#34;&lt;/span>
&lt;span class="n"> requestId&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.requestId&amp;#34;&lt;/span>
&lt;span class="n"> requestTime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.requestTime&amp;#34;&lt;/span>
&lt;span class="n"> responseLength&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.responseLength&amp;#34;&lt;/span>
&lt;span class="n"> routeKey&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.routeKey&amp;#34;&lt;/span>
&lt;span class="n"> status&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;$context.status&amp;#34;&lt;/span>
}
&lt;span class="p">)&lt;/span>
}
&lt;span class="k">lifecycle&lt;/span> {
&lt;span class="n"> ignore_changes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="k">deployment_id&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="k">default_route_settings&lt;/span>
&lt;span class="p">]&lt;/span>
}
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>lifecycle&lt;/code> block at the bottom is necessary at the time of writing due to some slight oddities between the way HTTP APIs work and the way Terraform works. Basically it tells Terraform to ignore the &lt;code>deployment_id&lt;/code> and &lt;code>default_route_settings&lt;/code> fields if AWS says they don&amp;rsquo;t match whats in the state file.&lt;/p>
&lt;p>The &lt;code>access_log_settings.format&lt;/code> field specifies the log format. You can find more about that in the &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-logging.html">AWS Docs&lt;/a>&lt;/p>
&lt;p>There are some other resources available at this level to allow you to add a domain name and map this stage on to it, and to create a JWT Authorizer config, but for the sake of simplicity I will explore those in a separate article.&lt;/p>
&lt;h2 id="adding-a-route">Adding a Route&lt;/h2>
&lt;p>Adding Routes in V1 of API Gateway required a lot of work. Multiple path segments, a resource to handle the incoming request, one for the Integration, another for the integration response and multiple resources for potential return codes. There is a lot to love about all that including VTL templates doing a lot of work for you, and being able to avoid calling Lambda Functions altogether, but it really was an awful lot of resources, and Terraform is very verbose. There were even some timing issues with AWS eventual consistency that made deployment of infra code changes quite painful.&lt;/p>
&lt;p>HTTP APIs have done away with all of that in favour of a single resource for the route, and another one for the integration of the lambda function (with the option to add the JWT Authorizer)&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_apigatewayv2_route&amp;#34; &amp;#34;route&amp;#34;&lt;/span> {
&lt;span class="n"> api_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_apigatewayv2_api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">id&lt;/span>
&lt;span class="n"> route_key&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;GET /{proxy+}&amp;#34;&lt;/span>
&lt;span class="n"> target&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;integrations/${aws_apigatewayv2_integration.integration.id}&amp;#34;&lt;/span>
}
&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_apigatewayv2_integration&amp;#34; &amp;#34;integration&amp;#34;&lt;/span> {
&lt;span class="n"> api_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_apigatewayv2_api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">api&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">id&lt;/span>
&lt;span class="n"> integration_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;AWS_PROXY&amp;#34;&lt;/span>
&lt;span class="n"> connection_type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;INTERNET&amp;#34;&lt;/span>
&lt;span class="n"> description&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;This is our {proxy+} integration&amp;#34;&lt;/span>
&lt;span class="n"> integration_method&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;POST&amp;#34;&lt;/span>
&lt;span class="n"> integration_uri&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_lambda_function&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">lambda&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">invoke_arn&lt;/span>
&lt;span class="n"> passthrough_behavior&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;WHEN_NO_MATCH&amp;#34;&lt;/span>
&lt;span class="k">lifecycle&lt;/span> {
&lt;span class="n"> ignore_changes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="k">passthrough_behavior&lt;/span>
&lt;span class="p">]&lt;/span>
}
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The fields are fairly self-explanatory, the &lt;code>aws_apigatewayv2_route&lt;/code> attached a specific route to an API, and directs it to an integration.&lt;/p>
&lt;p>The &lt;code>aws_apigatewayv2_integration&lt;/code> tells API Gateway to forward the request to a Lambda function as an &lt;code>AWS_PROXY&lt;/code> integration type, using a &lt;code>POST&lt;/code> request to the Lambda service.&lt;/p>
&lt;p>At some point there will be more integrations (you can already pass through to another API via a VPC link for example) however Lambda is the only service currently integrated with HTTP APIs.&lt;/p>
&lt;p>Again, due to time of writing oddities in Terraform we need to add a &lt;code>lifecycle&lt;/code> block to ignore the &lt;code>passthrough_behaviour&lt;/code> field if it changes in AWS.&lt;/p>
&lt;h2 id="the-lambda-function-handler">The Lambda Function Handler&lt;/h2>
&lt;p>The Lambda Function is as simple as providing the ARN (shown above). Given this is a {proxy+} route it could capture multiple route requests, and in our case we will handle just the &lt;code>/&lt;/code> route and 404&amp;rsquo;s for anything not defined:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-javascript" data-lang="javascript">&lt;span class="nx">module&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">exports&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">handler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nx">async&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">=&amp;gt;&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s1">&amp;#39;Event: &amp;#39;&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="nx">event&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nx">event&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">path&lt;/span> &lt;span class="o">===&lt;/span> &lt;span class="s1">&amp;#39;/&amp;#39;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">statusCode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">200&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">({&lt;/span>
&lt;span class="nx">message&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;Welcome&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">}),&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="nx">statusCode&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="mi">404&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nx">headers&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="p">{&lt;/span>
&lt;span class="s1">&amp;#39;Content-Type&amp;#39;&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;application/json&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">},&lt;/span>
&lt;span class="nx">body&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="nx">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="nx">stringify&lt;/span>&lt;span class="p">({&lt;/span>
&lt;span class="nx">message&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="s1">&amp;#39;Not Found&amp;#39;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">}),&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>You can drop this code in to the resources created in the article &lt;a href="https://barneyparker.com/posts/deploying-lambda-functions-with-terraform/">Deploying Lambda Functions with Terraform&lt;/a> and drop it straight in here.&lt;/p>
&lt;h2 id="accessing-the-api">Accessing the API&lt;/h2>
&lt;p>We&amp;rsquo;ve not added a domain name to map the API to, but we can still use the default url provided by API Gateway. The easiest way to get this is by adding an output to the code to tell Terraform to tell us what URL has been assigned to it:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">output&lt;/span> &lt;span class="s2">&amp;#34;api_url&amp;#34;&lt;/span> {
&lt;span class="n"> value&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_apigatewayv2_stage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">stage&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">invoke_url&lt;/span>
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>You should now be able to &lt;code>curl&lt;/code> the &lt;code>api_url&lt;/code> that Terraform gives you and see that for a &lt;code>GET&lt;/code> request on the &lt;code>/&lt;/code> route you see a welcome response, a &lt;code>404&lt;/code> response on all other requests, and an &lt;code>OPTIONS&lt;/code> request should return the CORS response to allow browsers to access the API!&lt;/p></description></item><item><title>Deploying Lambda Functions with Terraform</title><link>https://barneyparker.com/posts/deploying-lambda-functions-with-terraform/</link><pubDate>Tue, 16 Jun 2020 20:05:28 +0000</pubDate><guid>https://barneyparker.com/posts/deploying-lambda-functions-with-terraform/</guid><description>&lt;p>Serverless architectures are built from a collection of Managed service wired together to create a specific service. This could be anything from a shopping cart to a full ETL solution. The advantage of building applications this way is that each of the component parts are fully managed by AWS. That means you (either the developer or company) don&amp;rsquo;t have to worry about them functioning, you just need to make sure they&amp;rsquo;re wired together correctly.&lt;/p>
&lt;p>Managed services are great, but these service only offer basic functionality such as queues or databases. What they can&amp;rsquo;t do is know how your business actually adds value to the world.&lt;/p>
&lt;p>Lambda functions are the Serverless super-hero because they let you run your own code without ever having to worry about how the server operates (yes, there are servers!) or even how how to install the software that runs your code.&lt;/p>
&lt;p>There are a lot of ways to deploy AWS infrastructure, and even more for Serverless infrastructure, but my personal favourite is Terraform. Its designed to allow you to write your infrastructure as code (IaC) and is surprisingly flexible. It also allows you to interact with non-AWS services which can really help tie everything together.&lt;/p>
&lt;p>Terraform can be a little verbose, but its very easy to understand, and very simple to work with. On the face of it Terraform doesn&amp;rsquo;t obviously lend itself to Serverless, but that&amp;rsquo;s because it designed to give you extremely low-level access to APIs in the form of Resources.&lt;/p>
&lt;p>In this article I will show you the minimal set of code required to deploy a lambda function. Its not going to be production-ready, or even do anything very impressive, but its going to introduce at an entry level the minimal components required.&lt;/p>
&lt;h2 id="about-terraform">About Terraform&lt;/h2>
&lt;p>Terraform works by creating a direct-graph of resources. The resource dependency order can be determined by using outputs from one resource as the inputs to another. Terraform can then deploy resources which have no outstanding dependencies recursively until all resources have been deployed with their dependencies satisfied.&lt;/p>
&lt;p>Terraform then creates a state file which it uses on further deployments to identify which specific resources in AWS it should be adding, deleting or modifying. State files can be stored in local files or remotely, but for now we will just keep them locally.&lt;/p>
&lt;h2 id="terraform-meta-resources">Terraform Meta-Resources&lt;/h2>
&lt;p>Terraform is incredibly configurable, and to do that it happily eats it own dog-food by configuring with Terraform resources! This is the content of a file I like to call &lt;code>meta.tf&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">terraform&lt;/span> {
&lt;span class="n"> required_version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;gt; 0.12&amp;#34;&lt;/span>
}
&lt;span class="k">provider&lt;/span> &lt;span class="s2">&amp;#34;aws&amp;#34;&lt;/span> {
&lt;span class="n"> region&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;eu-west-1&amp;#34;&lt;/span>
&lt;span class="n"> version&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="n"> &amp;#34;&amp;gt;&lt;/span>&lt;span class="o">=&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">61&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="m">0&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;code>terraform&lt;/code> block tells Terraform to use a version no less that 0.12.0. While older versions are available, this version added some functionality that makes it a little easier to code and so is a good base.&lt;/p>
&lt;p>The &lt;code>provider&lt;/code> block tells Terraform that we want to use AWS resources, we want to place them in the &lt;code>eu-west-1&lt;/code> region and we want to use a provider version no less than 2.61.0&lt;/p>
&lt;h2 id="creating-the-lambda-function-bundle">Creating the Lambda Function Bundle&lt;/h2>
&lt;p>A Lambda Function needs some code to run. The code can be in a &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html">variety of languages&lt;/a> but in this case we&amp;rsquo;re going to use Javascript. Here is some simple code that we&amp;rsquo;re going to deploy:&lt;/p>
&lt;p>To supply the code to the Lambda Function it needs to be in a Zip file. Terraform can create these zip files for us, and for simplicity we&amp;rsquo;re going to define the Function code inline:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">data&lt;/span> &lt;span class="s2">&amp;#34;archive_file&amp;#34; &amp;#34;lambda&amp;#34;&lt;/span> {
&lt;span class="n"> type&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;zip&amp;#34;&lt;/span>
&lt;span class="n"> output_path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;${path.module}/.terraform/lambda.zip&amp;#34;&lt;/span>
&lt;span class="k">source&lt;/span> {
&lt;span class="n"> filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;index.js&amp;#34;&lt;/span>
&lt;span class="n"> content&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">&amp;lt;&amp;lt;-&lt;/span>&lt;span class="k">EOF&lt;/span>
&lt;span class="n"> module.exports.handler&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="n"> async (event, context)&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="err">&amp;gt;&lt;/span> {
&lt;span class="k">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;EVENT: \n&amp;#34;&lt;/span> &lt;span class="err">+&lt;/span> &lt;span class="k">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">event&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="k">console&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">log&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s2">&amp;#34;CONTEXT: \n&amp;#34;&lt;/span> &lt;span class="err">+&lt;/span> &lt;span class="k">JSON&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">stringify&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="k">context&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">null&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">2&lt;/span>&lt;span class="p">))&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="k">Promise&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">resolve&lt;/span>&lt;span class="p">()&lt;/span>
}
&lt;span class="k">EOF&lt;/span>
}
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lines 7-10 will be written to a file called &lt;code>index.js&lt;/code> which will be added to a zip file called lambda.zip. ${path.module} will be interpolated as the local path during deployment. The code exports a function called &lt;code>handler&lt;/code>. Keep these in mind as they will be useful when we actually deploy our function.&lt;/p>
&lt;p>The code is extremely simple: it&amp;rsquo;s going to print out the Event and Context objects which will be passed to it upon execution. This is how the function will get information related to its execution.&lt;/p>
&lt;h2 id="the-log-file">The Log File&lt;/h2>
&lt;p>Since the Lambda Function is running inside of AWS, we need some way to capture its output. The Lambda service automatically captures anything which would normally be sent to stdin and stderr and store that in a CloudWatch Log Stream. Although the Lambda service will create the Log Group, its best practice to create this yourself and set an expiry period so that the logs are automatically cleared out after a period of time:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_cloudwatch_log_group&amp;#34; &amp;#34;lambda_log_group&amp;#34;&lt;/span> {
&lt;span class="n"> name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/aws/lambda/${aws_lambda_function.lambda.function_name}&amp;#34;&lt;/span>
&lt;span class="n"> retention_in_days&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">30&lt;/span>
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>All Lambda functions log to a log group called &lt;code>/aws/lambda/&amp;lt;lambda name&amp;gt;&lt;/code> and so we&amp;rsquo;re creating that and capturing the name from the Lambda Function resource (created very soon!)&lt;/p>
&lt;p>We also set the logs to expire after 30 days. Logs are pretty cheap, but if the function is called a lot or it generates a lot of log output the cost can quickly mount up so after a short period we want AWS to delete them on our behalf.&lt;/p>
&lt;h2 id="permissions">Permissions&lt;/h2>
&lt;p>In order to be able to interact with any other service, AWS resources need an IAM Role which can be &amp;ldquo;assumed&amp;rdquo; by the calling service. In our case that&amp;rsquo;s the Lambda service:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_iam_role&amp;#34; &amp;#34;lambda&amp;#34;&lt;/span> {
&lt;span class="n"> name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;simplest_lambda_role&amp;#34;&lt;/span>
&lt;span class="n"> assume_role_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">&amp;lt;&amp;lt;-&lt;/span>&lt;span class="k">EOF&lt;/span>
{
&lt;span class="s2">&amp;#34;Version&amp;#34;: &amp;#34;2012-10-17&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;Statement&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
{
&lt;span class="s2">&amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;Action&amp;#34;: &amp;#34;sts:AssumeRole&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;Principal&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> {
&lt;span class="s2">&amp;#34;Service&amp;#34;: &amp;#34;lambda.amazonaws.com&amp;#34;&lt;/span>
}
}
&lt;span class="p">]&lt;/span>
}
&lt;span class="k">EOF&lt;/span>
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>The role needs to have policies associated with it in order to define what its allowed to do. In our case the only thing we need permission to do is to create Log Streams within the Log Group, and write to them. Policies can be stand-alone, re-usable policies, or in-line policies specific to this function. For simplicity in this case we&amp;rsquo;re going to create an in-line policy:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_iam_role_policy&amp;#34; &amp;#34;logging&amp;#34;&lt;/span> {
&lt;span class="n"> name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Cloudwatch-Logs&amp;#34;&lt;/span>
&lt;span class="n"> role&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_iam_role&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">lambda&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">name&lt;/span>
&lt;span class="n"> policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">&amp;lt;&amp;lt;-&lt;/span>&lt;span class="k">EOF&lt;/span>
{
&lt;span class="s2">&amp;#34;Statement&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
{
&lt;span class="s2">&amp;#34;Action&amp;#34;&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="s2">&amp;#34;logs:CreateLogGroup&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;logs:CreateLogStream&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;logs:PutLogEvents&amp;#34;&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="s2">&amp;#34;Effect&amp;#34;: &amp;#34;Allow&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="s2">&amp;#34;Resource&amp;#34;: &amp;#34;arn:aws:logs:*:*:*&amp;#34;&lt;/span>
}
&lt;span class="p">]&lt;/span>
}
&lt;span class="k">EOF&lt;/span>
}
&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="the-lambda-function">The Lambda Function&lt;/h2>
&lt;p>At last we have all the pieces in place to be able to create our Lambda Function resource. Here we will give it a name, point it to our code bundle, tell it what runtime to use and what exported function to execute and what Role it should be assuming to give itself the correct permissions:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_lambda_function&amp;#34; &amp;#34;lambda&amp;#34;&lt;/span> {
&lt;span class="n"> function_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;simplest_lambda_function&amp;#34;&lt;/span>
&lt;span class="n"> filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">archive_file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">lambda&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">output_path&lt;/span>
&lt;span class="n"> source_code_hash&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">archive_file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">lambda&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">output_base64sha256&lt;/span>
&lt;span class="n"> runtime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;nodejs12.x&amp;#34;&lt;/span>
&lt;span class="n"> handler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;index.handler&amp;#34;&lt;/span>
&lt;span class="n"> role&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">aws_iam_role&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">lambda&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">arn&lt;/span>
}
&lt;/code>&lt;/pre>&lt;/div>&lt;p>Terraform hides some nasty internal values such as ARNs and file hashes by taking the outputs from some of the resources we created earlier as inputs to this resource. In doing so we have also implied the dependencies of resources and helped Terraform work out what order things should be deployed.&lt;/p>
&lt;h2 id="deploying-the-function">Deploying the Function&lt;/h2>
&lt;p>To start the deployment process, Terraform needs to be initialised with the command:&lt;/p>
&lt;p>&lt;code>terraform init&lt;/code>&lt;/p>
&lt;p>This will ensure we&amp;rsquo;re using a valid version of Terraform and download the providers specified in &lt;code>meta.tf&lt;/code> (or a default if we didn&amp;rsquo;t specify a version). All of its internal &amp;ldquo;stuff&amp;rdquo; will be stored in a directory called &lt;code>.terraform&lt;/code>&lt;/p>
&lt;p>To see what Terraform is going to do execute the following command:&lt;/p>
&lt;p>&lt;code>terraform plan&lt;/code>&lt;/p>
&lt;p>You should get an output something like:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-hcl" data-lang="hcl">&lt;span class="k">Refreshing&lt;/span> &lt;span class="k">Terraform&lt;/span> &lt;span class="k">state&lt;/span> &lt;span class="k">in&lt;/span>&lt;span class="err">-&lt;/span>&lt;span class="k">memory&lt;/span> &lt;span class="k">prior&lt;/span> &lt;span class="k">to&lt;/span> &lt;span class="k">plan&lt;/span>&lt;span class="p">...&lt;/span>
&lt;span class="k">The&lt;/span> &lt;span class="k">refreshed&lt;/span> &lt;span class="k">state&lt;/span> &lt;span class="k">will&lt;/span> &lt;span class="k">be&lt;/span> &lt;span class="k">used&lt;/span> &lt;span class="k">to&lt;/span> &lt;span class="k">calculate&lt;/span> &lt;span class="k">this&lt;/span> &lt;span class="k">plan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">but&lt;/span> &lt;span class="k">will&lt;/span> &lt;span class="k">not&lt;/span> &lt;span class="k">be&lt;/span>
&lt;span class="k">persisted&lt;/span> &lt;span class="k">to&lt;/span> &lt;span class="k">local&lt;/span> &lt;span class="k">or&lt;/span> &lt;span class="k">remote&lt;/span> &lt;span class="k">state&lt;/span> &lt;span class="k">storage&lt;/span>&lt;span class="p">.&lt;/span>
&lt;span class="k">data&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">archive_file&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="k">lambda&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="k">Refreshing&lt;/span> &lt;span class="k">state&lt;/span>&lt;span class="p">...&lt;/span>
&lt;span class="err">------------------------------------------------------------------------&lt;/span>
&lt;span class="k">An&lt;/span> &lt;span class="k">execution&lt;/span> &lt;span class="k">plan&lt;/span> &lt;span class="k">has&lt;/span> &lt;span class="k">been&lt;/span> &lt;span class="k">generated&lt;/span> &lt;span class="k">and&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="k">shown&lt;/span> &lt;span class="k">below&lt;/span>&lt;span class="p">.&lt;/span>
&lt;span class="k">Resource&lt;/span> &lt;span class="k">actions&lt;/span> &lt;span class="k">are&lt;/span> &lt;span class="k">indicated&lt;/span> &lt;span class="k">with&lt;/span> &lt;span class="k">the&lt;/span> &lt;span class="k">following&lt;/span> &lt;span class="k">symbols&lt;/span>&lt;span class="err">:&lt;/span>
&lt;span class="err">+&lt;/span> &lt;span class="k">create&lt;/span>
&lt;span class="k">Terraform&lt;/span> &lt;span class="k">will&lt;/span> &lt;span class="k">perform&lt;/span> &lt;span class="k">the&lt;/span> &lt;span class="k">following&lt;/span> &lt;span class="k">actions&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="c1">
&lt;/span>&lt;span class="c1">
&lt;/span>&lt;span class="c1"> # aws_cloudwatch_log_group.lambda_log_group will be created
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="err">+&lt;/span> &lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_cloudwatch_log_group&amp;#34; &amp;#34;lambda_log_group&amp;#34;&lt;/span> {
&lt;span class="n"> + arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/aws/lambda/simplest_lambda_function&amp;#34;&lt;/span>
&lt;span class="n"> + retention_in_days&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">30&lt;/span>
}&lt;span class="c1">
&lt;/span>&lt;span class="c1">
&lt;/span>&lt;span class="c1"> # aws_iam_role.lambda will be created
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="err">+&lt;/span> &lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_iam_role&amp;#34; &amp;#34;lambda&amp;#34;&lt;/span> {
&lt;span class="n"> + arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + assume_role_policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">jsonencode&lt;/span>&lt;span class="p">(&lt;/span>
{
&lt;span class="n"> + Statement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="err">+&lt;/span> {
&lt;span class="n"> + Action&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;sts:AssumeRole&amp;#34;&lt;/span>
&lt;span class="n"> + Effect&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Allow&amp;#34;&lt;/span>
&lt;span class="n"> + Principal&lt;/span> &lt;span class="o">=&lt;/span> {
&lt;span class="n"> + Service&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;lambda.amazonaws.com&amp;#34;&lt;/span>
}
}&lt;span class="p">,&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="n"> + Version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;2012-10-17&amp;#34;&lt;/span>
}
&lt;span class="p">)&lt;/span>
&lt;span class="n"> + create_date&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + force_detach_policies&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">false&lt;/span>
&lt;span class="n"> + id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + max_session_duration&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">3600&lt;/span>
&lt;span class="n"> + name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;simplest_lambda_role&amp;#34;&lt;/span>
&lt;span class="n"> + path&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;/&amp;#34;&lt;/span>
&lt;span class="n"> + unique_id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
}&lt;span class="c1">
&lt;/span>&lt;span class="c1">
&lt;/span>&lt;span class="c1"> # aws_iam_role_policy.logging will be created
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="err">+&lt;/span> &lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_iam_role_policy&amp;#34; &amp;#34;logging&amp;#34;&lt;/span> {
&lt;span class="n"> + id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Cloudwatch-Logs&amp;#34;&lt;/span>
&lt;span class="n"> + policy&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">jsonencode&lt;/span>&lt;span class="p">(&lt;/span>
{
&lt;span class="n"> + Statement&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="err">+&lt;/span> {
&lt;span class="n"> + Action&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="err">+&lt;/span> &lt;span class="s2">&amp;#34;logs:CreateLogGroup&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="err">+&lt;/span> &lt;span class="s2">&amp;#34;logs:CreateLogStream&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="err">+&lt;/span> &lt;span class="s2">&amp;#34;logs:PutLogEvents&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="n"> + Effect&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;Allow&amp;#34;&lt;/span>
&lt;span class="n"> + Resource&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;arn:aws:logs:*:*:*&amp;#34;&lt;/span>
}&lt;span class="p">,&lt;/span>
&lt;span class="p">]&lt;/span>
}
&lt;span class="p">)&lt;/span>
&lt;span class="n"> + role&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;simplest_lambda_role&amp;#34;&lt;/span>
}&lt;span class="c1">
&lt;/span>&lt;span class="c1">
&lt;/span>&lt;span class="c1"> # aws_lambda_function.lambda will be created
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="err">+&lt;/span> &lt;span class="k">resource&lt;/span> &lt;span class="s2">&amp;#34;aws_lambda_function&amp;#34; &amp;#34;lambda&amp;#34;&lt;/span> {
&lt;span class="n"> + arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + filename&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;./.terraform/lambda.zip&amp;#34;&lt;/span>
&lt;span class="n"> + function_name&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;simplest_lambda_function&amp;#34;&lt;/span>
&lt;span class="n"> + handler&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;index.handler&amp;#34;&lt;/span>
&lt;span class="n"> + id&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + invoke_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + last_modified&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + memory_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">128&lt;/span>
&lt;span class="n"> + publish&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="kt">false&lt;/span>
&lt;span class="n"> + qualified_arn&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + reserved_concurrent_executions&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="err">-&lt;/span>&lt;span class="m">1&lt;/span>
&lt;span class="n"> + role&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + runtime&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s2">&amp;#34;nodejs12.x&amp;#34;&lt;/span>
&lt;span class="n"> + source_code_hash&lt;/span> &lt;span class="o">=&lt;/span>&lt;span class="n"> &amp;#34;TFoUgCw/pW7Sy3/gY/TB3AtGzXoqRjN1L6YT066iPGg&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="err">&amp;#34;&lt;/span>
&lt;span class="n"> + source_code_size&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="n"> + timeout&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="m">3&lt;/span>
&lt;span class="n"> + version&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
&lt;span class="err">+&lt;/span> &lt;span class="k">tracing_config&lt;/span> {
&lt;span class="n"> + mode&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="k">known&lt;/span> &lt;span class="k">after&lt;/span> &lt;span class="k">apply&lt;/span>&lt;span class="p">)&lt;/span>
}
}
&lt;span class="k">Plan&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="m">4&lt;/span> &lt;span class="k">to&lt;/span> &lt;span class="k">add&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="k">to&lt;/span> &lt;span class="k">change&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="m">0&lt;/span> &lt;span class="k">to&lt;/span> &lt;span class="k">destroy&lt;/span>&lt;span class="p">.&lt;/span>
&lt;span class="err">------------------------------------------------------------------------&lt;/span>
&lt;span class="k">Note&lt;/span>&lt;span class="err">:&lt;/span> &lt;span class="k">You&lt;/span> &lt;span class="k">didn&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="k">t&lt;/span> &lt;span class="k">specify&lt;/span> &lt;span class="k">an&lt;/span> &lt;span class="s2">&amp;#34;-out&amp;#34;&lt;/span> &lt;span class="k">parameter&lt;/span> &lt;span class="k">to&lt;/span> &lt;span class="k">save&lt;/span> &lt;span class="k">this&lt;/span> &lt;span class="k">plan&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="k">so&lt;/span> &lt;span class="k">Terraform&lt;/span>
&lt;span class="k">can&lt;/span>&lt;span class="err">&amp;#39;&lt;/span>&lt;span class="k">t&lt;/span> &lt;span class="k">guarantee&lt;/span> &lt;span class="k">that&lt;/span> &lt;span class="k">exactly&lt;/span> &lt;span class="k">these&lt;/span> &lt;span class="k">actions&lt;/span> &lt;span class="k">will&lt;/span> &lt;span class="k">be&lt;/span> &lt;span class="k">performed&lt;/span> &lt;span class="k">if&lt;/span>
&lt;span class="s2">&amp;#34;terraform apply&amp;#34;&lt;/span> &lt;span class="k">is&lt;/span> &lt;span class="k">subsequently&lt;/span> &lt;span class="k">run&lt;/span>&lt;span class="p">.&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>This shows that Terraform will create 4 new resources:&lt;/p>
&lt;ul>
&lt;li>The Log Group&lt;/li>
&lt;li>An IAM Role&lt;/li>
&lt;li>An Inline IAM Policy attached to the Role&lt;/li>
&lt;li>Our Lambda Function&lt;/li>
&lt;/ul>
&lt;p>To actually create resources in AWS, execute:&lt;/p>
&lt;p>&lt;code>terraform apply&lt;/code>&lt;/p>
&lt;p>When prompted, enter &lt;code>yes&lt;/code>&lt;/p>
&lt;h2 id="testing-our-function">Testing our Function&lt;/h2>
&lt;p>To invoke our function from the command line, execute:&lt;/p>
&lt;pre>&lt;code>`aws lambda invoke --function-name simplest_lambda_function /dev/null --log-type Tail --query 'LogResult' --output text | base64 -d`
&lt;/code>&lt;/pre>&lt;p>which should respond with something similar to:&lt;/p>
&lt;div class="highlight">&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="err">START&lt;/span> &lt;span class="err">RequestId:&lt;/span> &lt;span class="err">ab&lt;/span>&lt;span class="mi">934699-7094-4829-8&lt;/span>&lt;span class="err">cee-aecd&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="err">c&lt;/span>&lt;span class="mi">193583&lt;/span> &lt;span class="err">Version:&lt;/span> &lt;span class="err">$LATEST&lt;/span>
&lt;span class="mi">2020-06-16&lt;/span>&lt;span class="err">T&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="mi">58&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="mf">46.098&lt;/span>&lt;span class="err">Z&lt;/span> &lt;span class="err">ab&lt;/span>&lt;span class="mi">934699-7094-4829-8&lt;/span>&lt;span class="err">cee-aecd&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="err">c&lt;/span>&lt;span class="mi">193583&lt;/span> &lt;span class="err">INFO&lt;/span> &lt;span class="err">EVENT:&lt;/span>
&lt;span class="p">{}&lt;/span>
&lt;span class="mi">2020-06-16&lt;/span>&lt;span class="err">T&lt;/span>&lt;span class="mi">16&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="mi">58&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="mf">46.098&lt;/span>&lt;span class="err">Z&lt;/span> &lt;span class="err">ab&lt;/span>&lt;span class="mi">934699-7094-4829-8&lt;/span>&lt;span class="err">cee-aecd&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="err">c&lt;/span>&lt;span class="mi">193583&lt;/span> &lt;span class="err">INFO&lt;/span> &lt;span class="err">CONTEXT:&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;callbackWaitsForEmptyEventLoop&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">true&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;functionVersion&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;$LATEST&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;functionName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;simplest_lambda_function&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;memoryLimitInMB&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;128&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;logGroupName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;/aws/lambda/simplest_lambda_function&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;logStreamName&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;2020/06/16/[$LATEST]759235165de84d3bbe7d3a87a7bd2db4&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;invokedFunctionArn&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;arn:aws:lambda:eu-west-1:522711524578:function:simplest_lambda_function&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;awsRequestId&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;ab934699-7094-4829-8cee-aecd2c193583&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="err">END&lt;/span> &lt;span class="err">RequestId:&lt;/span> &lt;span class="err">ab&lt;/span>&lt;span class="mi">934699-7094-4829-8&lt;/span>&lt;span class="err">cee-aecd&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="err">c&lt;/span>&lt;span class="mi">193583&lt;/span>
&lt;span class="err">REPORT&lt;/span> &lt;span class="err">RequestId:&lt;/span> &lt;span class="err">ab&lt;/span>&lt;span class="mi">934699-7094-4829-8&lt;/span>&lt;span class="err">cee-aecd&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="err">c&lt;/span>&lt;span class="mi">193583&lt;/span> &lt;span class="err">Duration:&lt;/span> &lt;span class="mf">2.86&lt;/span> &lt;span class="err">ms&lt;/span> &lt;span class="err">Billed&lt;/span> &lt;span class="err">Duration:&lt;/span> &lt;span class="mi">100&lt;/span> &lt;span class="err">ms&lt;/span> &lt;span class="err">Memory&lt;/span> &lt;span class="err">Size:&lt;/span> &lt;span class="mi">128&lt;/span> &lt;span class="err">MB&lt;/span> &lt;span class="err">Max&lt;/span> &lt;span class="err">Memory&lt;/span> &lt;span class="err">Used:&lt;/span> &lt;span class="mi">63&lt;/span> &lt;span class="err">MB&lt;/span> &lt;span class="err">Init&lt;/span> &lt;span class="err">Duration:&lt;/span> &lt;span class="mf">129.60&lt;/span> &lt;span class="err">ms&lt;/span>
&lt;/code>&lt;/pre>&lt;/div>&lt;p>While the command was a little long-winded, this is the log data you will also find in the CloudWatch Log Group. We can see the invocation id ab934699-7094-4829-8cee-aecd2c193583 and the content of the event and context objects. Finally we can see the &lt;code>REPORT&lt;/code> line with tells us&lt;/p>
&lt;ul>
&lt;li>The function took 2.86ms to run&lt;/li>
&lt;li>It will be billed at 100ms (always rounded up to the nearest 100ms)&lt;/li>
&lt;li>It was allocated 128MB of memory&lt;/li>
&lt;li>At its peak, it actually used a total of 63MB of memory&lt;/li>
&lt;li>It &amp;ldquo;Cold-Start&amp;rdquo; initialisation time was 129.6ms&lt;/li>
&lt;/ul>
&lt;p>You can use these values to tune the function which I will address in a future post.&lt;/p>
&lt;h2 id="cleaning-up">Cleaning Up&lt;/h2>
&lt;p>Since this was just a simple demo, you probably want to remove all these resources at some point. To do so execute:&lt;/p>
&lt;p>&lt;code>terraform destroy&lt;/code>&lt;/p>
&lt;h2 id="final-remarks">Final Remarks&lt;/h2>
&lt;p>This function doesn&amp;rsquo;t do a great deal, and right now we can only invoke it from either the command line or from the AWS Lambda console, but it covers the basics of using Terraform to get it, and all its supporting resources deployed.&lt;/p>
&lt;p>For more information see the official &lt;a href="https://www.terraform.io/">Terraform&lt;/a> site.&lt;/p></description></item></channel></rss>