<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lars Jacobsson</title>
    <description>The latest articles on DEV Community by Lars Jacobsson (@ljacobsson).</description>
    <link>https://dev.to/ljacobsson</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F468071%2F731193d3-01d5-4f62-8190-e0f3c54b0323.jpeg</url>
      <title>DEV Community: Lars Jacobsson</title>
      <link>https://dev.to/ljacobsson</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ljacobsson"/>
    <language>en</language>
    <item>
      <title>Take StepFunctions' TestState API one step further with samp-cli</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Wed, 06 Dec 2023 17:39:11 +0000</pubDate>
      <link>https://dev.to/aws-builders/take-stepfunctions-teststate-api-one-step-further-with-samp-cli-4klm</link>
      <guid>https://dev.to/aws-builders/take-stepfunctions-teststate-api-one-step-further-with-samp-cli-4klm</guid>
      <description>&lt;p&gt;A lot of great features were released by the StepFunctions team just before and during re:invent 2023. One of them was the &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/test-state-isolation.html" rel="noopener noreferrer"&gt;TestState API&lt;/a&gt; that lets developers test individual states in isolation from the rest of the state machine. Until now, we've had to run the state machine from the beginning to reach the state we're working on.&lt;/p&gt;

&lt;p&gt;AWS offers two ways to integrate with the API;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programmatically using the AWS-SDK - useful for unit/integration testing&lt;/li&gt;
&lt;li&gt;Via Workflow Studio in the AWS console - useful for iterating over changes in Workflow Studio:
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdzo5uoj43er067v8bkak.png" alt="TestState UI 1"&gt;
&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhzezf927obifw3v7dcse.png" alt="TestState UI 2"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are simple operations that let you target an ASL snippet (the task you want to test) with an optional input along with an IAM role, which is typically the execution role of the state machine.&lt;/p&gt;

&lt;p&gt;Whilst this is a great feature that allows us to add a whole new level of testability to our state machines, I think there's a gap in developer experience that can easily be filled.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DX gap
&lt;/h2&gt;

&lt;p&gt;Developers spend their time in code editors and terminals of their choice. Every time they have to open up a web browser, let it be to comment on a Jira issue, Google/ChatGPT something or navigate the AWS console, they get subject to a cognitive distraction. To me, DX is about removing this distraction.&lt;/p&gt;

&lt;p&gt;The TestState API is great for iterating over changes in an individual step and lets you massage the input/output processing or conditional logic in a choice state.&lt;/p&gt;

&lt;p&gt;Unless you're testing the very first state in the state machine, you'll need to get a sensible input payload. Without having run through the previous steps, this can be difficult and prone to errors through typos or guesswork.&lt;/p&gt;

&lt;p&gt;You're likely to look up an execution that successfully reached the step you're working on, copy that input and use it in your test. That's a lot of clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use &lt;code&gt;samp-cli&lt;/code&gt; to bridge the gap
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.tosamp-cli"&gt;https://github.com/ljacobsson/samp-cli#readme&lt;/a&gt; is a CLI tool aimed at bridging DX gaps for developers using AWS SAM.&lt;/p&gt;

&lt;p&gt;Version 1.0.66 introduces support for the TestState API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;$ npm i -g samp-cli&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Usage
&lt;/h3&gt;

&lt;p&gt;There are three StepFunction's specific commands;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;samp sfn init&lt;/code&gt; - inits a new state machine in a SAM project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;samp sfn sync&lt;/code&gt; - establishes a live upstream sync between your local ASL and the cloud&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;samp sfn test-state&lt;/code&gt; - lets you integrate with the TestState API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the following example I'll use the &lt;code&gt;sync&lt;/code&gt; and &lt;code&gt;test-state&lt;/code&gt; sub commands.&lt;/p&gt;

&lt;p&gt;Consider the following example state machine:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbftzz7bcbr1nqh9sj7oq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbftzz7bcbr1nqh9sj7oq.png" alt="Example state machine"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It takes an input of a payment id &lt;code&gt;{ "id": "..." }&lt;/code&gt;, fetches the payment from DynamoDB, checks the payment status in a &lt;code&gt;Choice&lt;/code&gt; state which sends the execution down the appropriate flow. &lt;/p&gt;

&lt;p&gt;However, upon running the state machine, each execution ends up in the &lt;code&gt;Unknown error&lt;/code&gt; state:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg62e7nb6w7w8lld2pc0b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg62e7nb6w7w8lld2pc0b.png" alt="Failed execution"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can assume that something is wrong with the &lt;code&gt;Choice&lt;/code&gt; logic, so let's test that one in isolation. As we have an execution which we expected to succeed, we'll want to grab the input from that.&lt;/p&gt;

&lt;p&gt;To achieve this I run &lt;code&gt;samp sfn test-state&lt;/code&gt; in the same folder as my SAM template and &lt;code&gt;samconfig.(toml|yaml)&lt;/code&gt;. The tool will attempt to parse your AWS profile, region and stack name from your samconfig. You can override these (see &lt;code&gt;samp sfn --help&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;In a different terminal I run &lt;code&gt;samp sfn sync&lt;/code&gt; in order to get my changes to the ASL instantly reflected in my test account.&lt;/p&gt;

&lt;p&gt;The following video demonstrates the steps I took to test, identify, fix and verify the bug I had in the Choice state - all in less than a minute and without leaving VSCode.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/e1SqKhJUxH4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Note that reusing input from recent executions only works with standard workflows. For express you will need to use inputs from a JSON file or from manual input.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>stepfunctions</category>
      <category>opensource</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Introducing samp-cli for local Lambda debugging of SAM and CDK stacks</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Mon, 24 Jul 2023 20:05:39 +0000</pubDate>
      <link>https://dev.to/aws-builders/introducing-samp-cli-for-local-lambda-debugging-1m01</link>
      <guid>https://dev.to/aws-builders/introducing-samp-cli-for-local-lambda-debugging-1m01</guid>
      <description>&lt;p&gt;&lt;em&gt;"Ok, how do I run this thing locally"&lt;/em&gt;, is the first thing most developers from a non-serverless background ask when they're tasked to work on a serverless stack. It's a valid question which seasoned serverless developers have worked around in various ways by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using local emulators like &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/using-sam-cli-local.html" rel="noopener noreferrer"&gt;sam local&lt;/a&gt;, &lt;a href="https://github.com/dherault/serverless-offline" rel="noopener noreferrer"&gt;serverless-offline&lt;/a&gt;, &lt;a href="https://localstack.cloud/" rel="noopener noreferrer"&gt;localstack&lt;/a&gt;, etc.&lt;/li&gt;
&lt;li&gt;Wrapping Lambda handlers in unit or integration tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While these approaches sort of work, they're not ideal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local emulators are not always accurate and can be a pain to setup and maintain. Also, they don't work well with other AWS services like S3, SQS, etc.&lt;/li&gt;
&lt;li&gt;Unit tests are great for testing individual functions, but they don't test the entire stack and you'll need to keep test payloads up to date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you really want is to be able to run Lambda functions on your local machine under the same context and IAM permissions as in the cloud, but have them triggered by real event sources, such as API Gateway, SQS, EventBridge, etc. &lt;/p&gt;

&lt;p&gt;I became inspired to do something about this while listening to &lt;a href="https://twitter.com/TastefulElk" rel="noopener noreferrer"&gt;Sebastian Bille&lt;/a&gt; talk about sst.dev at a recent AWS User Group meetup in Stockholm. SST includes &lt;a href="https://docs.sst.dev/live-lambda-development" rel="noopener noreferrer"&gt;local debugging&lt;/a&gt; out of the box and Sebastian's demo looked great. &lt;/p&gt;

&lt;p&gt;With my FOMO nerve tightly pinched, I felt determined to deliver a similar experience to SAM/CDK users.&lt;/p&gt;

&lt;p&gt;My goals for this were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No need to install any additional dev dependencies (given that you already have &lt;code&gt;samp-cli&lt;/code&gt; installed).&lt;/li&gt;
&lt;li&gt;No need to change your existing SAM template or CDK code.&lt;/li&gt;
&lt;li&gt;No need to change your existing Lambda code.&lt;/li&gt;
&lt;li&gt;It should work for all runtimes supported by Lambda. (so far it supports JavaScript/TypeScript, .NET and Python as of v1.0.40. Java, Go and Ruby is coming)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get debugging
&lt;/h2&gt;

&lt;p&gt;In this example I will debug a CDK stack since it's slightly more complicated than a SAM. The same approach works for SAM templates as well.&lt;/p&gt;

&lt;p&gt;I will use &lt;a href="https://github.com/aws-samples/serverless-patterns/tree/main/dynamodb-streams-to-eventbridge-outbox-pattern/cdk" rel="noopener noreferrer"&gt;dynamodb-streams-to-eventbridge-outbox-pattern&lt;/a&gt; by &lt;a href="https://twitter.com/boyney123" rel="noopener noreferrer"&gt;David Boyne&lt;/a&gt; as an example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install samp-cli
&lt;/h3&gt;

&lt;p&gt;First, install the latest version of &lt;code&gt;samp-cli&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; samp-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Deploy CDK stack to AWS if you haven't already
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure launch.config
&lt;/h3&gt;

&lt;p&gt;Note that automatic launch configuration is currently only supported for vscode.&lt;/p&gt;

&lt;p&gt;From your project root, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;samp &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For SAM stacks, this step is fast and easy, as it finds all information it needs in the &lt;code&gt;samconfig.toml&lt;/code&gt; file, but for CDK you have to give it some additional information.&lt;/p&gt;

&lt;p&gt;This one-time step per project guides you through a short wizard to create (or append to) a launch.json file in your .vscode folder.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ajth1hoHNEM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Start debugging
&lt;/h3&gt;

&lt;p&gt;Now you're ready to start debugging, but before you press F5 it's a good idea to understand what's happening under the hood. This will help you understand why &lt;em&gt;YOU SHOULD NEVER RUN THIS ON PRODUCTION&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The first thing to understand is that the tool &lt;em&gt;replaces your function code in AWS&lt;/em&gt; with a relay proxy for the duration of the debug session. Once you stop debugging, it will restore to the last deployed code artifact. &lt;/p&gt;

&lt;p&gt;It also updates your function configuration to set &lt;code&gt;MemorySize&lt;/code&gt; to 128mb and &lt;code&gt;Timeout&lt;/code&gt; to 60 seconds (it can be increased to 15 minutes by adding environment variable &lt;code&gt;SAMP_TIMEOUT=900&lt;/code&gt; in &lt;code&gt;launch.json&lt;/code&gt;). The reason for this is to allow you to sit for longer on breakpoints without the function timing out. Also, the relay proxy doesn't require much memory, so setting it to 128mb gets a bit cheaper.&lt;/p&gt;

&lt;p&gt;Both the local machine and the relay proxy lambda function establish an MQTT connection to AWS IoT Core. When the relay function gets triggered, it publishes the event payload, context object and environment variables to the MQTT topic. A local event router receives the message and routes it to the correct function on the developer's machine. Finally, the response it returned to the cloud over MQTT.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnjssopvt4p20a243kye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flnjssopvt4p20a243kye.png" alt="Normal vs. debug flow"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Video demonstrating setting breakpoints in each of the function in the above architecture as well as hot-reloading code changes:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/5M4zP60lwsQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The following sequence diagram visualises the flow of interactions (&lt;a href="https://github.com/ljacobsson/samp-cli/blob/main/images/samp-local-swimlanes.png" rel="noopener noreferrer"&gt;high resolution image here&lt;/a&gt;):&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fljacobsson%2Fsamp-cli%2Fmain%2Fimages%2Fsamp-local-swimlanes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fljacobsson%2Fsamp-cli%2Fmain%2Fimages%2Fsamp-local-swimlanes.png" alt="Sequence diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop debugging
&lt;/h3&gt;

&lt;p&gt;When debugging is stopped, a clean-up script is run. This restores each function according to the last deploy in CloudFormation. Once this is done, functions are invoked as normal in AWS Lambda.&lt;/p&gt;

&lt;p&gt;If this should fail for whatever reason, like if your AWS credentials have expired, you can re-run it using &lt;code&gt;samp local --force-restore&lt;/code&gt;. If that fails, just redeploy your stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CDK can be set up in many different ways and there will be approaches out there that won't work with this tool. It's been tested with the TypeScript/CDK patterns in &lt;a href="https://serverlessland.com/patterns" rel="noopener noreferrer"&gt;Serverless Land&lt;/a&gt;. I'd like to hear about your setup though, so please take a minute to &lt;a href="https://github.com/ljacobsson/samp-cli/issues/new?assignees=&amp;amp;labels=bug%2C+triage&amp;amp;projects=&amp;amp;template=bug_report.md&amp;amp;title=[samp%20local%20issue]:%20%3Cyour%20description%3E" rel="noopener noreferrer"&gt;raise an issue&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Functions using Lambda Layers could cause issues. If the layer is in the same stack, &lt;a href="https://github.com/ljacobsson/samp-cli#samp-local:~:text=NOTE%20%232%3A%20this%20command%20does%20not%20fully%20support%20Lambda%20layers.%20If%20you%20use%20layers%20to%20bundle%20your%20dependencies%2C%20you%20will%20have%20to%20manually%20install%20them%20locally%20as%20well.%20If%20you%20use%20layers%20to%20share%20custom%20code%20between%20functions%2C%20create%20a%20symlink%20from%20/opt/nodejs%20to%20the%20layer%20folder%20in%20your%20function%20folder." rel="noopener noreferrer"&gt;this workaround&lt;/a&gt; might work. If you include third party layers, like &lt;a href="https://docs.powertools.aws.dev/lambda/typescript/latest/#lambda-layer" rel="noopener noreferrer"&gt;Lambda Powertools&lt;/a&gt;, just make sure you have it included in your &lt;code&gt;devDependencies&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If your function accesses resources in a VPC, you'll need to establish a tunnel to the VPC resources. See &lt;a href="https://docs.aws.amazon.com/vpn/latest/clientvpn-admin/how-it-works.html" rel="noopener noreferrer"&gt;the AWS docs&lt;/a&gt;. You can also mock these calls by checking the presence of environment variable &lt;code&gt;LOCAL_DEBUG&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;p&gt;The cost of running this should be small, but please make sure you understand the pricing for &lt;a href="https://aws.amazon.com/lambda/pricing/" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt; and &lt;a href="https://aws.amazon.com/iot-core/pricing/" rel="noopener noreferrer"&gt;IoT Core&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In short:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A 128mb Lambda invocation is charged at $0.000126 per minute&lt;/li&gt;
&lt;li&gt;$1 per 1 million AWS IoT messages&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  About &lt;code&gt;samp-cli&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Samp-cli&lt;/code&gt; was previously knows as &lt;code&gt;sam-patterns-cli&lt;/code&gt;. It was born in a &lt;a href="https://twitter.com/lajacobsson/status/1377368916800929796" rel="noopener noreferrer"&gt;response&lt;/a&gt; to the announcement of &lt;a href="https://serverlessland.com/patterns" rel="noopener noreferrer"&gt;serverlessland.com/patterns&lt;/a&gt; back in the spring of 2021. Since then, it's grown into a versatile productivity tool for SAM users, hence the rename. Here's a list of what it supports at the time of writing: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6u2coklbewkeetlj0wt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6u2coklbewkeetlj0wt.png" alt="samp help"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Every way to improve the developer experience for Lambda is a win for all the serverless developers out there. Getting fast feedback on the code we write has always been important and has not changed with the serverless movement.&lt;/p&gt;

&lt;p&gt;I hope this will speed up development for you as much as it has for me. If not, please let me know any improvements you'd like to see by &lt;a href="https://github.com/ljacobsson/samp-cli/issues/new?assignees=&amp;amp;labels=bug%2C+triage&amp;amp;projects=&amp;amp;template=bug_report.md&amp;amp;title=[samp%20local%20issue]:%20%3Cyour%20description%3E" rel="noopener noreferrer"&gt;raising an issue&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>lambda</category>
      <category>serverless</category>
      <category>cdk</category>
      <category>aws</category>
    </item>
    <item>
      <title>Supercharge your StepFunctions productivity with local file system sync in Workflow Studio</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Mon, 02 Jan 2023 22:10:10 +0000</pubDate>
      <link>https://dev.to/aws-builders/supercharge-your-stepfunctions-productivity-with-local-file-system-sync-in-workflow-studio-4ab9</link>
      <guid>https://dev.to/aws-builders/supercharge-your-stepfunctions-productivity-with-local-file-system-sync-in-workflow-studio-4ab9</guid>
      <description>&lt;h2&gt;
  
  
  Note: This article is now outdated, but I'll leave it public anyway. As of re:Invent 2023, Workflow Studio with local file system sync is available natively from within Application Composer. I hope that this article served as inspiration to AWS when deciding to build this.
&lt;/h2&gt;




&lt;p&gt;One of my favourite AWS services is Step Functions. It's a serverless visual workflow orchestration service which, once you are up to speed with it, helps you build extremely scalable and resilient applications.&lt;/p&gt;

&lt;p&gt;At &lt;a href="https://www.mathem.se" rel="noopener noreferrer"&gt;Mathem&lt;/a&gt;, we are heavy users of it as an integral part of our serverless architecture. At the time of writing we have over 70 state machines running in production.&lt;/p&gt;

&lt;p&gt;With heavy usage comes ideas on how you want the perfect developer experience to be.&lt;/p&gt;

&lt;p&gt;The release of &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/workflow-studio.html" rel="noopener noreferrer"&gt;Workflow Studio&lt;/a&gt; in 2021 was a huge leap in the right direction. It lets developers quickly and intuitively design visual workflows using drag and drop instead of writing ASL in YAML, JSON or CDK.&lt;/p&gt;

&lt;p&gt;This post assumes that you have some pre-existing experience of building state machines in Step Functions, preferably using Workflow Studio.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Despite how great Workflow Studio is, there is a gap in the full end-to-end developer experience. Once the workflow is designed, the developer has to go through the following manual steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Export the workflow as YAML or JSON&lt;/li&gt;
&lt;li&gt;Import the file in their project&lt;/li&gt;
&lt;li&gt;Replace placeholders in the YAML with &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html#sam-statemachine-definitionsubstitutions" rel="noopener noreferrer"&gt;DefinitionSubstitutions&lt;/a&gt;. 
In the vast majority of development scenarios, the workflow isn't perfect after the first iteration - it normally needs several iterations. If iterating requires manual steps, then it'll get very costly, not to mention boring, for the developer. For each change, the developer needs to:&lt;/li&gt;
&lt;li&gt;Convert their ASL to JSON (if it isn't already)&lt;/li&gt;
&lt;li&gt;Import it back into Workflow Studio&lt;/li&gt;
&lt;li&gt;Make changes&lt;/li&gt;
&lt;li&gt;GOTO 1 &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Below is a state machine describing the above steps. The steps with a sad smiley are the ones we want to eliminate.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpnlnjl9divmbcbw07jt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmpnlnjl9divmbcbw07jt.png" alt="DX state machine" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;My ideal solution, alike my #1 AWS wishlist item, would be an AWS supported version of Workflow Studio as a VSCode extension with a bidirectional sync between the designer and the ASL definition. Until we have that I will support this workaround.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting started
&lt;/h3&gt;

&lt;p&gt;First, you need to install a Chrome extension from &lt;a href="https://github.com/ljacobsson/sfn-workflow-studio-sync" rel="noopener noreferrer"&gt;github.com/ljacobsson/sfn-workflow-studio-sync&lt;/a&gt;. The instructions are in the readme.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link Workflow Studio with your local ASL file
&lt;/h3&gt;

&lt;p&gt;Next, you'll need to navigate to &lt;a href="https://eu-west-1.console.aws.amazon.com/states/home?region=eu-west-1#/visual-editor?flowType=create" rel="noopener noreferrer"&gt;Workflow Studio&lt;/a&gt;. You can also open an existing state machine and choose Edit and click through to Workflow Studio.&lt;/p&gt;

&lt;p&gt;If the extension was successfully installed, you'll see a new button at the top right corner: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqlrckkp6j2bzy7a96oq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyqlrckkp6j2bzy7a96oq.png" alt="Extension buttons 2" width="656" height="164"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking that will bring up a file picker dialog. This is using the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API" rel="noopener noreferrer"&gt;File System Access API&lt;/a&gt; to create a temporary read/write access between the webpage and the selected file for the duration of the page's lifetime.&lt;/p&gt;

&lt;p&gt;Keep your ASL file in one window and Workflow Studio in the other and watch the time saving as it happens: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwa39ndqpn0hie1ovm1sm.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwa39ndqpn0hie1ovm1sm.gif" alt="Demo 1" width="720" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This solves the export/import manual tasks in the developer experience flow from above, but we want to automate the linking of resources in the SAM template and the ASL definition. This is of course only applicable if you are a SAM user.&lt;/p&gt;

&lt;h3&gt;
  
  
  Link your SAM template with Workflow Studio
&lt;/h3&gt;

&lt;p&gt;After the ASL file sync has been established, you'll notice two new buttons in the top right corner of the studio:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1wtu8z66pap92pj0vod.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi1wtu8z66pap92pj0vod.png" alt="Extension buttons" width="521" height="59"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 'Force sync'-button forces the sync to happen. In some cases it's needed, for example after changing Choice conditions.&lt;/p&gt;

&lt;p&gt;The other button lets us link a SAM template to Workflow Studio in order to do the definition substitution mapping.&lt;/p&gt;

&lt;p&gt;Some resource types, for example Lambda, offer a drop down in the GUI to select a hard-coded reference. Using a hard coded reference is normally a bad idea. In order to create a substitution, select 'Enter' at the top of the dropdown list.&lt;/p&gt;

&lt;p&gt;This will bring up a dropdown representing the resource types in your SAM template along with their available attributes:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrnt7pk810sh3vu1s2vp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgrnt7pk810sh3vu1s2vp.png" alt="Dropdown 1" width="775" height="493"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selecting an option will update both your SAM and ASL simultaneously:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ln5ja72pqhp5scw55qv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ln5ja72pqhp5scw55qv.gif" alt="Simultaneous file sync" width="760" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most resource types, however, require you to complete JSON to set up your integrations. Here you simply type in the substitution variable you want to use:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqx6j7q2kcjsxjd9nh43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faqx6j7q2kcjsxjd9nh43.png" alt="JSON substitution" width="550" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can add more than one substitution variable per JSON configuration:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fre3gs70p9hh04yd4j33y.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fre3gs70p9hh04yd4j33y.gif" alt="Demo 2" width="800" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This extension is heavily dependent on the DOM of the Workflow Studion, which I have no control over. Chances are that AWS will release a breaking change at any time. If that happens, please submit an &lt;a href="https://github.com/ljacobsson/sfn-workflow-studio-sync/issues/new" rel="noopener noreferrer"&gt;issue&lt;/a&gt; and I'll do my best to fix it.&lt;/p&gt;

&lt;p&gt;AWS prioritises new features based on customer feedback, so if you like this, please submit a feature request to AWS for official support &amp;lt;3&lt;/p&gt;

&lt;p&gt;Follow me on Twitter (&lt;a href="https://twitter.com/lajacobsson" rel="noopener noreferrer"&gt;@lajacobsson&lt;/a&gt;) for updates on this and other serverless developer experience tools I maintain.&lt;/p&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>Building a low code URL shortener using API Gateway, StepFunctions and DynamoDB</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Tue, 25 Oct 2022 13:37:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/buidling-a-low-code-url-shortener-using-api-gateway-stepfunctions-express-and-dynamodb-37l3</link>
      <guid>https://dev.to/aws-builders/buidling-a-low-code-url-shortener-using-api-gateway-stepfunctions-express-and-dynamodb-37l3</guid>
      <description>&lt;p&gt;This post describes how you can implement a simple URL shortener using native low code AWS services. Some previous knowledge of API Gateway and StepFunctions is assumed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://www.mathem.se/" rel="noopener noreferrer"&gt;Mathem&lt;/a&gt;, we use a home built URL shortener in SMS communication with our customers. Until now, this has been hosted on an on-prem legacy environment which is soon to be turned off, so we had to move it to our serverless architecture in AWS one way or another.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://www.google.com/search?q=github+serverless+url+shortener" rel="noopener noreferrer"&gt;plenty of serverless URL shorteners&lt;/a&gt; publically available on GitHub. However, I wanted to explore if there's a way to achieve this without having to write any Lambda function code at all. &lt;/p&gt;

&lt;p&gt;I'm a huge fan of Lambda, but it does come with (for this use case, arguably insignificant) cold starts and the responsibility of keeping the runtime version up to date. &lt;/p&gt;

&lt;p&gt;The StepFunctions team has made a couple of releases during 2022 that has enabled state machines to do more than just orchestrating Lambda functions invocations, and this solution would not have been possible a few months ago prior to the &lt;a href="https://aws.amazon.com/blogs/compute/introducing-new-intrinsic-functions-for-aws-step-functions/" rel="noopener noreferrer"&gt;new intrinsic functions&lt;/a&gt; were released.&lt;/p&gt;

&lt;p&gt;The full solution is available &lt;a href="https://github.com/ljacobsson/low-code-url-shortener" rel="noopener noreferrer"&gt;on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a short URL
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funtzfvcwiqmuxutuw9pq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Funtzfvcwiqmuxutuw9pq.png" alt="Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  State machine design
&lt;/h3&gt;

&lt;p&gt;The state machine consists of three states, &lt;em&gt;Initialise&lt;/em&gt;, &lt;em&gt;Create hash&lt;/em&gt; and &lt;em&gt;Store URL&lt;/em&gt; as well as some logic to handle duplicate hashes.&lt;/p&gt;

&lt;p&gt;Let's go through each of them from top down:&lt;/p&gt;

&lt;h4&gt;
  
  
  Initialise
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initialise:
  Type: Pass
  OutputPath: $
  Parameters: 
    Splitter: "-"
    Attempts: 0
  Next: Create hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a &lt;code&gt;Pass&lt;/code&gt; state that initialises the execution. It passes on two parameters; &lt;code&gt;Splitter&lt;/code&gt;, which is used to split the UUID in the next step as well as &lt;code&gt;Attempts&lt;/code&gt; which is used to avoid an infinite loop if all hashes are already taken.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create hash
&lt;/h4&gt;

&lt;p&gt;To get a short, but unique hash to hide the long URL behind we'll make use of three new intrinsic functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-uuid-generate" rel="noopener noreferrer"&gt;States.UUID()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-string-operation" rel="noopener noreferrer"&gt;States.StringSplit()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-intrinsic-functions.html#asl-intrsc-func-arrays:~:text=%2C9%5D%20%7D-,States.ArrayGetItem,-This%20intrinsic%20function" rel="noopener noreferrer"&gt;States.ArrayGetItem()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is also a &lt;code&gt;Pass&lt;/code&gt; state and looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create hash:
  Type: Pass
  OutputPath: $
  Parameters:
    Hash.$: States.ArrayGetItem(States.StringSplit(States.UUID(), $.Splitter), 1)
    Attempts.$: States.MathAdd($.Attempts, 1)
  Next: Store URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note how the output from the first state, &lt;code&gt;$.Splitter&lt;/code&gt; is used here. Ideally we'd like to just use &lt;code&gt;States.StringSplit(States.UUID(), "-")&lt;/code&gt;, but the &lt;code&gt;StringSplit&lt;/code&gt; function expects a valid JSON path as the second argument.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The UUID is formatted like this: &lt;code&gt;ca4c1140-dcc1-40cd-ad05-7b4aa23df4a8&lt;/code&gt;. Splitting it on the dash ('-') character gives us this array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;["ca4c1140", "dcc1", "40cd", "ad05", "7b4aa23df4a8"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's always divided in lower case, alphanumeric sequences of 8, 4, 4, 4, and 12 characters. &lt;/p&gt;

&lt;p&gt;Next, we have to decide how long a hash we want and it comes down to how many different combinations (n&lt;sup&gt;36&lt;/sup&gt;) of URLs we need.&lt;br&gt;
4 characters: 1,679,616 permutations&lt;br&gt;
8 characters: 2,821109907×10&lt;sup&gt;12&lt;/sup&gt; permutations&lt;br&gt;
12 characters: 4,738381338×10&lt;sup&gt;18&lt;/sup&gt; permutations&lt;/p&gt;

&lt;p&gt;We are fine with 4 for our use case, so we'll access it using index 1: &lt;code&gt;States.ArrayGetItem(splitArray, 1)&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Note that we are limited to lower case characters. To make short hashes with a mix of casing, we'd need a Lambda function in the mix.&lt;/p&gt;
&lt;h4&gt;
  
  
  Store URL
&lt;/h4&gt;

&lt;p&gt;This state takes the output and stores it in DynamoDB using a native service integration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Store URL:
  Type: Task
  Resource: arn:aws:states:::aws-sdk:dynamodb:putItem
  ResultPath: null
  Parameters:
    TableName: ${UrlTable}
    ConditionExpression: attribute_not_exists(Id)
    Item:
      Id:
        S.$: $.Hash
      Url:
        S.$: $$.Execution.Input.Url
      HitCount: 
        N: "0"
  Catch:
    - ErrorEquals: 
        - DynamoDb.ConditionalCheckFailedException
      Next: Continue trying?
      ResultPath: null
  End: true
Continue trying?:
  Type: Choice
  Choices:
    - Variable: $.Attempts
      NumericLessThan: 10
      Next: Create hash
  Default: Fail
Fail:
  Type: Fail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;ConditionExpression&lt;/code&gt; and the error handling in the &lt;code&gt;Catch&lt;/code&gt; clause. This handles scenarios of duplicate hashes and will simply generate a new one until it finds an available one. As a safety guard it will bail out after 10 attempts. In a production environment you'd want an alarm on when that happens as it's an indication that the number of available permutations are running out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accessing a short URL
&lt;/h2&gt;

&lt;p&gt;This state machine is much simpler and only contains a single state that does two things; increments a hit counter and returns the long URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p9ttj64jigdx954a59p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8p9ttj64jigdx954a59p.png" alt="Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ASL looks like this and uses an SDK integration to DynamoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;StartAt: Do redirect
States:
  Do redirect:
    Type: Task
    Resource: arn:aws:states:::aws-sdk:dynamodb:updateItem
    Parameters:
      TableName: ${UrlTable}
      ConditionExpression: attribute_exists(Id)
      ReturnValues: ALL_NEW
      UpdateExpression: SET HitCount = HitCount + :incr
      ExpressionAttributeValues:
        :incr:
          N: "1"
      Key:
        Id:
          S.$: $.hash
    ResultSelector:
      Url.$: $.Attributes.Url.S
    End: true    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hooking the state machines up with API Gateway
&lt;/h2&gt;

&lt;p&gt;At first I wanted to use a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html" rel="noopener noreferrer"&gt;HttpApi&lt;/a&gt; to enjoy lower latency and less cost, but it proved really hard to get the request and response mapping working. The main issue was that the output from the state machine comes as stringified JSON, and when using HttpApi, the &lt;code&gt;$util.parseJSON()&lt;/code&gt; function wasn't available. Shoutout to all Community Builders, and in particular &lt;a href="https://twitter.com/jimmydahlqvist" rel="noopener noreferrer"&gt;@jimmydahlqvist&lt;/a&gt; who got engaged in the problem &amp;lt;3 &lt;/p&gt;

&lt;p&gt;After much frustration I swapped to use a RestApi, which made my life easier. I will not go into details here, but let's zoom in on the request and response mapping. The full OpenAPI&lt;/p&gt;

&lt;h3&gt;
  
  
  Create URL: (POST /)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;responses:
  200:
    statusCode: 200
    responseTemplates:
      application/json: 
        Fn::Sub: "#set($response = $input.path('$'))\n { \"ShortUrl\": \"https://${DomainName}/$util.parseJson($response.output).Hash\" }"
requestTemplates:
  application/json: 
    Fn::Sub: "#set($data = $input.json('$')) { \"input\": \"$util.escapeJavaScript($data)\", \"stateMachineArn\": \"${CreateUrl}\" }"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Redirect to URL (GET /{id})
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;responses:
  200:
    statusCode: 301
    responseTemplates:
      text/html: "#set($response = $input.path('$'))\n#set($context.responseOverride.header.Location = $util.parseJson($response.output).Url)"
requestTemplates:
  application/json: 
    Fn::Sub: "#set($data = $util.escapeJavaScript($input.params('id'))) { \"input\": \"{ \\\"hash\\\": \\\"$data\\\" }\", \"stateMachineArn\": \"${RedirectToUrl}\" }"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Too see the above mappings in context, visit the OpenAPI spec &lt;a href="https://github.com/ljacobsson/low-code-url-shortener/blob/main/api.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This article showed how we can use StepFunctions new intrinsic functions together with its native SDK service integrations to create a fully functional, yet simple, URL shortener. It certainly comes with some limitations that Lambda can solve and if you hit them, feel free to extend the workflow with a function.&lt;/p&gt;

&lt;p&gt;If you have any improvements, such as converting to HttpApi or introducing better hashing, please submit a &lt;a href="https://github.com/ljacobsson/low-code-url-shortener" rel="noopener noreferrer"&gt;pull request&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building this project I spent 5% on creating the state machines and 95% on the API Gateway mappings. I'm hoping to see an improved SAM support for connecting API Gateway and synchronous StepFunctions Express state machines. Please upvote &lt;a href="https://github.com/aws/serverless-application-model/issues/2561" rel="noopener noreferrer"&gt;this issue&lt;/a&gt; if you agree.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>The serverless architecture of a talking doorbell</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Sun, 09 Jan 2022 21:33:16 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-a-serverless-talking-doorbell-1f21</link>
      <guid>https://dev.to/aws-builders/building-a-serverless-talking-doorbell-1f21</guid>
      <description>&lt;p&gt;In this post I'll describe how I used some of AWS' serverless offering to build a doorbell that leverages AI and speech synthesis to describe the person or people outside the door. &lt;/p&gt;

&lt;p&gt;Here's a video demo. Keep on reading to learn how it's built.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/DYGVGh5M-wg"&gt;
&lt;/iframe&gt;
&lt;br&gt;
&lt;em&gt;Note: age approximation needs to be worked on&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;My family has gone through quite a few cheap wireless doorbells over recent years. They've all had issues such as breaking randomly and not warning when the battery is low. &lt;/p&gt;

&lt;p&gt;We've considered buying a &lt;a href="https://store.google.com/se/product/nest_doorbell_battery" rel="noopener noreferrer"&gt;Google Nest Doorbell&lt;/a&gt;, but they are quite expensive, so I decided to build my own doorbell that takes the concept to the next level.&lt;/p&gt;

&lt;p&gt;The speech synthesis isn't just a fun feature, it could also be useful for visually impaired people.&lt;/p&gt;
&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Hardware
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Raspberry-Pi-MS-004-00000024-Model-Board/dp/B01LPLPBS8/" rel="noopener noreferrer"&gt;Raspberry Pi 3B+&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Arducam-Megapixels-Sensor-OV5647-Raspberry/dp/B012V1HEP4" rel="noopener noreferrer"&gt;Raspberry Pi camera module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2 meter Raspbery Pi camera flex cable&lt;/li&gt;
&lt;li&gt;Small speaker with 3.5mm AUX interface&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://shelly.cloud/products/shelly-button-1-smart-home-automation-device/" rel="noopener noreferrer"&gt;Shelly Button 1&lt;/a&gt; (could really be any MQTT enabled button)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  AWS services
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;S3&lt;/a&gt; - for image and speech storage&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;EventBridge&lt;/a&gt; - to act on items created in S3&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/lambda/" rel="noopener noreferrer"&gt;Lambda&lt;/a&gt; - compute&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ses/" rel="noopener noreferrer"&gt;Simple Email Service&lt;/a&gt; - to send email alerts&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/rekognition/" rel="noopener noreferrer"&gt;Rekognition&lt;/a&gt; - picture analysis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/polly/" rel="noopener noreferrer"&gt;Polly&lt;/a&gt; - speech synthesis&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/iot/" rel="noopener noreferrer"&gt;Iot&lt;/a&gt; - MQTT broker to send messages to the Raspberry Pi&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;StepFunctions&lt;/a&gt; - to orchestrate the above&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  High level architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyyejobkjsifzefd13yp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkyyejobkjsifzefd13yp.png" alt="High level architecture"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Putting the hardware together
&lt;/h2&gt;

&lt;p&gt;The first challenge was to get the camera installed ouside the door. My door has a roof over it and right next to it a window. I 3D printed a &lt;a href="https://www.thingiverse.com/thing:1156296" rel="noopener noreferrer"&gt;small camera mount&lt;/a&gt; and squeezed the ribbon cable between the window and the window frame. Since I put it out it's been both humid and cold (-15C) and the camera is still alive.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsnpjlt847suv50ii26r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsnpjlt847suv50ii26r.png" alt="Camera mount"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also printed a &lt;a href="https://www.thingiverse.com/thing:4825668" rel="noopener noreferrer"&gt;wall mount&lt;/a&gt; for the button and modded it slightly to add my family's surname.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxh5ddrkwwmu71h3jja2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdxh5ddrkwwmu71h3jja2.png" alt="Button wall mount"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next I configured a Mosquitto MQTT broker on the Raspberry Pi and &lt;a href="https://shelly.cloud/documents/user_guide/shelly_button_1.pdf" rel="noopener noreferrer"&gt;configured the Shelly Button&lt;/a&gt; to send its events to it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Raspberry Pi services
&lt;/h2&gt;

&lt;p&gt;The Raspberry Pi is running three services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/raspberrypi/camera.py" rel="noopener noreferrer"&gt;camera.py&lt;/a&gt; - listens to MQTT messages from the doorbell button, takes a picture and uploads to S3 via a presigned URL it fetches from a Lambda function hosted behind API Gateway*&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/raspberrypi/speech.py" rel="noopener noreferrer"&gt;speech.py&lt;/a&gt; - listens to MQTT messages from the AWS IoT endpoint which contain a pre-signed S3 URL of an audio file describing the guest&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/raspberrypi/battery.py" rel="noopener noreferrer"&gt;battery.py&lt;/a&gt; - Reports battery status to an API Gateway endpoint*&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;I'm using IP restriction on the API Gateway endpoints to only allow traffic from my home network. IPs can be spoofed, so an enhancement here could be to use a Lambda authorizer with a client secret.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My Python skills include a lot of googling and copy/pasting, so I will not go into details on these scripts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cloud infra
&lt;/h2&gt;

&lt;p&gt;When a new image lands in the S3 bucket a number of independent things need to happen;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The image needs to be analysed for faces, emotion and other labels. This is done in two independent Amazon Rekognition jobs&lt;/li&gt;
&lt;li&gt;If there are faces found, generate a human friendly string based on the features and labels in the image&lt;/li&gt;
&lt;li&gt;Send this information to a number of destination - in this case email and MQTT back to the RaspberryPi&lt;/li&gt;
&lt;li&gt;In the case of MQTT also generate a speech synthesis&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Whilst all this could be achieved in a single Lambda functions, I wanted to acheive a clean orchestration with independently retryable tasks, for which StepFunctions is the obvious choice in AWS.&lt;/p&gt;

&lt;p&gt;I'm using the &lt;a href="https://aws.amazon.com/blogs/aws/new-use-amazon-s3-event-notifications-with-amazon-eventbridge/" rel="noopener noreferrer"&gt;native S3 to EventBridge integration&lt;/a&gt; to trigger the state machine with the following event pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;S3Event:
  Type: EventBridgeRule
  Properties:
    EventBusName: default
    InputPath: $.detail.object
    Pattern:
      source:
        - aws.s3
      detail-type:
        - Object Created
      detail:
        bucket:
          name:
            - !Ref S3Bucket
        object:
          key:
            - prefix: image/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This rule triggers the state machine when a new image with a prefix of &lt;code&gt;image/&lt;/code&gt; is uploaded to the bucket.&lt;/p&gt;

&lt;p&gt;I'm not normally a fan of drag-and-drop, but the &lt;a href="https://aws.amazon.com/about-aws/whats-new/2021/announcing-workflow-studio-a-new-low-code-visual-workflow-designer-foraws-step-functions/" rel="noopener noreferrer"&gt;StepFunctions Workflow Studio&lt;/a&gt; is actually really nice to get that initial ASL generated.&lt;/p&gt;

&lt;p&gt;The state machine looks like this and below I'll go through and describe each task.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F484rhpran8v6hov4flyk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F484rhpran8v6hov4flyk.png" alt="State machine design"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  State machine tasks
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Parallel image analysis
&lt;/h4&gt;

&lt;p&gt;I want to describe the person at the door by both facial features such as age, gender, beard, emotion, etc and by other labels, like clothing or other things they might be carrying.&lt;br&gt;
Amazon Rekognition offers two separate endpoints for this;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectFaces.html" rel="noopener noreferrer"&gt;DetectFaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/rekognition/latest/dg/API_DetectLabels.html" rel="noopener noreferrer"&gt;DetectLabels &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both jobs are run on the same image independently from eachother, hence I can use a &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-parallel-state.html" rel="noopener noreferrer"&gt;parallel state&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Until recently this would have to be run in a Lambda Function, but since the release of StepFunctions &lt;a href="https://aws.amazon.com/about-aws/whats-new/2021/09/aws-step-functions-200-aws-sdk-integration" rel="noopener noreferrer"&gt;AWS-SDK integration&lt;/a&gt; we can now integrate directly with most services without glue code in Lambda.&lt;/p&gt;

&lt;p&gt;The output of the parallel state is an array of both tasks outputs.&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Has faces?
&lt;/h4&gt;

&lt;p&gt;This is a &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-choice-state.html" rel="noopener noreferrer"&gt;choice state&lt;/a&gt; that checks if the face detection gave any results. There are three branches of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;No faces were found - use generic message.&lt;/li&gt;
&lt;li&gt;Exactly one face was found - generate description and enrich with results from label detection.&lt;/li&gt;
&lt;li&gt;More than one faces were found. Since there's no way to link the label detection to the faces I skip the label enrichment and just describe the people by their faces.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The choice state gives each branch exactly one thing to do without if statements. In the case of #1, there's no need to even invoke a Lambda function.&lt;/p&gt;
&lt;h4&gt;
  
  
  For each face / Build face description string
&lt;/h4&gt;

&lt;p&gt;This is a &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-map-state.html" rel="noopener noreferrer"&gt;map state&lt;/a&gt; that iterates through each face from the face detection and invokes a function &lt;code&gt;Build face description string&lt;/code&gt;. Each function returns a string like &lt;code&gt;"a happy man aged about 39"&lt;/code&gt;. The output of the map state is an array such as &lt;code&gt;["a surprised man aged about 25", "a happy woman aged about 32"]&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/src/FaceDescription.js" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Combine descriptions
&lt;/h4&gt;

&lt;p&gt;This is a simple function that joins the strings from the map state into one string and modifies it to make it gramatically ok. &lt;br&gt;
&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/src/CombineDescriptions.js" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Build single face description
&lt;/h4&gt;

&lt;p&gt;When exactly one face is detected we can enrich it with the output form the object label detection. The output of this function can be something like &lt;code&gt;"a surprised man aged about 25 with a baseball cap and a jacket"&lt;/code&gt;&lt;br&gt;
&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/src/SingleFaceDescription.js" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Generic message
&lt;/h4&gt;

&lt;p&gt;This is a &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/amazon-states-language-pass-state.html" rel="noopener noreferrer"&gt;pass state&lt;/a&gt; that outputs the generic message &lt;code&gt;"There is someone at the door"&lt;/code&gt; &lt;/p&gt;
&lt;h4&gt;
  
  
  Send message to notification channels
&lt;/h4&gt;

&lt;p&gt;In the first version I'm happy with receiving an email when someone is at the door as well as the audio description. This can easily be extended with Slack notifications, etc.&lt;/p&gt;

&lt;p&gt;In the 'send email'-branch we need a pre-signed S3 URL for the image before firing off the email which looks like this:&lt;br&gt;
&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/src/GetSignedImageURL.js" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4s2mmuwrckz1wi3kf8t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb4s2mmuwrckz1wi3kf8t.png" alt="Confused man"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If there are more than one person in the picture, then each person will be described, but without the label enrichment:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F646l0nv8ixjiy0qnln0n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F646l0nv8ixjiy0qnln0n.png" alt="Theives"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Synthesise speech and send to MQTT
&lt;/h4&gt;

&lt;p&gt;This step calls a Lambda function that does the following;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Uses the Amazon Polly SDK to synthesize speech&lt;/li&gt;
&lt;li&gt;Uploads audio to S3&lt;/li&gt;
&lt;li&gt;Generates a pre-signed URL to the audio&lt;/li&gt;
&lt;li&gt;Publish URL to an MQTT topic
[Source]&lt;a href="https://github.com/ljacobsson/ai-doorbell/blob/main/src/SynthesizeSpeech.js" rel="noopener noreferrer"&gt;https://github.com/ljacobsson/ai-doorbell/blob/main/src/SynthesizeSpeech.js&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first approach was to use the StepFunction's SDK integration and call &lt;a href="https://docs.aws.amazon.com/polly/latest/dg/API_StartSpeechSynthesisTask.html" rel="noopener noreferrer"&gt;StartSpeechSynthesis&lt;/a&gt; without a Lambda function and then act on the audio that it uploaded to S3. However, I saw delays of ~15 seconds before the audio was available, so I decided to pack it all into one function for performance reasons.&lt;/p&gt;
&lt;h3&gt;
  
  
  REST APIs
&lt;/h3&gt;

&lt;p&gt;There are two API Gateway endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /url&lt;/code&gt; - called by the raspberry Pi to get a pre-signed URL with which it uploads the captured image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PUT /battery&lt;/code&gt; - The Shelly button reports on its battery level via a MQTT topic on every button press. It PUTs the current level and it it goes below 20% it'll send an email to the user so they can take it of fthe wall for a couple of hours to charge. &lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Areas of improvement
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sometimes there's quite a long delay between the ding-dong and the speech. This brings a risk that the description of the person might be read out &lt;em&gt;after&lt;/em&gt; the door has been opened which could lead to an uncomfortable situation depending on how the AI interpreted the person. So far, whilst testing, it hasn't said anything rude or offensive.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Train a model to recognise people I know. My fellow AWS Community Builder &lt;a href="https://dev.to/pubudusj"&gt;Pubudu Jayawardana&lt;/a&gt; has also built a doorbell that does just that which he describes &lt;a href="https://dev.to/aws-builders/how-i-created-a-door-bell-with-aws-serverless-3n9j"&gt;here&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Convert the state machine to an express workflow. This will be faster and cheaper. However, I keep it as standard for now to get the visual debugging option in the console. We don't get enough people visiting to make the pricing benefit of express noticable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It currently connects to AWS IoT without [AWS Greengrass}(&lt;a href="https://aws.amazon.com/greengrass/" rel="noopener noreferrer"&gt;https://aws.amazon.com/greengrass/&lt;/a&gt;). If and when this doorbell goes on sale I'll need a better fleet management, so I should run the python scripts under Greengrass.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This doorbell has been deployed at my home for a week now and is the first of my IoT hobby projects that we've actually have found useful. &lt;/p&gt;

&lt;p&gt;The first working version of it took two hours to build and didn't use Polly for speech synthesis. Instead it used &lt;a href="https://pypi.org/project/pyttsx3/" rel="noopener noreferrer"&gt;pyttsx3&lt;/a&gt; which gave the user the voice response a bit faster faster, but with a very unpleasant tone:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/vpj4wN_sUXI"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The mechanical bell is something I might bring back in a future version.&lt;/p&gt;

&lt;p&gt;You can find the project on GitHub: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ljacobsson" rel="noopener noreferrer"&gt;
        ljacobsson
      &lt;/a&gt; / &lt;a href="https://github.com/ljacobsson/ai-doorbell" rel="noopener noreferrer"&gt;
        ai-doorbell
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      AI doorbell that uses speech synthesis to describe the person at the door
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>aws</category>
      <category>serverless</category>
      <category>iot</category>
    </item>
    <item>
      <title>An approach to loosely coupled CloudWatch alarms and contextual alerts</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Tue, 14 Sep 2021 21:50:27 +0000</pubDate>
      <link>https://dev.to/mathem/an-approach-to-loosely-coupled-cloudwatch-alarms-and-contextual-alerts-2fh2</link>
      <guid>https://dev.to/mathem/an-approach-to-loosely-coupled-cloudwatch-alarms-and-contextual-alerts-2fh2</guid>
      <description>&lt;p&gt;We have for the past four years used a third party for monitoring and alerting of our large scale serverless ecommerce platform. &lt;/p&gt;

&lt;p&gt;For a number of reasons, we have recently decided to leave this provider in favour of CloudWatch.&lt;/p&gt;

&lt;p&gt;This post will take you through how we used my two favourite AWS services, EventBridge and StepFunctions, to set up error anomaly detection for 100% of our Lambda functions along with context rich Slack alerts in less than two days. This model supports any resource type and alarm type, but for brevity I will focus on error anomaly alarms for Lambda.&lt;/p&gt;

&lt;p&gt;Some understanding of both &lt;a href="https://aws.amazon.com/eventbridge/" rel="noopener noreferrer"&gt;EventBridge&lt;/a&gt; and &lt;a href="https://aws.amazon.com/step-functions/" rel="noopener noreferrer"&gt;StepFunctions&lt;/a&gt; is assumed.&lt;/p&gt;




&lt;p&gt;One thing we lacked with the third party solution was a coupling with CloudFormation stacks. Monitors were created by hand and if something was removed on the AWS side we were left with an orphaned monitor.&lt;/p&gt;

&lt;p&gt;Being obsessed with automation, we needed a fast way to onboard all core resources to being monitored by CloudWatch. As a first approach we build &lt;a href="https://github.com/mhlabs/cfn-alarms" rel="noopener noreferrer"&gt;cfn-alarms&lt;/a&gt;, a CLI tool to generate CloudFormation alarm and alerting boilerplate resources based on the resources in a template, but to roll that out on hundreds of stacks proved inefficient. However, we still wanted to tie the alarms with the lifecycle of the resources they monitor so that when a Lambda function is deleted, the associated alarms are deleted with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loosely coupled alarms
&lt;/h2&gt;

&lt;p&gt;At first we looked at what we require our teams to monitor when they deploy features to our platform. We came up with a quite small list of services including Lambda errors, SQS queue depth, API 5XX/4XX rate, etc.&lt;/p&gt;

&lt;p&gt;When new resources are created or deleted, CloudTrail emits events to EventBridge's default bus with the configuration of the resource. To create or delete alarms we can simply create rules to match these events and route them to Lambda functions that programmatically spin them up or down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  LambdaCreation:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ./src
      Handler: lambda/creation.handler
      Events:
        LambdaCreationEvent:
          Type: EventBridgeRule
          Properties:
            InputPath: $.detail.requestParameters
            EventBusName: default
            Pattern:
              source:
                - aws.lambda
              detail-type:
                - AWS API Call via CloudTrail
              detail:
                eventName:
                  - prefix: CreateFunction
    [...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule will forward the CloudTrail event's requestParameters to the function handler, which looks like this (truncated for brevity. Refer to the &lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/CloudWatch.html#putMetricAlarm-property" rel="noopener noreferrer"&gt;SDK docs&lt;/a&gt; for syntax):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.handler = async (event) =&amp;gt; {
  const functionName = event.functionName;
  const tags = event.tags;

  const threshold = tags["alarm:lambda:errors:anomaly:threshold"] || 2;

  await cloudWatch
    .putAnomalyDetector({
        ... truncated ...
    })
    .promise();

  await cloudWatch
    .putMetricAlarm({
      AlarmName: `auto:${functionName}:lambda:errors:anomaly`,
      AlarmDescription: `Error anomaly detected`,
      Tags: Object.keys(tags).map((p) =&amp;gt; {
        return { Key: p, Value: tags[p] };
      }),
      ... truncated ...
    })
    .promise();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note how we allow for customisation of certain alarm configurations by using a tagging strategy of &lt;code&gt;alarm:&amp;lt;service&amp;gt;:&amp;lt;metric&amp;gt;:&amp;lt;evaluation type&amp;gt;:&amp;lt;variable&amp;gt;&lt;/code&gt;. This naming is reflected in the &lt;code&gt;AlarmName&lt;/code&gt; property for a semantic coupling. Also note how we relay the tags from the monitored resource onto the alarm resource. This will later be used in the alerting phase when monitors go in and out of alarm.&lt;/p&gt;

&lt;p&gt;The alarm lifecycle event flow is very simple and looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nkrqclfpsun3nv7n923.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7nkrqclfpsun3nv7n923.png" alt="Alarm creation and deletion diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This approach also allows us to build custom tooling to onboard or reconfigure the entire platform in one go without the need of deploying all stacks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Alerting
&lt;/h2&gt;

&lt;p&gt;Getting alerting right is difficult and is a balance act of not skipping valuable alerts, but at the same time keeping it brief and relevant to avoid &lt;a href="https://en.wikipedia.org/wiki/Alarm_fatigue" rel="noopener noreferrer"&gt;alarm fatigue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At Mathem we use Slack across all development teams and we have a requirement to tag all resources with the team name owning the service. We wanted to leverage this to automatically direct alerts to team specific Slack channels. This is why we relay the resource tags on the alarm resource in the code example above.&lt;/p&gt;

&lt;p&gt;When a CloudWatch alarm changes state there's an event put on EventBridge. A state can be one of &lt;code&gt;OK&lt;/code&gt;, &lt;code&gt;ALARM&lt;/code&gt; and &lt;code&gt;INSUFFICIENT_DATA&lt;/code&gt;. We are interested in alerting on &lt;code&gt;ALARM&lt;/code&gt; and communicating recovery on &lt;code&gt;OK&lt;/code&gt;. The aim is to be as brief as possible whilst giving the developers quick access to extended context, such as logs and CloudWatch metrics related to the alarm. For this we'll use a StepFunctions state machine triggered by an EventBridge rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source:
  - aws.cloudwatch
detail-type:
  - CloudWatch Alarm State Change
detail:
  state:
    value:
      - ALARM
      - OK
resources:
  - prefix: !Sub &amp;gt;-
      arn:aws:cloudwatch:${AWS::Region}:${AWS::AccountId}:alarm:auto:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the prefix matching on the alarm ARN. Including &lt;code&gt;auto:&lt;/code&gt; at the end ensures only automatically created alarms are consumed by the state machine.&lt;/p&gt;

&lt;p&gt;The state machine takes different actions depending on the alarm state and resource type the alarm is concerning.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiqwe10bsqkpew4cafty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiqwe10bsqkpew4cafty.png" alt="State machine"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first state simply fetches the tags from the alarm resource. The tags we are interested in are &lt;code&gt;team&lt;/code&gt;, &lt;code&gt;aws:cloudformation:stack-name&lt;/code&gt; and &lt;code&gt;aws:cloudformation:logical-id&lt;/code&gt;. The &lt;code&gt;team&lt;/code&gt; tag decides where to send the alert and the &lt;code&gt;aws:cloudformation:...&lt;/code&gt; tags are used to make the alert message more human readable.&lt;/p&gt;

&lt;p&gt;Next, we check if it's an alarm or a recovery. If it's an alarm we'll send a message to a Slack channel following &lt;code&gt;#alerts-&amp;lt;teamname&amp;gt;-&amp;lt;environment&amp;gt;&lt;/code&gt;. If the channel doesn't exist, our Slack bot creates it for us.&lt;/p&gt;

&lt;p&gt;The alert is short and to the point. It also provides the developer buttons that instantly takes them to either the alarm page or the failing resource's page in the AWS console. This saves us from wasting time manually clicking our way to the root cause.&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foeu6wbc2a3jtpadtuhpz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foeu6wbc2a3jtpadtuhpz.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the message is sent we pass the output to a parallel state. In one branch we store the message id, or timestamp, we get back from Slack to a DynamoDB table. We'll retrieve this when the alarm has recovered to update the original message.&lt;/p&gt;

&lt;p&gt;The other branch in the parallel state allow us to extend the alarm notification with additional context depending on the resource type we're notifying about. At this point in time we have only implemented extra context for Lambda error alarms for which we fetch the most recent error log and post it as a thread reply along with a link to the log group:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs88kwdjijajq4c3cb1c8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs88kwdjijajq4c3cb1c8.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This can be extended with for example X-Ray data or whatever might be useful without flooding the channel.&lt;/p&gt;

&lt;p&gt;When an alarm reach an &lt;code&gt;OK&lt;/code&gt; state we update the original alarm instead of posting a new message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8i4rv1johelqks5mxc5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw8i4rv1johelqks5mxc5.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This post covered an approach using EventBridge and Lambda to onboard a large set of resources to be monitored by CloudWatch Alarms and StepFunctions to create contextual  Slack alerts that can easily be extended to other notification channels.&lt;/p&gt;

&lt;p&gt;You can find a reference project for this post &lt;a href="https://github.com/ljacobsson/cw-alarms-poc" rel="noopener noreferrer"&gt;here&lt;/a&gt;. The state of the project is early days and be mindful of &lt;a href="https://aws.amazon.com/cloudwatch/pricing/" rel="noopener noreferrer"&gt;CloudWatch Alarm costs&lt;/a&gt; before deploying.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>eventbridge</category>
      <category>stepfunctions</category>
    </item>
    <item>
      <title>Accelerate your serverless development with sam-patterns-cli</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Tue, 06 Apr 2021 21:17:39 +0000</pubDate>
      <link>https://dev.to/mathem/accelerate-your-sam-development-with-sam-patterns-cli-1f08</link>
      <guid>https://dev.to/mathem/accelerate-your-sam-development-with-sam-patterns-cli-1f08</guid>
      <description>&lt;p&gt;&lt;em&gt;This post targets users of the AWS Serverless Application Model (SAM)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;AWS recently released the &lt;a href="https://serverlessland.com/patterns"&gt;Serverless Patterns Collection&lt;/a&gt; which serves as a central directory of common best practice serverless patterns that, when they are combined, can make up complex applications.&lt;/p&gt;

&lt;p&gt;At Mathem we have a home built templating engine that contains patterns that are common to our SAM stacks which developers can inject directly into their templates. Many of these are specific to our use cases and not easy to open source.&lt;/p&gt;

&lt;p&gt;Contributing to our solution is fairly easy, but is still enough of a learning curve and a context switching exercise for the collection to remain more basic than we’d desire.&lt;/p&gt;

&lt;p&gt;Our initial thought when we saw the Serverless Patterns Collection announcement was to build a command line interface around it to make the patterns easily accessible and usable directly from the code editor. From experience we've learned that for such collection to be alive and grow there has to be minimal effort involved in contributing to it. The goal is that a pattern gets written once, then shared and reused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing sam-patterns-cli
&lt;/h2&gt;

&lt;p&gt;Installation:&lt;br&gt;
&lt;code&gt;npm install -g sam-patterns-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is still a very young project under development - version 0.0.9 at the time of writing, but is nevertheless ready to use.&lt;/p&gt;

&lt;p&gt;It’s by default linked to the Serverless Patterns Collection’s &lt;a href="https://github.com/aws-samples/serverless-patterns"&gt;GitHub repository&lt;/a&gt;, but more sources, public or private, can be added.&lt;/p&gt;

&lt;p&gt;The tool comes with 4 commands;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;source&lt;/strong&gt; - Lets the user add their own repositories. This could be a private and company specific repository or some other collection, like Jeremy Daly’s &lt;a href="https://www.jeremydaly.com/serverless-reference-architectures/"&gt;Serverless Reference Architectures&lt;/a&gt;. See the &lt;a href="https://github.com/mhlabs/sam-patterns-cli#sam-patterns-source"&gt;readme&lt;/a&gt; for instructions on how to link it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;explore&lt;/strong&gt; - Lets the user navigate available patterns and visualise then using the power of &lt;a href="https://github.com/mhlabs/cfn-diagram"&gt;cfn-diagram&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;import&lt;/strong&gt; - Lets the user import a pattern straight into their CloudFormation/SAM template without manually copy/pasting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;share&lt;/strong&gt; - When working on a template, a developer might identify some resources making up a serverless pattern. This lets them extract that pattern and share it with other users of the tool by pushing it to a patterns collection on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the following example we'll build an application that receives order updates from EventBridge, processes the order in a Lambda function and stores it in an S3 bucket. There will be no focus on the function code itself - just the SAM template.&lt;/p&gt;

&lt;p&gt;We launch &lt;code&gt;sam-patterns import&lt;/code&gt; and get presented with a list of available patterns. Searching for ‘eventbridge lambda’ gives us a few options. These patterns are named according to the flow order of them. In our case the ideal pattern would be ‘eventbridge-lambda-s3’, but the closest one is ‘eventbridge-lambda’, so we’ll choose that.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ziKIfdVS1J4"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;At the time of writing there was no pattern where a Lambda function writes to an S3 bucket, so we’ll go ahead and create one. The template now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWSTemplateFormatVersion: 2010-09-09
Transform:
 - AWS::Serverless-2016-10-31
Resources:
 OrderConsumer:
   Type: AWS::Serverless::Function
   Properties:
     CodeUri: src/
     Handler: OrderConsumer.handler
     Runtime: nodejs14.x
     Timeout: 3
     Policies:
       - S3WritePolicy:
         BucketName: !Ref OrderBucket
     Environment:
       Variables:
         OrderBucket: !Ref OrderBucket
     Events:
       Trigger:
         Type: EventBridgeRule
         Properties:
           EventBusName: custom-bus
           Pattern:
             source:
               - order-service
             detail-type:
               - order-updated
 OrderBucket:
   Type: AWS::S3::Bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have now made a modification to an existing pattern that makes up a new pattern; EventBridge-&amp;gt;Lambda-&amp;gt;S3.&lt;/p&gt;

&lt;p&gt;To avoid having to write the same code again or to help someone else in your team or in the world to quickly create the same pattern you can now share it back to GitHub. If the pattern is of a generic nature we encourage you to submit a pull request to &lt;a href="https://github.com/aws-samples/serverless-patterns"&gt;aws-samples/serverless-patterns&lt;/a&gt;, but to quickly share it with your team you can submit it to a repository of your own.&lt;/p&gt;

&lt;p&gt;To do this, we’ve created a repository, github.com/mhlabs/sam-patterns-collection that will serve as our patterns repository. Next we’ll add it as a source for sam-patterns-cli. For this we use &lt;code&gt;sam-patterns source&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/LrBgq08tieU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We’ve now linked our repository and we’re ready to use &lt;code&gt;sam-patterns share&lt;/code&gt; to share the pattern with the world.&lt;/p&gt;

&lt;p&gt;It’s likely that the developer reusing this pattern will work on a stack that handles something else than orders, so in this step we’ll make the shared template dynamically editable by the end user.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/ridloYYuyoo"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;That’s it! The new pattern is ready to be used by ourselves or by someone else needing the same functionality.&lt;/p&gt;

&lt;p&gt;Let’s say that someone is building a payment processor service that receives payments from EventBridge, processes them and stores the records in S3. They’ll be up and running in seconds:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/UAYLh4z5iog"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The user gets prompted to fill in the dynamic values; item name, Lambda runtime, eventbus name, pattern source and detail-type and that’s all they need to do. Now, this was a simple example for the sake of brevity. More complex patterns can easily be created and shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This example took us through how to import a serverless pattern, modify it into a new pattern, share it and, finally, reuse it.&lt;/p&gt;

&lt;p&gt;To understand how the dynamic values work, please head over to the &lt;a href="https://github.com/mhlabs/sam-patterns-collection/blob/main/eventbridge-lambda-s3/template.yaml"&gt;pattern we created&lt;/a&gt; in this example and study the Metadata section of the template.&lt;/p&gt;

&lt;p&gt;At this point in time only SAM template code can be imported and shared. The aim is to support backing files, such as Lambda function code and OpenAPI definitions in the future.&lt;/p&gt;

&lt;p&gt;This project is open source and can be found &lt;a href="https://github.com/mhlabs/sam-patterns-cli"&gt;here&lt;/a&gt;. Contributions, bug reports and feature requests make us happy :-)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Maximize your EventBridge productivity with evb-cli</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Tue, 29 Dec 2020 07:46:56 +0000</pubDate>
      <link>https://dev.to/mathem/maximize-your-eventbridge-productivity-with-evb-cli-2o3l</link>
      <guid>https://dev.to/mathem/maximize-your-eventbridge-productivity-with-evb-cli-2o3l</guid>
      <description>&lt;p&gt;The aim of this article is to demonstrate how &lt;a href="https://www.npmjs.com/package/@mhlabs/evb-cli"&gt;evb-cli&lt;/a&gt; can be used to boost your productivity when working with &lt;a href="https://aws.amazon.com/eventbridge/"&gt;Amazon EventBridge&lt;/a&gt;. I will present use cases and how you can use the tool to solve them.&lt;/p&gt;

&lt;p&gt;I assume that you are familiar with or a user of EventBridge. If not, there are some &lt;a href="https://www.youtube.com/watch?v=0gPZx9ex9gY"&gt;great videos&lt;/a&gt; and &lt;a href="https://aws.amazon.com/blogs/compute/tag/amazon-eventbridge/"&gt;blog posts&lt;/a&gt; available to get started.&lt;/p&gt;

&lt;p&gt;This is part one of two covering this tool. Some commands require a back-end deployed to the target account and for the sake of digestability I will cover them in a separate post at a later date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Background&lt;/li&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Installation&lt;/li&gt;
&lt;li&gt;Schema Registry basics&lt;/li&gt;
&lt;li&gt;
Commands

&lt;ul&gt;
&lt;li&gt;
evb pattern - generate event patterns from schemas in the Schema Registry&lt;/li&gt;
&lt;li&gt;
evb input - generate InputTransformer based on schemas in the Schema Registry&lt;/li&gt;
&lt;li&gt;
evb code-binding - generate code bindings for you Lambda consumers&lt;/li&gt;
&lt;li&gt;
evb browse - browse where and how events from a given source/detail-type are consumed &lt;/li&gt;
&lt;li&gt;
evb diagram - Visualise your EventBridge architecture&lt;/li&gt;
&lt;li&gt;
evb extract-sam-event - convert SAM notation to &lt;code&gt;AWS::Events::Rule&lt;/code&gt; for more complex use cases&lt;/li&gt;
&lt;li&gt;
evb test-event - test event pattern against an event payload&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Commands covered in part two:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;evb replay - replay archived events against select target(s)&lt;/li&gt;
&lt;li&gt;evb replay-dead-letter - replay dead letter events&lt;/li&gt;
&lt;li&gt;evb local - local debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Background &lt;a&gt;&lt;/a&gt;&lt;a&gt;
&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;EventBridge was released in July 2019, but it took about six months before we started using it heavily at &lt;a href="https://mathem.se"&gt;MatHem&lt;/a&gt;. Up until then we were leveraging SNS and SQS for our events, but as we started playing with EventBridge we quickly noticed how it improved the decoupling of our services and decreased friction between teams. &lt;/p&gt;

&lt;p&gt;Prior to EventBridge, when someone from team A wanted to subscribe to messages using filtering from an SNS topic owned by team B, they had to submit a pull request to team B to add the message attribute they wanted to filter on. We found this being backwards and inefficient.&lt;/p&gt;

&lt;p&gt;With EventBridge, the producing team doesn't need to know its consumers. The consumers have access to perform filtering on any part of the payload, but they still need to know the contract of the data they can filter on. The release of the &lt;a href="https://aws.amazon.com/about-aws/whats-new/2019/12/introducing-amazon-eventbridge-schema-registry-now-in-preview/"&gt;EventBridge Schema Registry&lt;/a&gt; at re:Invent 2019 meant that the decoupling got even more optimized.&lt;/p&gt;

&lt;p&gt;As we started composing patterns we found that although we had removed team friction we still spent a lot of time on getting the patterns right. Also, writing complex patterns is error prone and requires multiple deploys to test and get right, so we began automating the bridge between the Schema Registry and the pattern composition.&lt;/p&gt;

&lt;p&gt;The first version of &lt;code&gt;evb-cli&lt;/code&gt; had that sole purpose - to generate event patterns to be pasted into the template. As we got really fast at that, we quickly found further bottlenecks, so we added commands for composing &lt;code&gt;InputTransformer&lt;/code&gt;s, architecture visualization, code bindings, etc. The sections below describe each command in depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Much of the functionality is based on content in the EventBridge Schema Registry. Make sure you have &lt;a href="https://aws.amazon.com/blogs/compute/introducing-amazon-eventbridge-schema-registry-and-discovery-in-preview/#:~:text=To%20discover%20the%20schema%2C%20in,bus%20and%20choose%20Start%20discovery."&gt;enabled schema discovery&lt;/a&gt; on your event buses.&lt;/p&gt;

&lt;p&gt;To get the most of this tool you need to have the &lt;a href="https://aws.amazon.com/cli/"&gt;aws-cli&lt;/a&gt; preconfigured with an IAM or SSO user with permissions covering &lt;code&gt;events:Get*&lt;/code&gt;, &lt;code&gt;events:List*&lt;/code&gt;, &lt;code&gt;schemas:Get*&lt;/code&gt;, &lt;code&gt;schemas:List*&lt;/code&gt; and &lt;code&gt;events:List*&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;You will also need &lt;a href="https://www.npmjs.com/"&gt;NPM package manager&lt;/a&gt; and NodeJS 12+ installed on your system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;npm install -g @mhlabs/evb-cli&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema Registry basics &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;To make the most of this tool it's important to understand the basics and the limitations of the EventBridge Schema Registry.&lt;/p&gt;

&lt;p&gt;There are three types of registries;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS event schema registry - the schemas of the events the AWS services produce. These are the events that live on the default event bus&lt;/li&gt;
&lt;li&gt;Discovered schema registry - on a custom eventbus you can enable schema discovery. These schemas for these events end up here. They can be from an SaaS-provider or your own custom events&lt;/li&gt;
&lt;li&gt;Custom schema registry - here you can upload your own schemas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The discoverer ingests a sampling of the real events and analyses the values. An annoying thing here is that it can't handle null very well and seem to create new versions from 2 events where a value is null in one and non-null in the other. This means when we run commands like &lt;code&gt;evb pattern&lt;/code&gt; it only knows the structure of the &lt;em&gt;last&lt;/em&gt; event it ingested. If that contained many null values it simply won't know about them.&lt;/p&gt;

&lt;p&gt;Another important thing to know is that what constitutes a schema is the combination of &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;detail-type&lt;/code&gt;. For example, the following event will get an entry in the registry under the name &lt;code&gt;my-source@my-detail-type&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "source": "my-source",
  "detail-type": "my-detail-type
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's therefore a good practice to stick to the same &lt;code&gt;detail&lt;/code&gt; structure for all events sharing that combination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Commands &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The commands of the CLI have come to life organically as we identified the need for them. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;evb pattern&lt;/code&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Purpose:&lt;/em&gt; Quickly and accurately build event patterns to match events from the EventBridge Schema Registry&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example use case&lt;/em&gt;: You are tasked with sending a Slack message to a channel each time a CodePipeline execution in any US region fails.&lt;/p&gt;

&lt;p&gt;The events from CodePipeline look like this (taken from &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/event-types.html#codepipeline-event-type"&gt;here&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "version": "0",
  "id": "CWE-event-id",
  "detail-type": "CodePipeline Pipeline Execution State Change",
  "source": "aws.codepipeline",
  "account": "123456789012",
  "time": "2017-04-22T03:31:47Z",
  "region": "us-east-1",
  "resources": [
    "arn:aws:codepipeline:us-east-1:123456789012:pipeline:myPipeline"
  ],
  "detail": {
    "pipeline": "myPipeline",
    "version": "1",
    "state": "STARTED",
    "execution-id": "01234567-0123-0123-0123-012345678901"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern you're after looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source:
  - "aws.codepipeline"
detail-type:
  - "CodePipeline Pipeline Execution State Change"
region:
  - prefix: "us-"
detail:
  state:
    - "FAILED"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though this is a very simple pattern it still requires googling the event structure if you want to compose it manually.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;evb pattern&lt;/code&gt; you can solve this in seconds:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/K48-gfii9p8"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Note that I'm passing &lt;code&gt;-t template.yaml&lt;/code&gt; to the command. This will prompt you with the option to attach the generated pattern to existing resources in your template. These can either be of type &lt;code&gt;AWS::Serverless::Function&lt;/code&gt; or &lt;code&gt;AWS::Events::Rule&lt;/code&gt;. If you skip the &lt;code&gt;-t&lt;/code&gt; flag, the event will be written to the console.&lt;/p&gt;

&lt;p&gt;Template injection works for both YAML and JSON, but note that any YAML comments will be stripped during serialization.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;evb input&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Purpose:&lt;/em&gt; Quickly and accurately build &lt;code&gt;InputTransformer&lt;/code&gt; CloudFormation objects.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example use case&lt;/em&gt;: Building on the use case above we now want to invoke a Lambda function when events from CodePipeline match our pattern. To make the function code simpler, we only want to forward the properties we actually need from the event payload. Let's say we want to let the Slack users know the name of the pipeline and the region it was running in.&lt;/p&gt;

&lt;p&gt;To achieve this we need to compose an &lt;code&gt;InputTransformer&lt;/code&gt; block for the &lt;code&gt;Target&lt;/code&gt; under the &lt;code&gt;AWS::Events::Rule&lt;/code&gt; resource that maps JSON paths form the original payload into a new template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;InputTransformer:
  InputPathsMap:
    region: "$.region"
    pipeline: "$.detail.pipeline"
  InputTemplate: "{\"region\": &amp;lt;region&amp;gt;, \"pipeline\": &amp;lt;pipeline&amp;gt;}" 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is tedious copy/paste work and, at least for us, the &lt;code&gt;InputTemplate&lt;/code&gt; is often a straight off representation of the &lt;code&gt;InputPathsMap&lt;/code&gt;. A nice feature would be if the CloudFormation team made &lt;code&gt;InputTemplate&lt;/code&gt; optional and default it to mirror the &lt;code&gt;InputPathsMap&lt;/code&gt; if omitted.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;evb input&lt;/code&gt; is very similar to evb-pattern, but the output is different. Also, at the time of writing it doesn't yet support template injection, so a copy/paste from the console is required:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/oa23XcZPJmk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Note that I pass in &lt;code&gt;-f yaml&lt;/code&gt;. This is telling the tool to output the result in YAML. Default is JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;evb code-binding&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Purpose:&lt;/em&gt; Generate code bindings in your preferred language for your Lambda function to use.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example use case&lt;/em&gt;: Given the above &lt;code&gt;InputTemplate&lt;/code&gt; we now want to receive this as a strongly typed object. For this example I will use C#, but a fill list of supported languages can be found in &lt;a href="https://github.com/quicktype/quicktype#target-languages"&gt;quicktype's documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the following video I generate two types of bindings; one for the &lt;code&gt;InputTemplate&lt;/code&gt; and one for the entire original event.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/nbuY-HS0VAU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;To get the optimized code binding for my &lt;code&gt;InputTemplate&lt;/code&gt; (FromTemplate.cs), I run &lt;code&gt;evb code-binding&lt;/code&gt; and point it at my template. At first I get prompted which language I want to generate for. Next it asks me in which registry the schema can be found. Since we're working with AWS produced events, it's under &lt;code&gt;aws.events&lt;/code&gt;. If it's from a custom event bus, then you'd select &lt;code&gt;discovered-schemas&lt;/code&gt;. Lastly, I select which &lt;code&gt;Rule&lt;/code&gt; and &lt;code&gt;Target&lt;/code&gt; combination I want to generate for. Here we only have one choice, so I select that one.&lt;/p&gt;

&lt;p&gt;For the sake of demo I also generate a class for the entire original untransformed event (FromSchema.cs). I do this by skipping to provide the &lt;code&gt;-t&lt;/code&gt; flag. The tool now takes me through prompts to select language, registry, source and detail-type. As you can see, the output is very verbose and the majority of properties will not be of any interest to the function.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;evb browse&lt;/code&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Purpose:&lt;/em&gt; Quickly find usages of events conforming to a given schema.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example use cases:&lt;/em&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I could be a producer of an event and I've sent broken data for a period of time and I need to alert downstream teams&lt;/li&gt;
&lt;li&gt;I'm about to start working on a feature and I want to check if something similar already exists in the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/C_qrJd6GIiI"&gt;
&lt;/iframe&gt;
&lt;br&gt;
Here we can see our Lambda target that consumes events belonging to the &lt;code&gt;aws.codepipeline@CodePipelinePipelineExecutionStateChange&lt;/code&gt; schema along with some other pre-existing consumers. I can view the event pattern and any input transformations.&lt;/p&gt;

&lt;p&gt;Another way to browse events is to visualize them using &lt;code&gt;evb diagram&lt;/code&gt; as described below.&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;evb diagram&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Purpose:&lt;/em&gt; Visualize how events are flowing within an event bus&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example use cases:&lt;/em&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To have a discussion around during project planning meetings&lt;/li&gt;
&lt;li&gt;To quickly get a mental view of how things hang together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/vFd0E0Rh7qk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In my next post I will go through setting up the &lt;code&gt;evb-local&lt;/code&gt; back-end. With that deployed on the target account you are able to see the events as JSON in real-time as they happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;evb extract-sam-event&lt;/code&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Purpose:&lt;/em&gt; To convert a SAM &lt;a href="https://github.com/aws/serverless-application-model/blob/master/versions/2016-10-31.md#eventbridgerule"&gt;EventBridgeRule&lt;/a&gt; to an &lt;code&gt;AWS::Event::Rule&lt;/code&gt; to get more control over the rule.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example use case:&lt;/em&gt; Often when you need an EventBridge to Lambda integration it's tempting to start with the &lt;a href="https://github.com/aws/serverless-application-model/blob/master/versions/2016-10-31.md#eventbridgerule"&gt;EventBridgeRule&lt;/a&gt; SAM shorthand. Under the hood, the SAM macro inflates this into a full &lt;code&gt;AWS::Events::Rule&lt;/code&gt; and an &lt;code&gt;AWS::Lambda::Permission&lt;/code&gt;, but using this disables you from using the powerful &lt;code&gt;InputTransformer&lt;/code&gt;. I've opened an &lt;a href="https://github.com/aws/serverless-application-model/issues/1535"&gt;issue&lt;/a&gt; about this, so please upvote. &lt;/p&gt;

&lt;p&gt;I've found myself refactoring a SAM &lt;code&gt;EventBridgeRule&lt;/code&gt; into the real thing enough times to automate it with a simple command. Again, note that if you have comments in your YAML, then these will be stripped using this command.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/qzv0VPBGoC8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;evb test-event&lt;/code&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Purpose:&lt;/em&gt; Tests an event payload against rules on an event bus.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example use cases:&lt;/em&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have been given an example payload to consume and you want to test your rule.&lt;/li&gt;
&lt;li&gt;You are a producer of events and want to see which rules match it.&lt;/li&gt;
&lt;li&gt;Integration tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/EGrE_MNylx8"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post I've gone through how we optimize many of the tasks developers working with EventBridge encounter - from building patterns, input transformers, code bindings to gaining insights into the system by browsing schema usage or visualizing the architecture.&lt;/p&gt;

&lt;p&gt;In my next post I will focus on local debugging and replaying of archived events.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>serverless</category>
      <category>eventbridge</category>
    </item>
    <item>
      <title>Turning hand written shopping lists into online carts using Amazon Textract</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Wed, 23 Dec 2020 13:41:40 +0000</pubDate>
      <link>https://dev.to/mathem/turning-hand-written-shopping-lists-into-online-carts-using-amazon-textract-5eb5</link>
      <guid>https://dev.to/mathem/turning-hand-written-shopping-lists-into-online-carts-using-amazon-textract-5eb5</guid>
      <description>&lt;p&gt;&lt;em&gt;MatHem is Sweden's leading independent online grocery shop, with a distribution network reaching over 50% of Swedish households and a strong brand built over the past ten years.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Table of content
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Motivation&lt;/li&gt;
&lt;li&gt;Hardware&lt;/li&gt;
&lt;li&gt;
Device software

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Components setup&lt;/li&gt;
&lt;li&gt;Capture and upload image&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

Backend architecture

&lt;ul&gt;
&lt;li&gt;Choosing AI service&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Designing the case&lt;/li&gt;

&lt;li&gt;End result&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Motivation &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Using your fingers to type on a keyboard or phone when searching for things to buy is the assumed most efficient way to shop online. Along came voice assistants a few years ago as a good alternative to the screen.&lt;/p&gt;

&lt;p&gt;I think there's a good point in minimizing pulling the phone up from our pockets and in my family we have a habit of using old fashioned hand written shopping lists that, when it's time to place the order, we type into the search bar at &lt;a href="//mathem.se"&gt;Mathem&lt;/a&gt;. This means we're writing it twice - once with a pen and once with a keyboard which is way too inefficient.&lt;/p&gt;

&lt;p&gt;To tackle this problem I decided to bridge the gap between the physical list and the online cart by building a scanning device that uses hand writing recognition along with existing Mathem APIs. I wanted to have this done as a weekend project, and knowing that the 3D modelling and printing would be the most time consuming parts, I decided to time cap the development time to a minimum and write as little code as possible. &lt;/p&gt;

&lt;p&gt;Since MatHem is already runs on a 100% serverless architecture, the obvious path was to hook this into that and rely on AWS's serverless offerings for AI/ML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hardware components &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I decided to use components I had lying around at home;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/ELEMENT-Element14-Raspberry-Pi-Motherboard/dp/B07P4LSDYV" rel="noopener noreferrer"&gt;Raspberry Pi 3 Model B+&lt;/a&gt; running latest Raspberry Pi OS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://joy-it.net/en/products/SEN-BUMP01" rel="noopener noreferrer"&gt;Bump sensor&lt;/a&gt;. Used as button. There are probably better buttons out there, but this was accessible&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/Raspberry-Pi-Camera-Module-Megapixel/dp/B01ER2SKFS/" rel="noopener noreferrer"&gt;Raspberry Pi Camera Module&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The case was printed using a &lt;a href="https://www.amazon.com/Creality-Ender-Printer-Year-Warranty/dp/B08GJ8FV37" rel="noopener noreferrer"&gt;Creality Ender Pro 3&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wiring the bump sensor is easily done. It has three pins - one for ground, one for power and one for GPIO - take note of the pin you use. In the code below I use GPIO 12, but it could be any GPIO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Device software &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Firstly I had to decide where to do the inference - on the edge or in the cloud. There are solutions for this that can run on the Pi, but that would tie this feature to the hardware. What if I want to move it to a less performant hardware or integrate it in our mobile apps?&lt;/p&gt;

&lt;p&gt;The only benefit I could see for running it on the Raspberry Pi would be that it'd probably be faster since it would be fewer round trips to the cloud.&lt;/p&gt;

&lt;p&gt;With extensibility in mind I decided to make the edge device dumb and literally just capture the image and upload to the cloud and handle the AI there.&lt;/p&gt;

&lt;h4&gt;
  
  
  Authentication &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;At Mathem we use Cognito User Pools for authentication. Since the UI for this this device is just a bump sensor we need to perform a headless login. I perform this the same way as you do &lt;a href="https://www.raspberrypi.org/documentation/configuration/wireless/headless.md" rel="noopener noreferrer"&gt;headless authentication&lt;/a&gt; to your WiFi network, but with a custom file in the boot partition, &lt;code&gt;mh-credentials.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "username": "me@email.com",
    "password": "mypassword"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;make sure to change your Pi's default password and make sure that it's not publicly accessible over the internet&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;/home/pi/camera&lt;/code&gt; we place a python script, camera.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import RPi.GPIO as GPIO
import time
import json
from picamera import PiCamera
from pycognito import Cognito
import requests

userPoolId = 'eu-west-1_abcdefgh'
appClientId = '1unghcf5krf83i76325abcdefg'

credentials = None
with open('/boot/mh-credentials.json') as credentials_file:
    credentials = json.load(credentials_file)

user = Cognito(userPoolId, appClientId, username=credentials['username'])

user.authenticate(password=credentials['password'])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reads the credentials file and uses &lt;a href="https://pypi.org/project/pycognito/" rel="noopener noreferrer"&gt;pycognito&lt;/a&gt; to authenticate the username and password.&lt;/p&gt;

&lt;h4&gt;
  
  
  Components setup &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;Next, we initialize the camera and the bump sensor&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;camera = PiCamera()
camera.rotation = 180  # The camera is fitted on the device upside down, so we need to rotate it
GPIO.setmode(GPIO.BCM) # This means I'm using GPIO numbering instead of board numbering

GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Use GPIO 12
GPIO.add_event_detect(12, GPIO.RISING, callback=capture, bouncetime=3000) # add event listener to bump sensor
while True:
    time.sleep(10)  # Wait for input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above snippet assigns &lt;code&gt;capture()&lt;/code&gt; as a callback to the GPIO event.&lt;/p&gt;

&lt;h4&gt;
  
  
  Capture and upload image&lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def capture(channel):    
    global user

    # Refresh token if expired
    user = Cognito(userPoolId, appClientId, id_token=user.id_token,refresh_token=user.refresh_token, access_token=user.access_token)

    # Set authorization header
    headers = {'authorization': user.id_token, 'content-type': 'image/jpg'}

    # Request presigned URL
    s3url = requests.get('https://api.mathem.io/cam-cart/s3/signedurl', headers=headers)
    contentDict = json.loads(s3url.content)
    signedUrl = contentDict["url"]

    # Take picture
    camera.capture('/tmp/capture.jpg')
    print('Captured image. Uploading...')

    # Upload
    with open('/tmp/capture.jpg', 'rb') as f:
        http_response = requests.put(signedUrl, data=f.read())
        print('Done!')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the S3 key will have the format &lt;code&gt;&amp;lt;userid&amp;gt;/&amp;lt;timestamp&amp;gt;.jpg&lt;/code&gt;. More on that later.&lt;/p&gt;

&lt;p&gt;To make this run on startup, add the following to &lt;code&gt;/etc/rc.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo python3 /home/pi/camera/camera.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Backend architecture &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Already having a 100% serverless architecture made it simple to hook this workflow into the mix.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Farch-diagram.png%3Fv%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Farch-diagram.png%3Fv%3D1" alt="Arch diagram"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;The prerequisite for uploading an image in a secure manner is to obtain a pre-signed URL that allows this. Having already authenticated the device, we can now call an API Gateway endpoint that authorizes the user before invoking the following code. For nodejs we use Jeremy Daly's &lt;a href="https://github.com/jeremydaly/lambda-api" rel="noopener noreferrer"&gt;lambda-api&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function get(req, res) {
  const jwt = req.headers.authorization;
  const claims = jwtDecode(jwt);
  // adding custom claim `userid` to the file name
  const filename = `${claims['custom:userid']}/${new Date().getTime()}.jpg`;
  const url = await s3.getSignedUrlPromise('putObject', {
    Bucket: process.env.Bucket,
    Key: filename,
    Expires: 10 // valid for 10 seconds
  });
  return res.status(200).send({ url });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next we need to act when new images get uploaded. We do this by triggering a lambda function &lt;code&gt;upload-trigger.js&lt;/code&gt; when new objects get created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UploadTrigger:
    Type: 'AWS::Serverless::Function'
    Properties:
        Handler: src/upload-trigger.handler
        Policies:
        - AmazonAPIGatewayInvokeFullAccess
        - Version: 2012-10-17
            Statement:
            - Sid: Statement1
                Effect: Allow
                Action:
                - 'textract:detectDocumentText'
                Resource: '*'
        - S3CrudPolicy:
            BucketName: !Sub ${AWS::AccountId}-${AWS::Region}-cam-cart
        Events:
        S3Event:
            Type: S3
            Properties:
            Bucket: !Ref Bucket
            Events: s3:ObjectCreated:*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Choosing AI service  &lt;a&gt;&lt;/a&gt;
&lt;/h4&gt;

&lt;p&gt;AWS offers two AI services that can recognize hand writing; &lt;a href="https://aws.amazon.com/rekognition/" rel="noopener noreferrer"&gt;Rekognition&lt;/a&gt; and &lt;a href="https://aws.amazon.com/textract/" rel="noopener noreferrer"&gt;Textract&lt;/a&gt;. At first I wasn't aware that Textract had this capabliity, but it turns out it was &lt;a href="https://aws.amazon.com/blogs/machine-learning/amazon-textract-recognizes-handwriting-and-adds-five-new-languages/" rel="noopener noreferrer"&gt;announced&lt;/a&gt; only about a month prior to the time of writing.&lt;/p&gt;

&lt;p&gt;Using the Rekognition API worked ok, but it wasn't great once the hand writing turned scribbly. Also, as you'll see later on, the camera is fitted a bit too close to the paper, so no matter how I adjust the lens, it will always be out of focus on that distance.&lt;/p&gt;

&lt;p&gt;Once I realized Textract had similar capabilities I did some side-by-side comparison and saw that using Textract takes this project a step away from being a fun proof-of-concept and closer to an actually useful device.&lt;/p&gt;

&lt;p&gt;Here follows an example of using the same blurry image in Rekogintion vs. Textract. Since MatHem is currently only offered in Swedish, so are the items on the list:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tr&gt;
    &lt;td colspan="2"&gt;Rekognition&lt;/td&gt;
   &lt;/tr&gt; 
   &lt;tr&gt;
      &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Frekognition.png%3Fv%3D1"&gt;&lt;/td&gt;
      &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Frekognition-zoom.png%3Fv%3D1"&gt;&lt;/td&gt;
  &lt;/tr&gt;
   &lt;tr&gt;
      &lt;td colspan="2"&gt;Pricing&lt;/td&gt;
  &lt;/tr&gt;
   &lt;tr&gt;
      &lt;td colspan="2"&gt;&lt;a href="https://aws.amazon.com/rekognition/pricing/" rel="noopener noreferrer"&gt;$0.001 per image after free tier&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;tr&gt;
    &lt;td colspan="2"&gt;Textract&lt;/td&gt;
   &lt;/tr&gt; 
   &lt;tr&gt;
      &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Ftextract.png%3Fv%3D1"&gt;&lt;/td&gt; 
      &lt;td&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Ftextract-zoom.png%3Fv%3D1"&gt;&lt;/td&gt; 
   &lt;/tr&gt;
&lt;tr&gt;
      &lt;td colspan="2"&gt;Pricing&lt;/td&gt;
  &lt;/tr&gt;
   &lt;tr&gt;
      &lt;td colspan="2"&gt;&lt;a href="https://aws.amazon.com/textract/pricing/" rel="noopener noreferrer"&gt;$0.0015 per document (image) after free tier&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here we can see that Textract nailed all four words while Rekognition really struggled. Now, our product search engine allows for typos, so the end result still often made sense with Rekogintion's results, but Textract was still superior.&lt;/p&gt;

&lt;p&gt;Also - Rekognition just gives a list of words and their coordinates in the image while Textract categorizes the matches into blocks, so for example "bar of soap" would be one search term using Textract, but three separate ones using Textract.&lt;/p&gt;

&lt;p&gt;Pricing is similar and although Textract is charged a bit higher, for our volumes it's neglectable.&lt;/p&gt;

&lt;p&gt;With all that in account and the fact that Textract is the better service name, the choice was simple.&lt;/p&gt;

&lt;h4&gt;
  
  
  upload-trigger.js
&lt;/h4&gt;

&lt;p&gt;When an image is uploaded, we trigger the following Lambda function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.handler = async function (event) {
  const s3Record = event.Records[0].s3;

  const detection = await textract
    .detectDocumentText({
      Document: {
        S3Object: { Bucket: s3Record.bucket.name, Name: s3Record.object.key }
      }
    })
    .promise();

  // Get user id based on the S3 object prefix
  const userId = s3Record.object.key.split('/')[0]; 

  for (const block of detection.Blocks.filter((p) =&amp;gt; p.BlockType === 'LINE')) {
    if (block.Confidence &amp;gt; 80) {
        // call internal APIs to search product and add to cart
    }
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When calling the Textract API you can choose if you want to do it synchronously or in a asynchronous fire-and-forget manner. The latter means that you pass it an SNS topic ARN to which it sends a notification when the job is done. &lt;/p&gt;

&lt;p&gt;Here I went for the synchronous call since these images are small and processed fairly quickly (~2s) and I feel it favours the end-to-end duration.&lt;/p&gt;

&lt;p&gt;That's it for the code. The detected text is passed to internal APIs and the front end is updated using web sockets, but that's out of scope for this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing the case &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The initial proof of concept wasn't presented in a very usability inviting way, so I had to wrap all the wiring up in a sturdy case:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/elFLHO7BnVk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The end result is this wall mounted beauty:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fwallmounted.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fwallmounted.jpg" alt="wall mounted device"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm a noob when it comes to 3D modelling, but it's really easy to get started with Tinkercad.com. &lt;/p&gt;

&lt;p&gt;The case consists of three items;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A case with screw holes for attaching the Raspberry Pi and bump sensor.&lt;/li&gt;
&lt;li&gt;An arm holding the camera. The arm is hollow to allow us to hide the flex cable. This arm would benefit from being longer to get less blurry images, but I was constrained to the 10cm length of the stock cable.&lt;/li&gt;
&lt;li&gt;A wall mount that the inner construction slides in to.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Inner case and arm
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fcase-and-arm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fcase-and-arm.png" alt="Case and arm"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Printed and all wired up it looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fcase-inside.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fcase-inside.jpg" alt="Case inside"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These took ~12 hours to print&lt;/p&gt;
&lt;h4&gt;
  
  
  Outer casing / wall mount
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fwallmount.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fmhlabs%2Ftech-blog%2Fmaster%2Fcart-cam%2Fwallmount.png" alt="Wall mount model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This took an additional 8-9 hours to print. &lt;/p&gt;

&lt;p&gt;The inner casing slides perfectly into the mount and the back of the mount secures the loose cables in place.&lt;/p&gt;
&lt;h2&gt;
  
  
  End result &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Here I have mounted it on the wall in the kitchen next to my Echo Show on which I've used Firefox to browse and log in to &lt;a href="//mathem.se"&gt;mathem.se&lt;/a&gt; for the sake of this demo. I also coated the device with a blue paper to add some friction to when I push the shopping list in.&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/7P1CiuTNRKU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Please get in touch if you have ideas for improvement or other applications for this use case.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>aws</category>
      <category>mathem</category>
      <category>ai</category>
    </item>
    <item>
      <title>A brief summary of our AWS productivity tools</title>
      <dc:creator>Lars Jacobsson</dc:creator>
      <pubDate>Sat, 12 Sep 2020 22:29:37 +0000</pubDate>
      <link>https://dev.to/ljacobsson/a-brief-summary-of-our-aws-productivity-tools-2ofl</link>
      <guid>https://dev.to/ljacobsson/a-brief-summary-of-our-aws-productivity-tools-2ofl</guid>
      <description>&lt;h2&gt;
  
  
  Table of content
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Background - What brought us here?&lt;/li&gt;
&lt;li&gt;
cfn-diagram - CloudFormation visualizer&lt;/li&gt;
&lt;li&gt;
evb-cli - EventBridge pattern generator and debugging suite&lt;/li&gt;
&lt;li&gt;
cfn-resource-actions - VS Code extension that lets you interact with deployed resources from the template&lt;/li&gt;
&lt;li&gt;
sam-policies-cli - CLI UI for browsing and injecting SAM Policy Templates&lt;/li&gt;
&lt;li&gt;
iam-policies-cli - CLI UI for building complex IAM policies&lt;/li&gt;
&lt;li&gt;
cwlogs-cli - Tool to quickly launch CloudWatch Logs Insights with multiple log groups pre-selected&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A background &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When we in January 2017 made the decision to rewrite Sweden's leading online grocery store, &lt;a href="https://mathem.se"&gt;MatHem.se&lt;/a&gt;, from scratch into a serverless microservices architecture we found ourselves in an extremely promising yet premature tech segment with a lack of tooling due to a yet quite small community.&lt;/p&gt;

&lt;p&gt;This led us to adopting a mindset of 'when tasks get repetitive, find a tool for it or write one yourself'.&lt;/p&gt;

&lt;p&gt;Much of our tooling solves bespoke in-house procedural bottlenecks, but when we find solutions to tasks that can benefit the community we make sure to publish it in the open.&lt;/p&gt;

&lt;p&gt;This post is a summary of the AWS/serverless related productivity tools we have authored.&lt;/p&gt;

&lt;h2&gt;
  
  
  cfn-diagram &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;NPM: &lt;a href="https://www.npmjs.com/package/@mhlabs/cfn-diagram"&gt;https://www.npmjs.com/package/@mhlabs/cfn-diagram&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mhlabs/cfn-diagram"&gt;https://github.com/mhlabs/cfn-diagram&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm i -g @mhlabs/cfn-diagram&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;br&gt;
Parses CloudFormation/SAM JSON or YAML and renders a diagram in either draw.io or vis.js network format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pmAqB9y6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-diagram/master/demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pmAqB9y6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-diagram/master/demo.gif" alt="Cfn-diagram-drawio" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZnuffyHB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-diagram/master/demo-html.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZnuffyHB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-diagram/master/demo-html.gif" alt="Cfn-diagram-html" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason for existing&lt;/strong&gt;&lt;br&gt;
Our application is made up of hundreds of microservices, each defined with its own CloudFormation/SAM template. No engineer knows about every stack. Our engineering team has been going through a rapid growth phase which has required onboarding of new developers to be efficient.&lt;/p&gt;

&lt;p&gt;To ease the getting to know a stack, we had an initiative that every repository was required to have an up-to-date diagram over the service's resources in the readme file. No matter how good the intentions were, those sort of 'musts' will never be followed and these diagram will be something that 'might be true' and therefore never trusted.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;cfn-diagram&lt;/code&gt; any developer new to a stack can quickly pull up a visualisation of how it all hangs together. It's also useful to render in planning meetings instead of drawing on the whiteboard&lt;/p&gt;

&lt;h2&gt;
  
  
  evb-cli &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;NPM: &lt;a href="https://www.npmjs.com/package/@mhlabs/evb-cli"&gt;https://www.npmjs.com/package/@mhlabs/evb-cli&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mhlabs/evb-cli"&gt;https://github.com/mhlabs/evb-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm i -g @mhlabs/evb-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;br&gt;
Pattern generator and debugging tool for EventBridge with features including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Event pattern builder &lt;/li&gt;
&lt;li&gt;Input transformation builder&lt;/li&gt;
&lt;li&gt;Event rules browser&lt;/li&gt;
&lt;li&gt;Generate a diagram over the events flow in a region&lt;/li&gt;
&lt;li&gt;Local debugging of events where actual events are sent to the console over websockets. These can be forwarded into sam-local for some advanced debugging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gv4q1gIl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/evb-cli/master/demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gv4q1gIl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/evb-cli/master/demo.gif" alt="Demo1" width="600" height="404"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Pattern generation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lvc57wA7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/evb-cli/master/demo-diagram.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lvc57wA7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/evb-cli/master/demo-diagram.gif" alt="Demo2" width="800" height="500"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Events flow diagram generator&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--clZzCLpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/evb-cli/master/demo-local.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--clZzCLpi--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/evb-cli/master/demo-local.gif" alt="Demo2" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Local debugging&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason for existing&lt;/strong&gt;&lt;br&gt;
Historically we have been heavily invested in pub/sub using SNS/SQS, but we were always annoyed with the tight coupling this created between services and teams, so when EventBridge was announced we got very excited. &lt;/p&gt;

&lt;p&gt;One of the most powerful features of EventBridge is its content based filtering where you only let through events matching a part of the payload that interests the consumer of the event. Together with EventBridge's Schema Registry an engineer can build event patterns without knowing anything about the producing service.&lt;/p&gt;

&lt;p&gt;These event patterns often get quite complex and we found ourselves spending a lot of time first writing, then deploying, then debugging them. Typos were a common mistake both when composing event patterns and input transformations.&lt;/p&gt;

&lt;p&gt;Our developers also reported that they find it cumbersome to debug their rules as well as finding it difficult to see the whole picture of how all events hang together, so from that feedback we added local debugging features&lt;/p&gt;

&lt;h2&gt;
  
  
  cfn-resource-actions &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Marketplace: &lt;a href="https://marketplace.visualstudio.com/items?itemName=ljacobsson.cfn-resource-actions"&gt;https://marketplace.visualstudio.com/items?itemName=ljacobsson.cfn-resource-actions&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mhlabs/cfn-resource-actions"&gt;https://github.com/mhlabs/cfn-resource-actions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ext install ljacobsson.cfn-resource-actions&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;br&gt;
Interact with your deployed CloudFormation/SAM templates directly from the template. Turns your template in to an interface into the AWS console&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tail lambda log&lt;/li&gt;
&lt;li&gt;Query DynamoDB tables&lt;/li&gt;
&lt;li&gt;Copy Physical Id of any resource&lt;/li&gt;
&lt;li&gt;Consume EventBridge events directly in vs code&lt;/li&gt;
&lt;li&gt;Send to and Poll SQS&lt;/li&gt;
&lt;li&gt;Visualise template&lt;/li&gt;
&lt;li&gt;etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dG6cLy-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-lambda.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dG6cLy-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-lambda.gif" alt="Demo1" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Invoke lambda + tail its logs&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--f5Ej7_uA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-f12.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--f5Ej7_uA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-f12.gif" alt="Demo2" width="800" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CTRL+click from template to Lambda handler&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LcSkHUAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-dynamodb.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LcSkHUAI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-dynamodb.gif" alt="Demo3" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Query DynamoDB&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0hBLw21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-visualize.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Y0hBLw21--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cfn-resource-actions/master/images/example-visualize.gif" alt="Demo4" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Visualise stack requires &lt;a href="https://marketplace.visualstudio.com/items?itemName=hediet.vscode-drawio"&gt;Draw.io Integration&lt;/a&gt; extension&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason for existing&lt;/strong&gt;&lt;br&gt;
Context switching is a huge cost in productivity loss. Leaving VS Code to go and do stuff in the AWS console is a distraction we want to avoid&lt;/p&gt;

&lt;h2&gt;
  
  
  sam-policies-cli &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;NPM: &lt;a href="https://www.npmjs.com/package/@mhlabs/sam-policies-cli"&gt;https://www.npmjs.com/package/@mhlabs/sam-policies-cli&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mhlabs/sam-policies-cli"&gt;https://github.com/mhlabs/sam-policies-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm i -g @mhlabs/sam-policies-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;br&gt;
CLI UI to quickly inject SAM policy templates into your &lt;code&gt;AWS::Serverless::Function&lt;/code&gt; resources&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W12k6nP0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/sam-policies-cli/master/demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W12k6nP0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/sam-policies-cli/master/demo.gif" alt="Demo" width="600" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason for existing&lt;/strong&gt;&lt;br&gt;
We strive to follow the Principle of Least Privilege and finding the correct policy templates requires googling&lt;/p&gt;

&lt;h2&gt;
  
  
  iam-policies-cli &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;NPM: &lt;a href="https://www.npmjs.com/package/@mhlabs/iam-policies-cli"&gt;https://www.npmjs.com/package/@mhlabs/iam-policies-cli&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mhlabs/iam-policies-cli"&gt;https://github.com/mhlabs/iam-policies-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm i -g @mhlabs/iam-policies-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;br&gt;
UI to quickly build simple to complex IAM policies based on resources in a CloudFormation template&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CI1UQknl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/iam-policies-cli/master/demo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CI1UQknl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/iam-policies-cli/master/demo.gif" alt="Demo" width="600" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason for existing&lt;/strong&gt;&lt;br&gt;
We strive to follow the Principle of Least Privilege and composing granular IAM policies is time consuming and prone to human errors&lt;/p&gt;

&lt;h2&gt;
  
  
  cwlogs-cli &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;NPM: &lt;a href="https://www.npmjs.com/package/@mhlabs/cwlogs-cli"&gt;https://www.npmjs.com/package/@mhlabs/cwlogs-cli&lt;/a&gt;&lt;br&gt;
GitHub: &lt;a href="https://github.com/mhlabs/cwlogs-cli"&gt;https://github.com/mhlabs/cwlogs-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;npm i -g @mhlabs/cwlogs-cli&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Features&lt;/strong&gt;&lt;br&gt;
Create groups of log groups from resource tags or log group prefixes and launch CloudWatch Logs Insights with those groups predefined&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P2T1np_r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cwlogs-cli/master/demo/demo-tag.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P2T1np_r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://raw.githubusercontent.com/mhlabs/cwlogs-cli/master/demo/demo-tag.gif" alt="Demo" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason for existing&lt;/strong&gt;&lt;br&gt;
CloudWatch Insights Logs is a great way to search your logs. Often you want to search across many log groups to, for example, tracing a correlation id across multiple services. CloudWatch Logs Insights lets you search across up to 20 log groups in one query, but adding log groups from the dropdown is a bit cumbersome.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
