<?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: Alastair McClelland</title>
    <description>The latest articles on DEV Community by Alastair McClelland (@almcc).</description>
    <link>https://dev.to/almcc</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%2F370851%2F0f7b3f5d-1014-4dae-9748-58aa6028cfda.png</url>
      <title>DEV Community: Alastair McClelland</title>
      <link>https://dev.to/almcc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/almcc"/>
    <language>en</language>
    <item>
      <title>An Alternative to MCP - Python CLI-Based Agent Skills</title>
      <dc:creator>Alastair McClelland</dc:creator>
      <pubDate>Tue, 03 Mar 2026 08:13:42 +0000</pubDate>
      <link>https://dev.to/almcc/an-alternative-to-mcp-python-cli-based-agent-skills-43ki</link>
      <guid>https://dev.to/almcc/an-alternative-to-mcp-python-cli-based-agent-skills-43ki</guid>
      <description>&lt;p&gt;I wrote recently about &lt;a href="https://almcc.me/blog/2026/01/26/mcp-tools-vs-skills-with-scripts-why-simpler-might-be-better" rel="noopener noreferrer"&gt;MCP's patchy reliability&lt;/a&gt; and how mature CLI tools wrapped in skills felt more stable. I wanted to explore that idea further — specifically, what if you could build custom Python CLI tools with a skills wrapper and have the same success without managing virtual environments?&lt;/p&gt;

&lt;p&gt;Python-tool-skills are that exploration. They're Python CLI tools built with Click, run via &lt;code&gt;uvx&lt;/code&gt;, and bundled inside skills. No venv pollution, locked dependencies, just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What works: wrapping mature CLI tools
&lt;/h2&gt;

&lt;p&gt;Most CLI tools are battle-tested, predictable, well-documented. Wrapping them in skills works brilliantly. The agent calls a command, parses the output, moves on.&lt;/p&gt;

&lt;p&gt;These tools have been used by thousands of developers across wildly different contexts. Error messages are clear. Edge cases are handled. You're building on solid ground.&lt;/p&gt;

&lt;p&gt;Most importantly, they are a text efficient, well understood textual interface — one well suited to LLMs. With in-context &lt;code&gt;--help&lt;/code&gt; output, the agent can discover capabilities progressively rather than loading full schemas upfront.&lt;/p&gt;

&lt;p&gt;But what if you need a custom tool? Skills today tend towards writing a bash script, but anything beyond basic text parsing gets messy. Python's cleaner, but then you're managing virtual environments, dependencies, import paths.&lt;/p&gt;

&lt;p&gt;Enter uvx — a tool runner that handles Python package isolation automatically, no venv required. Well, not one you have to manage yourself at least! &lt;/p&gt;

&lt;h2&gt;
  
  
  Python-tool-skills: CLI tools bundled in skills
&lt;/h2&gt;

&lt;p&gt;So why don't we try building custom tools the same way — as standalone CLIs that just happen to be written in Python?&lt;/p&gt;

&lt;p&gt;I've put together what I'm calling a python-tool-skill — an AI agent tool packaged as a skill that bundles a Python CLI in its &lt;code&gt;assets/&lt;/code&gt; directory. The CLI is built with &lt;a href="https://click.palletsprojects.com/" rel="noopener noreferrer"&gt;Click&lt;/a&gt; and runs via &lt;a href="https://docs.astral.sh/uv/guides/tools/" rel="noopener noreferrer"&gt;&lt;code&gt;uvx&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can see examples and try it yourself at &lt;a href="https://github.com/almcc/python-tool-skill-creator" rel="noopener noreferrer"&gt;github.com/almcc/python-tool-skill-creator&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;f1/
├── SKILL.md          ← trigger description + agent instructions
└── assets/
    ├── f1.py         ← Click CLI entry point
    ├── pyproject.toml
    └── uv.lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent runs it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvx &lt;span class="nt"&gt;--from&lt;/span&gt; /path/to/skill/assets f1 races
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No venv, no &lt;code&gt;__pycache__&lt;/code&gt;, no &lt;code&gt;.pyc&lt;/code&gt; files. &lt;code&gt;uvx&lt;/code&gt; handles isolation automatically. Dependencies are pinned in a committed &lt;code&gt;uv.lock&lt;/code&gt; — reproducible and offline-capable after first cache population.&lt;/p&gt;

&lt;p&gt;(I chose F1 as a subject because we are very excited for the 2026 season in our house!)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Click
&lt;/h2&gt;

&lt;p&gt;Click is the most mature (and my favourite!) Python CLI framework. It handles argument parsing, option validation, help text generation, error formatting and more. &lt;/p&gt;

&lt;p&gt;A minimal Click CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;click&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlopen&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="nd"&gt;@click.group&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;F1 CLI — Formula 1 data powered by OpenF1.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="nd"&gt;@cli.command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;races&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@click.option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;races_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;List all races for a season with circuit and local times.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;

    &lt;span class="c1"&gt;# Fetch from OpenF1 API
&lt;/span&gt;    &lt;span class="n"&gt;meetings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/meetings?year=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ... format and display race calendar
&lt;/span&gt;    &lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Fetching &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; calendar...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Top-level &lt;code&gt;@click.group()&lt;/code&gt;, sub-commands with &lt;code&gt;@cli.command()&lt;/code&gt;, output via &lt;code&gt;click.echo()&lt;/code&gt;. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why uvx
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; is a fast Python package manager. &lt;code&gt;uvx&lt;/code&gt; is its tool runner — like &lt;code&gt;npx&lt;/code&gt; for Python.&lt;/p&gt;

&lt;p&gt;Point &lt;code&gt;uvx&lt;/code&gt; at a directory with a &lt;code&gt;pyproject.toml&lt;/code&gt;, and it installs the package in an isolated environment and runs it. The environment is cached. Subsequent calls are fast.&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="c"&gt;# First run: installs dependencies, runs command&lt;/span&gt;
uvx &lt;span class="nt"&gt;--from&lt;/span&gt; /path/to/assets f1 races

&lt;span class="c"&gt;# Subsequent runs: uses cached environment&lt;/span&gt;
uvx &lt;span class="nt"&gt;--from&lt;/span&gt; /path/to/assets f1 race 5

&lt;span class="c"&gt;# After editing f1.py: force reinstall&lt;/span&gt;
uvx &lt;span class="nt"&gt;--from&lt;/span&gt; /path/to/assets &lt;span class="nt"&gt;--reinstall&lt;/span&gt; f1 races
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;pip install&lt;/code&gt;, no &lt;code&gt;python -m venv&lt;/code&gt;, no activation scripts. It just works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing it in action
&lt;/h2&gt;

&lt;p&gt;Here's what it looks like when the agent uses the F1 skill:&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="c"&gt;# Agent calls the tool&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;uvx &lt;span class="nt"&gt;--from&lt;/span&gt; /path/to/f1/assets f1 races &lt;span class="nt"&gt;--year&lt;/span&gt; 2026

&lt;span class="c"&gt;# Output: formatted race calendar&lt;/span&gt;
Fetching 2026 calendar...
  2026 Formula 1 Season — 24 Races
  &lt;span class="c"&gt;#  Race                        Circuit               Race Time (Circuit)       Race Time (GMT)         When      &lt;/span&gt;
&lt;span class="nt"&gt;-------------------------------------------------------------------------------------------------------------------&lt;/span&gt;
  1  Australian GP               Melbourne             Sun 08 Mar, 15:00 UTC+11  Sun 08 Mar, 04:00       &lt;span class="k"&gt;in &lt;/span&gt;9d     
  2  Chinese GP                  Shanghai              Sun 15 Mar, 15:00 UTC+8   Sun 15 Mar, 07:00       &lt;span class="k"&gt;in &lt;/span&gt;16d    
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent reads the output, extracts what it needs, and moves on. Same experience as calling and other cli. The fact that it's a custom Python tool is invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works for me
&lt;/h2&gt;

&lt;p&gt;I've been using python-tool-skills for a week or so and they feel significantly more user-friendly than creating custom MCP servers. The biggest win? The tools are just CLI programs. I can run them directly from my terminal, test them independently, and use them outside the agent context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing to MCP servers
&lt;/h2&gt;

&lt;p&gt;MCP servers offer fine-grained control — you can enable and disable specific tools, control exactly what the agent sees. With CLI-based skills, you lose that granularity. You load the skill, the agent gets access to everything the CLI can do.&lt;/p&gt;

&lt;p&gt;But as we trust LLMs more, is this much of a concern? The underlying authentication model of the tool you're calling might be enough. If the tool requires credentials, the agent still can't do anything you haven't authorized. The permission boundary shifts from the agent runtime to the tool itself.&lt;/p&gt;

&lt;p&gt;Of course, the LLM still needs a way to instigate a tool call. That mechanism may be MCP, or some kind of custom tool schema specific to that agent runtime. The proposal here isn't about replacing those mechanisms — it's about shifting the interface at which we (the community) build our tooling. Rather than building custom protocols and servers, we build portable CLI tools that any agent can wrap.&lt;/p&gt;

&lt;p&gt;I do wonder if we'll eventually see a shell dedicated for LLMs — a safer sandbox in which they can work. Something that provides better guardrails, audit logging, and resource limits than a standard shell, while maintaining the familiar CLI interface pattern. &lt;/p&gt;

&lt;h2&gt;
  
  
  The cost advantage
&lt;/h2&gt;

&lt;p&gt;There's another compelling reason to prefer CLI tools: they're dramatically cheaper to use. As &lt;a href="https://kanyilmaz.me/2026/02/23/cli-vs-mcp.html" rel="noopener noreferrer"&gt;documented by Kan Yilmaz&lt;/a&gt;, CLI-based tools use ~94% fewer tokens than equivalent MCP servers through progressive disclosure — the agent only loads tool details when it needs them via &lt;code&gt;--help&lt;/code&gt;, rather than loading complete JSON schemas for every tool upfront.&lt;/p&gt;

&lt;p&gt;With 84 tools loaded, MCP burns ~15,540 tokens at session start. The same tools via CLI? ~300 tokens. The agent discovers what it needs on-demand, keeping context focused and costs low.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond Python
&lt;/h2&gt;

&lt;p&gt;Of course, this pattern isn't limited to Python. You could bundle Ruby tools with &lt;code&gt;gem install&lt;/code&gt;, Node.js tools with &lt;code&gt;npx&lt;/code&gt;, Go binaries, Rust crates — anything that can run as a CLI and be distributed in a portable way. Python-tool-skills just happen to be the convenient starting point I chose. &lt;/p&gt;

&lt;h2&gt;
  
  
  The future of agent tooling
&lt;/h2&gt;

&lt;p&gt;Sentry clearly agrees that CLI tools are the way forward — they've published &lt;a href="https://docs.sentry.io/ai/sentry-cli/" rel="noopener noreferrer"&gt;official agent tooling documentation&lt;/a&gt; centered around their CLI, not MCP. Will we see skills become a new distribution method for application tooling? Package your CLI, add usage instructions, ship it as a skill. Developers get tools they can use directly. Agents get tools they can discover progressively. Everyone wins. &lt;/p&gt;

</description>
      <category>agents</category>
      <category>python</category>
      <category>cli</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Getting into Serverless with CDK, Lambda &amp; DynamoDB - Part 2</title>
      <dc:creator>Alastair McClelland</dc:creator>
      <pubDate>Wed, 27 May 2020 05:41:39 +0000</pubDate>
      <link>https://dev.to/almcc/getting-into-serverless-with-cdk-lambda-dynamodb-part-2-5g73</link>
      <guid>https://dev.to/almcc/getting-into-serverless-with-cdk-lambda-dynamodb-part-2-5g73</guid>
      <description>&lt;p&gt;In this second post, we are going to add a DynamoDB Table to our stack for our lambda function to pull data from. See &lt;a href="https://dev.to/almcc/getting-into-serverless-with-cdk-lambda-dynamodb-part-1-3g7c"&gt;Part 1&lt;/a&gt; on getting started to get caught up.&lt;/p&gt;

&lt;p&gt;Before we go too far, you are going to need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python 3.8 with Pip installed&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html" rel="noopener noreferrer"&gt;AWS SAM CLI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1 - Defining the table.
&lt;/h2&gt;

&lt;p&gt;Install the DynamoDB CDK module, being careful to use the same release you used before.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-cdk/aws-dynamodb@1.33.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And add it to our imports in &lt;code&gt;lib/myapp-stack.ts&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;import * as dynamodb from "@aws-cdk/aws-dynamodb";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we can add a DynamoDB &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-dynamodb.Table.html" rel="noopener noreferrer"&gt;Table&lt;/a&gt; construct to our stack. DynamoDB is a no-sql database and requires you to think a little bit about how your data will be accessed. Read about the key concepts over &lt;a href="https://www.dynamodbguide.com/key-concepts/" rel="noopener noreferrer"&gt;here&lt;/a&gt; on &lt;a href="https://www.dynamodbguide.com/" rel="noopener noreferrer"&gt;dynamodbguide.com&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tourfinishersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TourFinishersTable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;partitionKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Year&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUMBER&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;sortKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Position&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AttributeType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NUMBER&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TourFinishers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;billingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BillingMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PAY_PER_REQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;removalPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have chosen in this instance to make the &lt;code&gt;Year&lt;/code&gt; the primary key and &lt;code&gt;Position&lt;/code&gt; the sort key, together this will be a unique value within this simple context a high cardinality. It will support our access pattern later as will.&lt;/p&gt;

&lt;p&gt;Next step is to grant our function read access to the table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;tourfinishersTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grantReadData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think this is one of the most powerful features of CDK, it makes dealing with permissions so easy that you don't feel the need to grant overly permissive policies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 - Updating out function
&lt;/h2&gt;

&lt;p&gt;Our python function is going to be a little more complex than before and will require some dependencies. Therefore we first need to move it into its own &lt;code&gt;src&lt;/code&gt; directory.&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="nb"&gt;mkdir &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;/src
&lt;span class="nb"&gt;mv &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;/index.py &lt;span class="k"&gt;function&lt;/span&gt;/src/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we will want to add a requreiments.txt file at &lt;code&gt;function/src/requirements.txt&lt;/code&gt; and add the following dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;boto3&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;1.12.43
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will write a &lt;code&gt;build.sh&lt;/code&gt; script at the base of our project to build are code bundle. AWS Lambda's require a code bundle to come pre-packaged with all its dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash
&lt;/span&gt;
&lt;span class="err"&gt;mkdir&lt;/span&gt; &lt;span class="err"&gt;-p&lt;/span&gt; &lt;span class="err"&gt;function/_bundle&lt;/span&gt;
&lt;span class="err"&gt;pip3.8&lt;/span&gt; &lt;span class="err"&gt;install&lt;/span&gt; &lt;span class="err"&gt;-r&lt;/span&gt; &lt;span class="err"&gt;function/src/requirements.txt&lt;/span&gt; &lt;span class="err"&gt;--target&lt;/span&gt; &lt;span class="err"&gt;function/_bundle&lt;/span&gt;
&lt;span class="err"&gt;cp&lt;/span&gt; &lt;span class="err"&gt;-r&lt;/span&gt; &lt;span class="err"&gt;function/src/*&lt;/span&gt; &lt;span class="err"&gt;function/_bundle&lt;/span&gt;
&lt;span class="err"&gt;pushd&lt;/span&gt; &lt;span class="err"&gt;function/_bundle&lt;/span&gt;
&lt;span class="err"&gt;zip&lt;/span&gt; &lt;span class="err"&gt;-r&lt;/span&gt; &lt;span class="err"&gt;--quiet&lt;/span&gt; &lt;span class="err"&gt;../bundle.zip&lt;/span&gt; &lt;span class="err"&gt;.&lt;/span&gt;
&lt;span class="err"&gt;popd&lt;/span&gt;
&lt;span class="err"&gt;rm&lt;/span&gt; &lt;span class="err"&gt;-rf&lt;/span&gt; &lt;span class="err"&gt;function/_bundle&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we can update our code &lt;code&gt;function/src/index.py&lt;/code&gt; to pull the data from dynamoDB.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;import boto3
from boto3.dynamodb.conditions import Key

logger &lt;span class="o"&gt;=&lt;/span&gt; logging.getLogger&lt;span class="o"&gt;()&lt;/span&gt;
logger.setLevel&lt;span class="o"&gt;(&lt;/span&gt;logging.DEBUG&lt;span class="o"&gt;)&lt;/span&gt;

dynamodb &lt;span class="o"&gt;=&lt;/span&gt; boto3.resource&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dynamodb'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
table &lt;span class="o"&gt;=&lt;/span&gt; dynamodb.Table&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'TourFinishers'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

def main&lt;span class="o"&gt;(&lt;/span&gt;event, context&lt;span class="o"&gt;)&lt;/span&gt;:
    logger.info&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Start of function"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

    response &lt;span class="o"&gt;=&lt;/span&gt; table.query&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;KeyConditionExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Key&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Year'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;.eq&lt;span class="o"&gt;(&lt;/span&gt;2019&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"statusCode"&lt;/span&gt;: 200,
        &lt;span class="s2"&gt;"body"&lt;/span&gt;: response[&lt;span class="s2"&gt;"Items"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;,
        &lt;span class="s2"&gt;"headers"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"Content-Type"&lt;/span&gt;: &lt;span class="s2"&gt;"application/json"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we run &lt;code&gt;./build.sh&lt;/code&gt; to build are bundle and then we can point CDK at our code bundle by updating the &lt;code&gt;code&lt;/code&gt; property of our lambda in &lt;code&gt;lib/myapp-stack.ts&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../function/bundle.zip&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 - Deploy the changes
&lt;/h2&gt;

&lt;p&gt;Simply build and deploy are changes to AWS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;run&lt;/span&gt; &lt;span class="nx"&gt;build&lt;/span&gt;
&lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="nx"&gt;deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To speed things along for this demo, I have a gist that has the top 3 winners of the last 3 Tours that we can import into our table in AWS directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;aws&lt;/span&gt; &lt;span class="nx"&gt;dynamodb&lt;/span&gt; &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;write&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//gist.githubusercontent.com/almcc/1437d07e5f344bb8a5b03376a9473af2/raw/a96c545774c5a046b8e7b32e597ded0f7a84306c/TourFinishers.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 4 - Execute to lambda
&lt;/h2&gt;

&lt;p&gt;Now to run your function in AWS we can use the AWS command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; MyappStack-MyFunction3BAA72D1-1H78B5LRDK3NI &lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; | jq&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--log-type&lt;/span&gt; Tail | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .LogResult | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see something a bit like this as the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"RiderNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Egan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bernal"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"TeamName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Team INEOS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"RiderNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Geraint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Thomas"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"TeamName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Team INEOS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"RiderNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;81&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Position"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"FirstName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Steven"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"LastName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kruijswijk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"TeamName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Team Jumbo-Visma"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus Step - Running the function locally
&lt;/h2&gt;

&lt;p&gt;You can also run the function locally with the AWS sam CLI which can be handy during development.&lt;/p&gt;

&lt;p&gt;The first thing to do is to get hold of the CloudFormation template that CDK is creating behind the scenes for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk synth &lt;span class="nt"&gt;--no-staging&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; template.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can use the &lt;code&gt;sam&lt;/code&gt; CLI to invoke your function. (Check the contents of &lt;code&gt;template.yaml&lt;/code&gt; for the functions name)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;invoke MyFunction3BAA72D1 &lt;span class="nt"&gt;--no-event&lt;/span&gt; | jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run your code locally, using your local credentials to query the live table in AWS.&lt;/p&gt;

&lt;p&gt;Then if you make any changes to you code you will need to run &lt;code&gt;./build.sh&lt;/code&gt; again, if you make a change to your template, you will need to do the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
cdk synth &lt;span class="nt"&gt;--no-staging&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; template.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Job done!
&lt;/h2&gt;

&lt;p&gt;That's it for this post, Next time we will look at adding the API gateway.&lt;/p&gt;

&lt;p&gt;If you would like to clean up everything you have created in AWS, see Step 6 - &lt;em&gt;Cleaning up&lt;/em&gt; from the &lt;a href="https://dev.to/almcc/getting-into-serverless-with-cdk-lambda-dynamodb-part-1-3g7c"&gt;first post in this series&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>tutorial</category>
      <category>serverless</category>
      <category>devops</category>
    </item>
    <item>
      <title>Getting into Serverless with CDK, Lambda &amp; DynamoDB - Part 1</title>
      <dc:creator>Alastair McClelland</dc:creator>
      <pubDate>Tue, 21 Apr 2020 18:35:49 +0000</pubDate>
      <link>https://dev.to/almcc/getting-into-serverless-with-cdk-lambda-dynamodb-part-1-3g7c</link>
      <guid>https://dev.to/almcc/getting-into-serverless-with-cdk-lambda-dynamodb-part-1-3g7c</guid>
      <description>&lt;p&gt;AWS Cloud Development Kit (CDK) is a great tool for using code to deploy resources to AWS. In a series of posts, we will cover a few of the key concepts you need to be aware of getting started with AWS and CDK. We will create a Lambda Function that returns some data from a DynamoDB table over an API Gateway.&lt;/p&gt;

&lt;p&gt;CDK allows you to define &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/constructs.html" rel="noopener noreferrer"&gt;Constructs&lt;/a&gt; in code that map to AWS components or resources, you can then collect those Constructs together into a &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/stacks.html" rel="noopener noreferrer"&gt;Stack&lt;/a&gt; which is what we deploy to an AWS region. Finally, a Stack is defined within an &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/apps.html" rel="noopener noreferrer"&gt;App&lt;/a&gt; which is the root of our project, it can contain many instances of the same stack for say multiple regions or multiple different stack instances.&lt;/p&gt;

&lt;p&gt;You can choose to develop CDK projects with TypeScipt, JavaScript, Python Java or C#. While initially learning CDK I went for Python as it is usually my language of choice, I have later found TypeScript to be much more comfortable as there is a wealth of examples already out there. We will use TypeScript for this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Many tutorials and guides on learning AWS will suggest using the web console to setup and configure resources, however, beyond simple examples I have felt uncomfortable building so much in an unrepeatable fashion. If you are someone who learns by doing, then a pattern I have found works really well is building a setup partially with CDK, then using the web console to learn and experiment further before refactoring those changes back into the CDK stack itself. Simply rinse and repeat to develop ever bigger or more bespoke setups. Having AWS best practice built into the CDK constructs is a comforting safety net.&lt;/p&gt;

&lt;p&gt;So that is already too many words, let's get started.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;You have the &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt; installed and configured, this post was created using V1 of the CLI however both V1 and V2 should work. The &lt;code&gt;aws configure list&lt;/code&gt; command is a good way to test if you have your credentials set up correctly. For those of you who use profiles with the &lt;code&gt;--profile&lt;/code&gt; flag, the &lt;code&gt;cdk&lt;/code&gt; command works in the same way,&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1, Install the CDK CLI.
&lt;/h2&gt;

&lt;p&gt;Assuming you have &lt;a href="https://nodejs.org/en/" rel="noopener noreferrer"&gt;node&lt;/a&gt; already installed, you just now need TypeScript and the CDK CLI&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; typescript
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; aws-cdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2, Create our project.
&lt;/h2&gt;

&lt;p&gt;Let's make a directory and initialise a typescript app project.&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="nb"&gt;mkdir &lt;/span&gt;myapp
&lt;span class="nb"&gt;cd &lt;/span&gt;myapp/
cdk init app &lt;span class="nt"&gt;--language&lt;/span&gt; typescript
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a quick test that it builds ok.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3, The Lambda Function
&lt;/h2&gt;

&lt;p&gt;Install the lambda package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @aws-cdk/aws-lambda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the dependency to our stack (&lt;code&gt;lib/myapp-stack.ts&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@aws-cdk/aws-lambda&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyappStack&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// The code that defines your stack goes here&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to write some function lambda code. Create a file inside a newly created function directory (&lt;code&gt;function/index.py&lt;/code&gt;). In this case, we are just going to return the top 3 finishers of the 2019 Tour de France, later we will work on returning their names from a DynamoDB table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Start of function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Egan Bernal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Geraint Thomas&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Steven Kruijswijk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that I have also set up a logger with a simple logging statement, this will be useful later to demonstrate how we retrieve those logs.&lt;/p&gt;

&lt;p&gt;Now we need to define our lambda function in our stack, &lt;a href="https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_aws-lambda.Function.html" rel="noopener noreferrer"&gt;we can use the &lt;code&gt;Function&lt;/code&gt; construct from the &lt;code&gt;aws-lambda&lt;/code&gt; package.&lt;/a&gt; Inside the &lt;code&gt;MyappStack&lt;/code&gt; construct in &lt;code&gt;lib/myapp-stack.ts&lt;/code&gt; add the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MyFunction&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PYTHON_3_8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;index.main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to add &lt;code&gt;import * as path from "path";&lt;/code&gt; as well.&lt;/p&gt;

&lt;p&gt;You can see that I have defined a function imaginatively named &lt;code&gt;MyFunction&lt;/code&gt;, it uses the Python 3.8 runtime, it's handler points to &lt;code&gt;main&lt;/code&gt; function inside the &lt;code&gt;[index.py](http://index.py)&lt;/code&gt; module and finally, the code lives in the &lt;code&gt;function&lt;/code&gt; directory which is in the parent directory to our &lt;code&gt;lib&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;As it stands, this is enough for us to deploy our code. However, to invoke our function later we are going to need the function name that is created. CDK will take our &lt;code&gt;MyFunction&lt;/code&gt; name and &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/identifiers.html" rel="noopener noreferrer"&gt;add's to it to make sure that it's unique&lt;/a&gt;. To extract that name of the function that will be created in AWS we can add a CloudFormation Output to our stack.&lt;/p&gt;

&lt;p&gt;Again in &lt;code&gt;lib/myapp-stack.ts&lt;/code&gt; and the following code under our lambda function declaration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FunctionNameOutput&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myFunction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;exportName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Lambda-Function-Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note here that the &lt;code&gt;exportName&lt;/code&gt; is the identifier for how we will reference the value and it &lt;strong&gt;must&lt;/strong&gt; be unique across your entire AWS account, regardless of the stack. You can also see the reason we defined our function to a variable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4, Build and Deploy
&lt;/h2&gt;

&lt;p&gt;Let's quickly check that our project builds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also check that CDK can successfully convert (or synthesise) are code into a CloudFormation template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk synth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tend to pipe the output of &lt;code&gt;cdk synth&lt;/code&gt; to less as output is long and it's useful to have a way to search it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cdk synth | less
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all that worked then we are almost good to go. If this is the first time out have deployed a CDK project to your aws account you will need to &lt;a href="https://docs.aws.amazon.com/cdk/latest/guide/tools.html" rel="noopener noreferrer"&gt;bootstrap you account&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk bootstrap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to deploy our stack.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You will be asked to approve any IAM policies and statements that CDK is creating. This is also a good insight into how policies are stitched together in AWS as well as a good reminder of the heavy lifting that CDK is doing for you.&lt;/p&gt;

&lt;p&gt;When making changes to your project and deploying again, it can be useful to chain the build command and the deploy command together to avoid deploying a stale stack without your changes and then scratching your head as to why you change didn't work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cdk deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As I have used &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;cdk deploy&lt;/code&gt; will only run if the previous command (the build command) succeeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5, Invoking our Function
&lt;/h2&gt;

&lt;p&gt;In the output of the deploy command, you should see our CloudFormation Output. It will look a little like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Outputs:
MyappStack.FunctionNameOutput = MyappStack-MyFunction3BAA72D1-173G8DYDM9QA7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a function that we are now ready to invoke. We can do this with the &lt;code&gt;aws lambda invoke&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; MyappStack-MyFunction3BAA72D1-173G8DYDM9QA7 out.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will invoke our function and push the output to &lt;code&gt;out.txt&lt;/code&gt; which should look 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;{"statusCode": 200, "body": ["Egan Bernal", "Geraint Thomas", "Steven Kruijswijk"], "headers": {"Content-Type": "application/json"}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you also want to see the logging output you can use the &lt;code&gt;--log-type Tail&lt;/code&gt; option and it will include the LogResult in the function invocation response that was printed to the terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; MyappStack-MyFunction3BAA72D1-173G8DYDM9QA7 out.txt &lt;span class="nt"&gt;--log-type&lt;/span&gt; Tail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, this will be in base64. You can paste it into your favourite base64 decoder to view the logs. For example, you could use the &lt;code&gt;base64&lt;/code&gt; command.&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="nb"&gt;echo&lt;/span&gt; &amp;lt;insert-base64-content-here&amp;gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get some output that looks a little 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;START RequestId: 1fc4012a-3963-449b-8085-347e0a12e961 Version: $LATEST
[INFO]  2020-04-21T05:47:14.519Z        1fc4012a-3963-449b-8085-347e0a12e961    Start of function
END RequestId: 1fc4012a-3963-449b-8085-347e0a12e961
REPORT RequestId: 1fc4012a-3963-449b-8085-347e0a12e961  Duration: 1.15 ms       Billed Duration: 100 ms Memory Size: 128 MB     Max Memory Used: 49 MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Below I get a little sidetracked with command line tricks, if you are not a fan of the one-liner bash commands please feel free to skip ahead, otherwise make sure you have the &lt;code&gt;jq&lt;/code&gt; command installed and read on!&lt;/p&gt;

&lt;p&gt;We could also use some &lt;code&gt;jq&lt;/code&gt; trickery to do that on the command line with the &lt;code&gt;base64&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; MyappStack-MyFunction3BAA72D1-173G8DYDM9QA7 out.txt &lt;span class="nt"&gt;--log-type&lt;/span&gt; Tail | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .LogResult | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While we are at it, it can be cumbersome to always be having to peek inside the &lt;code&gt;out.txt&lt;/code&gt; file to see the output. We can avoid creating that file all together with the use of a process substitution that will work in most shells. We can also use &lt;code&gt;jq&lt;/code&gt; again to clean up the output. We simply replace &lt;code&gt;out.txt&lt;/code&gt; with &lt;code&gt;&amp;gt;(cat | jq)&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;aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; MyappStack-MyFunction3BAA72D1-173G8DYDM9QA7 &lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; | jq&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--log-type&lt;/span&gt; Tail | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .LogResult | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now see the output of your lambda function printed cleanly to the terminal followed by any logging.&lt;/p&gt;

&lt;p&gt;Finally, while we are down this rabbit hole, could we pull out the function name from CloudFormation programmatically so that you can just copy and paste the command? Well, yes we could.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws lambda invoke &lt;span class="nt"&gt;--function-name&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;aws cloudformation list-exports &lt;span class="nt"&gt;--query&lt;/span&gt; &lt;span class="s1"&gt;'Exports[?Name==`Lambda-Function-Name`].Value'&lt;/span&gt; &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; | jq&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--log-type&lt;/span&gt; Tail | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .LogResult | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 6, Cleaning up
&lt;/h2&gt;

&lt;p&gt;We have covered quite a bit there already and your at the point where you can experiment further on your own. In the next post, we will look at building a DynamoDB table and querying our data.&lt;/p&gt;

&lt;p&gt;You could choose to leave your stack in place, AWS gives you 1 Million free lambda invocations per month and the S3 Bucket created by CDK will be free if your account is less than 12 months old.&lt;/p&gt;

&lt;p&gt;However, if you would like to destroy your stack you can do the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cdk destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you would like to remove the CDKToolkit stack you will need to use the &lt;code&gt;aws&lt;/code&gt; command to get the name of the cdk bucket (or use the web console)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can then empty the bucket and delete the stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://cdktoolkit-stagingbucket-19rslbj9gvcos &lt;span class="nt"&gt;--recursive&lt;/span&gt;
aws cloudformation delete-stack &lt;span class="nt"&gt;--stack-name&lt;/span&gt; CDKToolkit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>aws</category>
      <category>serverless</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
