<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Ivan Bliskavka</title>
  
  <subtitle>Digital Inventor</subtitle>
  <link href="https://bliskavka.com/atom.xml" rel="self"/>
  
  <link href="https://bliskavka.com/"/>
  <updated>2026-06-11T13:44:18.173Z</updated>
  <id>https://bliskavka.com/</id>
  
  <author>
    <name>Ivan Bliskavka</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Amazon Connect AI Call Center Builder</title>
    <link href="https://bliskavka.com/2026/06/11/amazon-connect-ai-call-center-builder/"/>
    <id>https://bliskavka.com/2026/06/11/amazon-connect-ai-call-center-builder/</id>
    <published>2026-06-11T14:00:00.000Z</published>
    <updated>2026-06-11T13:44:18.173Z</updated>
    
    <content type="html"><![CDATA[<p>There’s a workshop floating around that generates Amazon Connect contact flows and CloudFormation from a conversational UI powered by Claude Opus.</p><span id="more"></span><p>The <a href="https://sukwonie.gitbook.io/amazon-connect-aicc-builder-agent-workshop/gWzCDnQYz8mQUQ0GtYa4/en">workshop</a> is built on GitBook, so you can actually ask the LLM questions and it’ll surface answers from the workshop docs.</p><p>The <a href="https://github.com/aws-samples/sample-aicc-builder-for-amazon-connect-ai-agent">source code</a> deploys a web app that walks you through questions about your call center design (menus, intents, tools) and generates the contact flows and backing infrastructure.</p><p>I built a retirement fund sample: callers can check their balance, ask about the next payment date, order a 1099, or request special forms. The tool scaffolded the Bedrock agent, wired up the tools, and generated the Lambda stubs. Not bad for a few minutes of clicking through a form.</p><h2 id="Where-it-broke"><a href="#Where-it-broke" class="headerlink" title="Where it broke"></a>Where it broke</h2><p>The generated CloudFormation had the wrong Lambda handler entry point. Easy fix once you know what you’re looking for, but it took a debugging pass to find it.</p><p>If you’re starting fresh, paste the error and the CloudFormation into an LLM and it’ll find it in seconds.</p><h2 id="Why-look-at-it"><a href="#Why-look-at-it" class="headerlink" title="Why look at it"></a>Why look at it</h2><p>Even if you never deploy it, read the prompts. The way it structures the agent definition and maps intents to tools is a concrete example of how a production Connect + Bedrock setup actually gets wired together. Good working examples of that are hard to come by.</p><p>For POCs and small call centers, it’s a solid starting point. For a long-running production project, you’d outgrow the scaffolding fast. But for standing up a demo or validating an architecture, it does the job.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;There’s a workshop floating around that generates Amazon Connect contact flows and CloudFormation from a conversational UI powered by Claude Opus.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
    <category term="Amazon Connect" scheme="https://bliskavka.com/tags/Amazon-Connect/"/>
    
    <category term="AI" scheme="https://bliskavka.com/tags/AI/"/>
    
    <category term="Claude" scheme="https://bliskavka.com/tags/Claude/"/>
    
  </entry>
  
  <entry>
    <title>Avoid AI Writing</title>
    <link href="https://bliskavka.com/2026/06/11/avoid-ai-writing/"/>
    <id>https://bliskavka.com/2026/06/11/avoid-ai-writing/</id>
    <published>2026-06-11T14:00:00.000Z</published>
    <updated>2026-06-11T13:52:30.982Z</updated>
    
    <content type="html"><![CDATA[<p>A colleague pointed me at a Claude Code skill that strips AI-isms out of generated text. I needed it.</p><span id="more"></span><p>My blog workflow: I have a vague idea, I run <code>/post</code> in my blog repo, and Claude drafts something. Then I proofread it. The problem is the output is loaded with AI tells — too many transition phrases, filler words, and inflated sentence structures that aren’t how I talk or write.</p><p>“It’s worth noting that…”, “This is particularly useful when…”, “Let’s dive in.” None of that is me.</p><p>The <a href="https://github.com/conorbronsdon/avoid-ai-writing/blob/main/README.md">avoid-ai-writing</a> skill audits generated text and rewrites it to remove those patterns. You can run it in detect-only mode to see what’s wrong, or have it edit in place. There’s also a voice profile option if you want to tune the output toward casual, professional, blunt, etc.</p><p>I set it to run on my drafts after <code>/post</code> generates them. The diff is usually significant.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;A colleague pointed me at a Claude Code skill that strips AI-isms out of generated text. I needed it.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="Writing" scheme="https://bliskavka.com/tags/Writing/"/>
    
    <category term="Claude" scheme="https://bliskavka.com/tags/Claude/"/>
    
    <category term="Claude Code" scheme="https://bliskavka.com/tags/Claude-Code/"/>
    
  </entry>
  
  <entry>
    <title>Who Touched My Flow!?</title>
    <link href="https://bliskavka.com/2026/06/03/who-touched-my-flow/"/>
    <id>https://bliskavka.com/2026/06/03/who-touched-my-flow/</id>
    <published>2026-06-03T14:00:00.000Z</published>
    <updated>2026-06-03T20:06:10.170Z</updated>
    
    <content type="html"><![CDATA[<p>A client called me out — users were overwriting each other’s contact flows, and the pipeline got blamed.</p><span id="more"></span><p>The pipeline hadn’t run in days, but that didn’t matter. My job was to figure out who actually made the change.</p><p>Here’s what I think happened:</p><ul><li>Sally is building a new feature in dev — not ready for production</li><li>John needs to test a hotfix, so he exports the prod flow and imports it into dev</li><li>John validates in dev, replicates the fix in prod, moves on</li><li>Sally comes back and finds all her work gone</li></ul><p>Sally <em>can</em> restore from the flow’s version history. But who caused the overwrite?</p><h2 id="The-Easy-Way-I-Should-Have-Known-This"><a href="#The-Easy-Way-I-Should-Have-Known-This" class="headerlink" title="The Easy Way (I Should Have Known This)"></a>The Easy Way (I Should Have Known This)</h2><p>Right there on the Contact Flow List page is a link: <strong>“View Historical Changes”</strong>. You can filter by date and time and see exactly who made each change.</p><p>And on the flow details page, if you select an older version and switch to the <strong>Details</strong> tab, it shows you the user who saved that version.</p><p>I went straight to CloudTrail. The answer was sitting in the UI the whole time. I’ve spent way too much time in code and not enough in the Connect console.</p><h2 id="The-CloudTrail-Way"><a href="#The-CloudTrail-Way" class="headerlink" title="The CloudTrail Way"></a>The CloudTrail Way</h2><p>If you need an API-based approach — or if you’re already in CloudTrail for other reasons — here’s how to read the events.</p><h3 id="Changes-Made-via-API-or-CLI"><a href="#Changes-Made-via-API-or-CLI" class="headerlink" title="Changes Made via API or CLI"></a>Changes Made via API or CLI</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;userIdentity&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AssumedRole&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;arn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:sts::999999999999:assumed-role/developer-sso-role/user@example.com&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The email is embedded in the role ARN as the <strong>Role Session Name</strong>. Easy.</p><h3 id="Changes-Made-via-the-Connect-Console"><a href="#Changes-Made-via-the-Connect-Console" class="headerlink" title="Changes Made via the Connect Console"></a>Changes Made via the Connect Console</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;userIdentity&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AssumedRole&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;arn&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:sts::999999999999:assumed-role/AWSServiceRoleForAmazonConnect_DwT0YgEuWX9uaWsPKbOu/78adbca8-17ff-47dd-b354-2405d9371018&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The Role Session Name here is an internal Connect user ID, not an email. To resolve it:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">aws connect describe-user --user-id 78adbca8-17ff-47dd-b354-2405d9371018 --instance-id &lt;your-instance-id&gt;</span><br></pre></td></tr></table></figure><h3 id="Emergency-Access"><a href="#Emergency-Access" class="headerlink" title="Emergency Access"></a>Emergency Access</h3><p>If <code>describe-user</code> returns nothing, the user logged in with Emergency Access. You can’t identify them directly from the flow change event, but you can search CloudTrail for who used the feature:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;userIdentity&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AssumedRole&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;principalId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AROA3LF2EZF67TQBAWQ23:user@example.com&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventSource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;connect.amazonaws.com&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;eventName&quot;</span><span class="punctuation">:</span> <span class="string">&quot;AdminGetEmergencyAccessToken&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The <code>principalId</code> contains the email of whoever requested emergency access.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;A client called me out — users were overwriting each other’s contact flows, and the pipeline got blamed.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Amazon Connect" scheme="https://bliskavka.com/tags/Amazon-Connect/"/>
    
    <category term="CloudTrail" scheme="https://bliskavka.com/tags/CloudTrail/"/>
    
    <category term="Audit" scheme="https://bliskavka.com/tags/Audit/"/>
    
  </entry>
  
  <entry>
    <title>Stop Returning Status Codes from Your Amazon Connect Lambdas</title>
    <link href="https://bliskavka.com/2026/05/18/amazon-connect-lambda-observability/"/>
    <id>https://bliskavka.com/2026/05/18/amazon-connect-lambda-observability/</id>
    <published>2026-05-18T04:00:00.000Z</published>
    <updated>2026-05-18T19:40:07.440Z</updated>
    
    <content type="html"><![CDATA[<p>I keep seeing the same pattern in Amazon Connect implementations: Lambda functions that return HTTP-style status codes — 200 for success, 400 for validation errors, 404 for not found, 500 for unhandled exceptions.</p><span id="more"></span><p>It feels familiar, but it’s the wrong tool for this job.</p><h2 id="The-Problem"><a href="#The-Problem" class="headerlink" title="The Problem"></a>The Problem</h2><p>When your Lambda returns a response object — any response object — Amazon Connect sees a successful invocation. There’s no Lambda error. No CloudWatch alarm fires. No metric spikes. From the platform’s perspective, everything is fine.</p><p>Now your contact flow has to do the detective work. You add a “Check Contact Attributes” block, branch on <code>$.External.statusCode</code>, wire up paths for 200, 400, 404, 500… and suddenly a flow that should be simple is a maze.</p><p>And let’s be honest — how often does the IVR actually handle all those codes differently? Usually there’s a “happy path” for 200 and everything else hits a generic error message. You’ve added complexity without adding value, and you’ve buried your errors where no one can see them.</p><h2 id="What-to-Do-Instead"><a href="#What-to-Do-Instead" class="headerlink" title="What to Do Instead"></a>What to Do Instead</h2><p>Let the Lambda throw.</p><p>If there’s an unhandled exception or a validation error, don’t swallow it. You can still use a global try&#x2F;catch to log structured details before you give up:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Logger</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;@aws-lambda-powertools/logger&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> logger = <span class="keyword">new</span> <span class="title class_">Logger</span>(&#123; <span class="attr">serviceName</span>: <span class="string">&#x27;customer-lookup&#x27;</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">handler</span> = <span class="keyword">async</span> (<span class="params">event: ConnectContactFlowEvent</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// your logic here</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">await</span> <span class="title function_">lookupCustomer</span>(event);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    logger.<span class="title function_">error</span>(<span class="string">&#x27;Unhandled error&#x27;</span>, &#123; err &#125;);</span><br><span class="line">    <span class="keyword">throw</span> err; <span class="comment">// let it propagate</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>The Lambda fails. CloudWatch logs the structured error. Your Lambda error metrics reflect reality. CloudWatch Insights lets you query across invocations to see exactly what’s going wrong and how often.</p><p>Your contact flow stays clean: there’s a success branch and an error branch. The error branch handles the situation gracefully — a fallback message, a queue transfer, whatever makes sense — and you move on.</p><h2 id="The-One-Legitimate-Exception"><a href="#The-One-Legitimate-Exception" class="headerlink" title="The One Legitimate Exception"></a>The One Legitimate Exception</h2><p>Sometimes you genuinely need to branch on whether a record exists, and that’s not an error — it’s a valid business case. A caller might or might not have an account. That’s fine.</p><p>Return a boolean:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">return</span> &#123; <span class="attr">found</span>: <span class="literal">true</span>, ...customerData &#125;;</span><br><span class="line"><span class="comment">// or</span></span><br><span class="line"><span class="keyword">return</span> &#123; <span class="attr">found</span>: <span class="literal">false</span> &#125;;</span><br></pre></td></tr></table></figure><p>Check <code>$.External.found</code> in your flow and take the appropriate branch. That’s a business decision, not an error condition, and it shouldn’t be modeled as one.</p><h2 id="A-Convention-in-the-Wrong-Place"><a href="#A-Convention-in-the-Wrong-Place" class="headerlink" title="A Convention in the Wrong Place"></a>A Convention in the Wrong Place</h2><p>Status codes are an API Gateway convention. They exist so HTTP clients can interpret responses without reading the body. Amazon Connect isn’t an HTTP client — it doesn’t need that layer of indirection, and adding it just obscures what your system is actually doing.</p><p>Throw on errors. Return meaningful data on success. Your flows will be simpler and your CloudWatch logs will actually tell you when something is broken.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I keep seeing the same pattern in Amazon Connect implementations: Lambda functions that return HTTP-style status codes — 200 for success, 400 for validation errors, 404 for not found, 500 for unhandled exceptions.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Lambda" scheme="https://bliskavka.com/tags/Lambda/"/>
    
    <category term="Amazon Connect" scheme="https://bliskavka.com/tags/Amazon-Connect/"/>
    
    <category term="CloudWatch" scheme="https://bliskavka.com/tags/CloudWatch/"/>
    
    <category term="Observability" scheme="https://bliskavka.com/tags/Observability/"/>
    
  </entry>
  
  <entry>
    <title>Beautiful Branded PDFs from Markdown</title>
    <link href="https://bliskavka.com/2026/02/18/markdown-pdf-client-branding/"/>
    <id>https://bliskavka.com/2026/02/18/markdown-pdf-client-branding/</id>
    <published>2026-02-18T05:00:00.000Z</published>
    <updated>2026-02-18T18:48:47.833Z</updated>
    
    <content type="html"><![CDATA[<p>Client docs shouldn’t look like a README.</p><span id="more"></span><p>I write a lot of technical documentation in Markdown — architecture docs, design proposals, runbooks. It’s fast, version-controlled, and plays well with Mermaid diagrams. But when it’s time to hand something to a client, a raw markdown export looks amateurish.</p><p>Here’s how I now generate polished, branded PDFs without leaving VS Code.</p><h2 id="The-Setup"><a href="#The-Setup" class="headerlink" title="The Setup"></a>The Setup</h2><p>Install the <a href="https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced">Markdown Preview Enhanced</a> extension for VS Code. It renders Markdown in a live preview pane and can export to PDF via Chrome.</p><h2 id="Let-Claude-Generate-the-Stylesheet"><a href="#Let-Claude-Generate-the-Stylesheet" class="headerlink" title="Let Claude Generate the Stylesheet"></a>Let Claude Generate the Stylesheet</h2><p>Open a Claude Code session and ask it to generate a stylesheet based on the client’s website:</p><blockquote><p>“Generate a Markdown Preview Enhanced stylesheet based on the branding at acmecorp.com. Save it to .crossnote&#x2F;style.less”</p></blockquote><p>Claude fetches the site, pulls the primary colors, fonts, and heading styles, and writes a stylesheet to <code>.crossnote/style.less</code> — the folder MPE uses for custom styles.</p><h2 id="Preview-and-Export"><a href="#Preview-and-Export" class="headerlink" title="Preview and Export"></a>Preview and Export</h2><p>Open your Markdown file, open the MPE preview pane (<code>Ctrl+Shift+V</code>), and you’ll see the document rendered in the client’s colors. When you’re ready, right-click in the preview pane and select <strong>Export → PDF</strong>.</p><p>You get a PDF with client-matching typography, colors, and properly rendered Mermaid diagrams.</p><h2 id="Per-Client-Stylesheets"><a href="#Per-Client-Stylesheets" class="headerlink" title="Per-Client Stylesheets"></a>Per-Client Stylesheets</h2><p>The <code>.crossnote</code> folder lives in the repo, so commit stylesheets per project or client:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">.crossnote/</span><br><span class="line">  style.less         ← active stylesheet</span><br><span class="line">  acme-style.less</span><br><span class="line">  globex-style.less</span><br></pre></td></tr></table></figure><p>Swap stylesheets when switching projects. The whole team gets consistent, on-brand output.</p><p><a href="markdown-pdf-client-branding.pdf">Download a sample PDF</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Client docs shouldn’t look like a README.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="Markdown" scheme="https://bliskavka.com/tags/Markdown/"/>
    
    <category term="VSCode" scheme="https://bliskavka.com/tags/VSCode/"/>
    
    <category term="Documentation" scheme="https://bliskavka.com/tags/Documentation/"/>
    
  </entry>
  
  <entry>
    <title>Letting Go to Lead</title>
    <link href="https://bliskavka.com/2026/02/08/letting-go-to-lead/"/>
    <id>https://bliskavka.com/2026/02/08/letting-go-to-lead/</id>
    <published>2026-02-08T15:00:00.000Z</published>
    <updated>2026-02-08T22:06:31.755Z</updated>
    
    <content type="html"><![CDATA[<p>I’ve been Director of Engineering at CXBuilder for a few months now. Time for some honest reflection.</p><span id="more"></span><p>I still love the technical work — debugging gnarly issues, designing systems, writing code. But leading a team of exceptional developers on cutting-edge projects is just as challenging, if not more. Like any hard technical problem, I’m approaching it with study and practice.</p><p>These lessons apply at every level of leadership — a senior mentoring a junior, a team lead guiding senior devs. But as a director, getting them right is vital. The more I free up my own time, the more I have for the meetings, one-on-ones, interviews, and the work that only I can do.</p><h2 id="Let-People-Get-Bruised"><a href="#Let-People-Get-Bruised" class="headerlink" title="Let People Get Bruised"></a>Let People Get Bruised</h2><p>The hardest part is letting others stumble. Use the Socratic method — prompt people to discover solutions rather than handing them the answer. Let them learn to solve their own problems.</p><p>As an individual contributor, this was a challenge. In this role, it’s mandatory.</p><h2 id="Stop-Being-the-Hero"><a href="#Stop-Being-the-Hero" class="headerlink" title="Stop Being the Hero"></a>Stop Being the Hero</h2><p>I often spot something I could “just fix myself” in 15 minutes. Using tools like Claude Code, it may even be fun! Resist.</p><p>We work with serverless applications that contain infrastructure, backend, frontend, and testing code. A simple change can surface deployment issues, runtime bugs, rendering problems, or test failures. Your “15 minute fix” quietly becomes 3 hours.</p><p>A better approach: spend those 15 minutes with Claude Code in the target repo drafting a user story. Iterate until it’s clear, then put it on the backlog for a developer to pick up. Your job is to multiply the team, not be a bottleneck.</p><h2 id="Vibe-Code-Your-Specs"><a href="#Vibe-Code-Your-Specs" class="headerlink" title="Vibe Code Your Specs"></a>Vibe Code Your Specs</h2><p>I tend to write short user stories. A few sentences is enough for me to come back in a few weeks and know exactly what it means. Other developers don’t have the project and industry context sitting in my head. A short doc means a meeting for context transfer. A detailed doc is time consuming to write.</p><p>So I started using Claude Code to draft design documents from the target repo or source materials. The output is more detailed and accurate than what I’d normally produce on my own. I iterate until it says what I need and nothing else — AI tools love to drop in extra fluff like future enhancements, troubleshooting, and scaling sections. If it’s not relevant, cut it. Reading docs takes brain power too.</p><p>This ties into a broader trend. Spec Driven Development is gaining traction with tools like Kiro, OpenSpec, and the BMAD method. A good spec pays off twice — once for the human developer reading it, and again as input for an agentic coding tool that someone else monitors. The spec becomes the handoff, not a meeting.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I’ve been Director of Engineering at CXBuilder for a few months now. Time for some honest reflection.&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="Leadership" scheme="https://bliskavka.com/tags/Leadership/"/>
    
  </entry>
  
  <entry>
    <title>Director of Engineering at CXBuilder</title>
    <link href="https://bliskavka.com/2025/11/11/director-of-engineering-cxbuilder/"/>
    <id>https://bliskavka.com/2025/11/11/director-of-engineering-cxbuilder/</id>
    <published>2025-11-11T15:00:00.000Z</published>
    <updated>2026-02-08T20:48:26.038Z</updated>
    
    <content type="html"><![CDATA[<p>I’ve been promoted to Director of Engineering at <a href="https://cxbuilder.com/">CXBuilder</a>!</p><span id="more"></span><p>When I joined CXBuilder back in April as a Senior Architect, I was drawn to the scale and complexity of the work — designing active&#x2F;active global resiliency for one of the world’s largest Amazon Connect deployments. Six months in, I’m now leading the engineering organization.</p><p>I’m excited about this next chapter and the opportunity to shape both the technical direction and the team building it.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I’ve been promoted to Director of Engineering at &lt;a href=&quot;https://cxbuilder.com/&quot;&gt;CXBuilder&lt;/a&gt;!&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Amazon Connect" scheme="https://bliskavka.com/tags/Amazon-Connect/"/>
    
  </entry>
  
  <entry>
    <title>Stop Making Callers Wait</title>
    <link href="https://bliskavka.com/2025/09/03/stop-making-callers-wait/"/>
    <id>https://bliskavka.com/2025/09/03/stop-making-callers-wait/</id>
    <published>2025-09-03T14:54:09.000Z</published>
    <updated>2025-09-22T15:29:27.078Z</updated>
    
    <content type="html"><![CDATA[<p>A contact center handling 10,000 calls daily with just 8 seconds of sequential Lambda execution wastes <strong>22+ hours of caller time every day</strong>.</p><span id="more"></span><p>Amazon Connect quietly introduced a game-changing feature that can eliminate this waste entirely—but their announcement barely scratched the surface.</p><p>I wrote a technical guide on the CXBuilder blog:</p><p><strong><a href="https://www.cxbuilder.ai/insights/stop-making-callers-wait">Stop Making Callers Wait →</a></strong></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;A contact center handling 10,000 calls daily with just 8 seconds of sequential Lambda execution wastes &lt;strong&gt;22+ hours of caller time every day&lt;/strong&gt;.&lt;/p&gt;</summary>
    
    
    
    <category term="Optimization" scheme="https://bliskavka.com/categories/Optimization/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Amazon Connect" scheme="https://bliskavka.com/tags/Amazon-Connect/"/>
    
    <category term="Performance" scheme="https://bliskavka.com/tags/Performance/"/>
    
  </entry>
  
  <entry>
    <title>Scaling Amazon Connect Health Checks</title>
    <link href="https://bliskavka.com/2025/08/26/scaling-amazon-connect-health-checks/"/>
    <id>https://bliskavka.com/2025/08/26/scaling-amazon-connect-health-checks/</id>
    <published>2025-08-26T14:43:15.000Z</published>
    <updated>2025-09-22T15:28:43.610Z</updated>
    
    <content type="html"><![CDATA[<p>Health check APIs with 200ms to 8-second response times at 200,000+ daily call volume? That’s a recipe for disaster.</p><span id="more"></span><p>We solved this for a client and I wrote up the solution on the CXBuilder blog — how to perform reliable backend health checks without destroying caller experience or overwhelming your systems.</p><p><strong><a href="https://www.cxbuilder.ai/insights/scaling-amazon-connect-health-checks">Scaling Amazon Connect Health Checks →</a></strong></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Health check APIs with 200ms to 8-second response times at 200,000+ daily call volume? That’s a recipe for disaster.&lt;/p&gt;</summary>
    
    
    
    <category term="Scalability" scheme="https://bliskavka.com/categories/Scalability/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Amazon Connect" scheme="https://bliskavka.com/tags/Amazon-Connect/"/>
    
    <category term="Monitoring" scheme="https://bliskavka.com/tags/Monitoring/"/>
    
  </entry>
  
  <entry>
    <title>Joined CXBuilder as Senior Architect</title>
    <link href="https://bliskavka.com/2025/04/07/joined-cxbuilder/"/>
    <id>https://bliskavka.com/2025/04/07/joined-cxbuilder/</id>
    <published>2025-04-07T14:00:00.000Z</published>
    <updated>2026-02-08T20:36:58.914Z</updated>
    
    <content type="html"><![CDATA[<p>I’ve joined <a href="https://cxbuilder.com/">CXBuilder</a> as a Senior Architect!</p><span id="more"></span><p>I’m leading the technical architecture for one of the world’s largest Amazon Connect deployments — designing active&#x2F;active global resiliency at a scale that pushes the boundaries of what’s possible with contact center infrastructure.</p><p>CXBuilder tackles the complex integrations that standard solutions can’t handle. It’s the kind of work where you need both technical precision and creative problem-solving, and I’m excited to keep pushing the envelope on what’s achievable with serverless architectures and Amazon Connect at global scale.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I’ve joined &lt;a href=&quot;https://cxbuilder.com/&quot;&gt;CXBuilder&lt;/a&gt; as a Senior Architect!&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Amazon Connect" scheme="https://bliskavka.com/tags/Amazon-Connect/"/>
    
  </entry>
  
  <entry>
    <title>Prefill a HubSpot Form in a React App</title>
    <link href="https://bliskavka.com/2025/02/20/prefill-hubspot-form-in-react/"/>
    <id>https://bliskavka.com/2025/02/20/prefill-hubspot-form-in-react/</id>
    <published>2025-02-20T15:51:30.000Z</published>
    <updated>2025-02-20T16:14:00.598Z</updated>
    
    <content type="html"><![CDATA[<p>I have a React App with a HubSpot contact form. The user must be logged into submit the form so I wanted to prefill their email address.</p><p>Fortunately, <a href="https://legacydocs.hubspot.com/docs/methods/forms/advanced_form_options">HubSpot Forms API</a> provides a <code>onFormReady</code> callback!</p><h2 id="Code-Sample"><a href="#Code-Sample" class="headerlink" title="Code Sample"></a>Code Sample</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">HubSpotContactForm</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; getClaims &#125; = <span class="title function_">useAmplify</span>();</span><br><span class="line">  <span class="keyword">const</span> [email, setEmail] = useState&lt;<span class="built_in">string</span>&gt;(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">getClaims</span>().<span class="title function_">then</span>(<span class="function">(<span class="params">d</span>) =&gt;</span> <span class="title function_">setEmail</span>(d?.<span class="property">email</span>));</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (email) &#123;</span><br><span class="line">      <span class="keyword">const</span> script = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&#x27;script&#x27;</span>);</span><br><span class="line">      script.<span class="property">src</span> = <span class="string">&#x27;https://js.hsforms.net/forms/v2.js&#x27;</span>;</span><br><span class="line">      <span class="variable language_">document</span>.<span class="property">body</span>.<span class="title function_">appendChild</span>(script);</span><br><span class="line"></span><br><span class="line">      script.<span class="title function_">addEventListener</span>(<span class="string">&#x27;load&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">const</span> hbspt = (<span class="variable language_">window</span> <span class="keyword">as</span> <span class="built_in">any</span>).<span class="property">hbspt</span>;</span><br><span class="line">        <span class="keyword">if</span> (hbspt) &#123;</span><br><span class="line">          hbspt.<span class="property">forms</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">            <span class="attr">portalId</span>: <span class="string">&#x27;TODO&#x27;</span>,</span><br><span class="line">            <span class="attr">formId</span>: <span class="string">&#x27;TODO&#x27;</span>,</span><br><span class="line">            <span class="attr">target</span>: <span class="string">&#x27;#hubspotForm&#x27;</span>,</span><br><span class="line">            <span class="attr">onFormReady</span>: <span class="keyword">function</span> (<span class="params">$form: <span class="built_in">any</span></span>) &#123;</span><br><span class="line">              <span class="keyword">const</span> emailInput = $form[<span class="number">0</span>];</span><br><span class="line">              emailInput.<span class="property">value</span> = email;</span><br><span class="line">              emailInput.<span class="title function_">dispatchEvent</span>(<span class="keyword">new</span> <span class="title class_">Event</span>(<span class="string">&#x27;input&#x27;</span>, &#123; <span class="attr">bubbles</span>: <span class="literal">true</span> &#125;));</span><br><span class="line">            &#125;,</span><br><span class="line">          &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;, [email]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">Container</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">header</span>=<span class="string">&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &lt;<span class="attr">Header</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">variant</span>=<span class="string">&quot;h2&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">description</span>=<span class="string">&quot;Submit any questions or feedback here and we will get back to you shortly&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &gt;</span></span></span><br><span class="line"><span class="language-xml">          Contact Form</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Header</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#125;</span></span><br><span class="line"><span class="language-xml">    &gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;hubspotForm&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Container</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I have a React App with a HubSpot contact form. The user must be logged into submit the form so I wanted to prefill their email</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="React" scheme="https://bliskavka.com/tags/React/"/>
    
    <category term="HubSpot" scheme="https://bliskavka.com/tags/HubSpot/"/>
    
  </entry>
  
  <entry>
    <title>Track HubSpot Form Conversion</title>
    <link href="https://bliskavka.com/2025/02/20/hubspot-form-events/"/>
    <id>https://bliskavka.com/2025/02/20/hubspot-form-events/</id>
    <published>2025-02-20T15:47:27.000Z</published>
    <updated>2025-02-20T16:12:22.516Z</updated>
    
    <content type="html"><![CDATA[<p>I was working a Google AdWords campaign for <a href="https://mind-wellness.net/">Mind-Wellness.net</a> and I wanted to track when a user submitted the contact form.</p><p>I generated the static site using <a href="https://gohugo.io/">Hugo</a>, and I am relying on HubSpot for the contact form.</p><p>Fortunately, <a href="https://legacydocs.hubspot.com/docs/methods/forms/advanced_form_options">HubSpot Forms API</a> provides a <code>onFormSubmit</code> callback!</p><h2 id="Code-Sample"><a href="#Code-Sample" class="headerlink" title="Code Sample"></a>Code Sample</h2><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">hbspt.<span class="property">forms</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">  <span class="attr">region</span>: <span class="string">&#x27;na1&#x27;</span>,</span><br><span class="line">  <span class="attr">portalId</span>: <span class="string">&#x27;TODO&#x27;</span>,</span><br><span class="line">  <span class="attr">formId</span>: <span class="string">&#x27;TODO&#x27;</span>,</span><br><span class="line">  <span class="attr">onFormSubmit</span>: <span class="keyword">function</span> (<span class="params">$form</span>) &#123;</span><br><span class="line">    <span class="comment">// Use gtag to track conversion</span></span><br><span class="line">    <span class="title function_">gtag_report_conversion</span>();</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I was working a Google AdWords campaign for &lt;a href=&quot;https://mind-wellness.net/&quot;&gt;Mind-Wellness.net&lt;/a&gt; and I wanted to track when a user</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="HubSpot" scheme="https://bliskavka.com/tags/HubSpot/"/>
    
  </entry>
  
  <entry>
    <title>AWS Chime StartBotConversation Service Limit</title>
    <link href="https://bliskavka.com/2024/11/19/aws-chime-start-bot-conversation-service-limit/"/>
    <id>https://bliskavka.com/2024/11/19/aws-chime-start-bot-conversation-service-limit/</id>
    <published>2024-11-19T13:53:07.000Z</published>
    <updated>2024-11-27T13:50:51.567Z</updated>
    
    <content type="html"><![CDATA[<p>I am working on a Amazon Chime application that provides natural language and GenAI features to a legacy phone system. After deploying the app into a new account, I got the following error in my SMA Lambda Handler when trying to invoke the Amazon Lex bot:</p><span id="more"></span><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;SchemaVersion&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Sequence&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;InvocationEventType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;INVALID_LAMBDA_RESPONSE&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;CallDetails&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> ... <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;ErrorType&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ActionExecutionThrottled&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;ErrorMessage&quot;</span><span class="punctuation">:</span> <span class="string">&quot;This account has exceeded the number of bot references allowed by the PSTN Audio service&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>This error was a surprise because I have deployed similar apps in older accounts, without being throttled.</p><h2 id="The-Service-Quota"><a href="#The-Service-Quota" class="headerlink" title="The Service Quota"></a>The Service Quota</h2><p>After doing some searching, I found <a href="https://docs.aws.amazon.com/general/latest/gr/chime-sdk.html#chm-sdk-pstn-quotas">this documentation</a> about a new service quota</p><ul><li>Name: Amazon Chime SDK SIP trunking and voice - <code>StartBotConversation</code> Amazon Lex bots</li><li><strong>Default</strong>: 0</li><li>Adjustable: Yes</li><li>Description: The maximum number of Amazon Lex bots you can use with the PSTN Audio StartBotConversation action in the current AWS Region.</li></ul><p>I assume this quota is pretty new, because I could not find it in the Service Quota console, or list it using the CLI.</p><p>I do not know yet if the quota will limit how many distinct bots I can use, or how many bot sessions. Ping me if you know the answer!</p><h2 id="Timeline-ETA-12-Days"><a href="#Timeline-ETA-12-Days" class="headerlink" title="Timeline (ETA 12 Days)"></a>Timeline (ETA 12 Days)</h2><p>I submitted a support case to get the limit increased. I will keep this timeline updated until the issue is resolved.</p><ul><li>2024-11-15: Submitted case titled: Need to increase service quote. Option is documented but not available in AWS Console.</li><li>2024-11-18: Response from support requesting use case description and what region I expect to use.</li><li>2024-11-19: Response from support: The request has been forwarded to the service team, expect a response in 7-10 days.</li><li>2024-11-27: The service quota has been updated and applied.</li></ul><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>If developing an Chime SIP Media Application, include time for the following service quota increases:</p><ul><li>Amazon Chime SDK SIP trunking and voice - StartBotConversation Amazon Lex bots<ul><li>Default: 0</li><li>ETA: 12 days</li></ul></li><li>Amazon Chime SDK SIP trunking and voice - provisioned phone numbers<ul><li>Default: 5</li><li>ETA: 10 days</li></ul></li><li><a href="https://docs.aws.amazon.com/general/latest/gr/chime-sdk.html#chm-sdk-pstn-quotas">Other Quotas</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;I am working on a Amazon Chime application that provides natural language and GenAI features to a legacy phone system. After deploying the app into a new account, I got the following error in my SMA Lambda Handler when trying to invoke the Amazon Lex bot:&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/HowTo/Troubleshooting/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="Chime" scheme="https://bliskavka.com/tags/Chime/"/>
    
    <category term="Lex" scheme="https://bliskavka.com/tags/Lex/"/>
    
  </entry>
  
  <entry>
    <title>How to Deploy CDK Using Lambda</title>
    <link href="https://bliskavka.com/2024/10/03/deploy-cdk-using-lambda/"/>
    <id>https://bliskavka.com/2024/10/03/deploy-cdk-using-lambda/</id>
    <published>2024-10-03T20:34:40.000Z</published>
    <updated>2024-11-19T13:55:34.008Z</updated>
    
    <content type="html"><![CDATA[<p>I really like the declarative nature of CloudFormation - you describe an end-state, and it computes the diffs. CDK gives me even more flexibility in creating complex apps.</p><p>Recently I built a CDK web application that could provision other CDK apps. React app sends a message to API GW, which invokes a lambda, which triggers a CDK deployment.</p><p>The default Lambda runtime cannot deploy CDK. You have to use a custom Docker image. There are plenty of online guides on how to do this, so I am only covering the highlights.</p><h2 id="Deployer-Stack"><a href="#Deployer-Stack" class="headerlink" title="Deployer Stack"></a>Deployer Stack</h2><p>It takes a while to build the docker container, so you should define a deployer stack separate from your web app.</p><p>It is also handy to add the following script to your package.json:</p><p><code>&quot;cdk:deployer&quot;: &quot;npm run cdk -- --app=\&quot;npx ts-node --prefer-ts-exts bin/deployer.ts\&quot;&quot;</code></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">StackProps</span>, <span class="title class_">Stack</span>, <span class="title class_">Duration</span>, <span class="title class_">CfnOutput</span>, <span class="title class_">Aws</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PolicyStatement</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-iam&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">Architecture</span>,</span><br><span class="line">  <span class="title class_">DockerImageCode</span>,</span><br><span class="line">  <span class="title class_">DockerImageFunction</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-lambda&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Construct</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;constructs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="variable constant_">PROJECT_ROOT</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;../../constants&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">DeployerStackProps</span> <span class="keyword">extends</span> <span class="title class_">StackProps</span> &#123;</span><br><span class="line">  <span class="attr">prefix</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Deploys the cdk deployer lambda.</span></span><br><span class="line"><span class="comment"> * Implemented as own stack with export because it takes a while to build and deploy.</span></span><br><span class="line"><span class="comment"> * You should redeploy this when your CDK code changes</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">DeployerStack</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Stack</span> &#123;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">deployFunctionArn</span>: <span class="title class_">CfnOutput</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span>, props: DeployerStackProps</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; prefix &#125; = props;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id, &#123; <span class="attr">stackName</span>: <span class="string">`<span class="subst">$&#123;prefix&#125;</span>-deployer`</span>, ...props &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> deploy = <span class="keyword">new</span> <span class="title class_">DockerImageFunction</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Deploy&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">code</span>: <span class="title class_">DockerImageCode</span>.<span class="title function_">fromImageAsset</span>(<span class="variable constant_">PROJECT_ROOT</span>),</span><br><span class="line">      <span class="attr">timeout</span>: <span class="title class_">Duration</span>.<span class="title function_">minutes</span>(<span class="number">15</span>),</span><br><span class="line">      <span class="attr">memorySize</span>: <span class="number">4096</span>,</span><br><span class="line">      <span class="attr">initialPolicy</span>: [</span><br><span class="line">        <span class="comment">// Scope this down to whatever you need.</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">          <span class="attr">resources</span>: [</span><br><span class="line">            <span class="string">`arn:aws:iam::<span class="subst">$&#123;Aws.ACCOUNT_ID&#125;</span>:role/cdk-hnb659fds-*-role-<span class="subst">$&#123;Aws.ACCOUNT_ID&#125;</span>-*`</span>,</span><br><span class="line">          ],</span><br><span class="line">          <span class="attr">actions</span>: [<span class="string">&#x27;sts:AssumeRole&#x27;</span>],</span><br><span class="line">        &#125;),</span><br><span class="line">      ],</span><br><span class="line">      <span class="attr">environment</span>: &#123;</span><br><span class="line">        <span class="comment">// Required to use npx</span></span><br><span class="line">        <span class="attr">HOME</span>: <span class="string">&#x27;/tmp&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">deployFunctionArn</span> = <span class="keyword">new</span> <span class="title class_">CfnOutput</span>(<span class="variable language_">this</span>, <span class="string">&#x27;DeployFunctionArn&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">value</span>: deploy.<span class="property">functionArn</span>,</span><br><span class="line">      <span class="attr">exportName</span>: <span class="string">`<span class="subst">$&#123;prefix&#125;</span>-deployer-function-arn`</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Notice the <code>deployer-function-arn</code> export. You will want to import this into your other app.</p><h2 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h2><p>This docker file will copy your entire application, and set the lambda entry point.</p><figure class="highlight dockerfile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">FROM</span> public.ecr.aws/lambda/nodejs:<span class="number">20</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">WORKDIR</span><span class="language-bash"> <span class="variable">$&#123;LAMBDA_TASK_ROOT&#125;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Copy in pre-installed/built resources</span></span><br><span class="line"><span class="keyword">COPY</span><span class="language-bash"> . .</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Entry point</span></span><br><span class="line"><span class="keyword">CMD</span><span class="language-bash"> [<span class="string">&quot;dist/lib/DeployerStack/lambda/index.handler&quot;</span>]</span></span><br></pre></td></tr></table></figure><p>Note: I found a few examples online which use a different base image (<code>node:18-bookworm</code>), which require additional steps including installing <code>aws-lambda-ric</code>, but I ran into some issues.</p><ul><li>After a certain amount of pulls from the Docker registry, you will have to sign up. For AWS work, ECR is fast and automatic.</li><li>The <code>public.ecr.aws/lambda/nodejs:20</code> image is already configured for running node apps. Total build time dropped from ~15 to 4.5 minutes.</li></ul><h2 id="Deployer-Lambda"><a href="#Deployer-Lambda" class="headerlink" title="Deployer Lambda"></a>Deployer Lambda</h2><p>Here is the lambda entry point. Notice the handler accepts stack props. Since my app uses API GW with a 15 second timeout, I trigger the trigger this second lambda.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; randomUUID &#125; <span class="keyword">from</span> <span class="string">&#x27;crypto&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">App</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> fs <span class="keyword">from</span> <span class="string">&#x27;fs-extra&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">MyMiniAppStack</span>,</span><br><span class="line">  <span class="title class_">MyMiniAppStackProps</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;../../constructs/MyMiniAppStack&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; exec &#125; <span class="keyword">from</span> <span class="string">&#x27;@lsw-apps/lsw-dev-util&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Entry point for the deployer lambda</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">handler</span>(<span class="params">childStackProps: MyMiniAppStackProps</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;deploying&#x27;</span>, childStackProps);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Synthesize a Cloud Assembly somewhere in /tmp</span></span><br><span class="line">  <span class="keyword">const</span> assemblyDir = <span class="string">`/tmp/cdk.out.<span class="subst">$&#123;randomUUID()&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title class_">App</span>(&#123; <span class="attr">outdir</span>: assemblyDir &#125;);</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">MyMiniAppStack</span>(app, <span class="string">&#x27;MyMiniApp&#x27;</span>, childStackProps);</span><br><span class="line">  app.<span class="title function_">synth</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">// Deploy the assembly</span></span><br><span class="line">    <span class="keyword">await</span> <span class="title function_">exec</span>(</span><br><span class="line">      <span class="string">`npx cdk deploy --app <span class="subst">$&#123;assemblyDir&#125;</span> --all --require-approval=never`</span>,</span><br><span class="line">      <span class="literal">true</span></span><br><span class="line">    );</span><br><span class="line">  &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">    <span class="comment">// Clean up.</span></span><br><span class="line">    <span class="keyword">await</span> fs.<span class="title function_">remove</span>(assemblyDir);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// <span class="doctag">TODO:</span> Publish error to SNS topic?</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Limitations"><a href="#Limitations" class="headerlink" title="Limitations"></a>Limitations</h2><ul><li>Lambda has a 15 minute timeout. If you expect your app to take longer to run, you can use CodeBuild.<ul><li>Alternatively, once CloudFormation has started deploying the changeset, you can kill the process and exit the lambda.</li></ul></li><li>You should add SNS notifications to the CloudFormation stack so that you can update your provisioning app with success&#x2F;failure.</li></ul><h3 id="Using-Codebuild-to-Build-the-Deployer"><a href="#Using-Codebuild-to-Build-the-Deployer" class="headerlink" title="Using Codebuild to Build the Deployer"></a>Using Codebuild to Build the Deployer</h3><p>Building a Docker container on a MacBook Pro is crazy slow. The following CodeBuild took ~5 minutes to build the deployer image and deploy it.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Stack</span>, <span class="title class_">StackProps</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">BuildEnvironmentVariableType</span>,</span><br><span class="line">  <span class="title class_">BuildSpec</span>,</span><br><span class="line">  <span class="title class_">ComputeType</span>,</span><br><span class="line">  <span class="title class_">LinuxBuildImage</span>,</span><br><span class="line">  <span class="title class_">Project</span>,</span><br><span class="line">  <span class="title class_">Source</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-codebuild&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PolicyStatement</span>, <span class="title class_">Role</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-iam&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Construct</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;constructs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">CodeBuildStackProps</span> <span class="keyword">extends</span> <span class="title class_">StackProps</span> &#123;</span><br><span class="line">  <span class="attr">prefix</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">defaultBranch</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">CodeBuildStack</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Stack</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span>, props: CodeBuildStackProps</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; prefix, defaultBranch &#125; = props;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id, &#123;</span><br><span class="line">      <span class="attr">stackName</span>: prefix,</span><br><span class="line">      ...props,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> project = <span class="keyword">new</span> <span class="title class_">Project</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Project&#x27;</span>, &#123;</span><br><span class="line">      <span class="comment">// Have to manually add app credential in the UI</span></span><br><span class="line">      <span class="attr">source</span>: <span class="title class_">Source</span>.<span class="title function_">gitHub</span>(&#123;</span><br><span class="line">        <span class="attr">owner</span>: <span class="string">&#x27;ibliskavka&#x27;</span>,</span><br><span class="line">        <span class="attr">repo</span>: <span class="string">&#x27;my-provisioning-app&#x27;</span>,</span><br><span class="line">        <span class="attr">branchOrRef</span>: defaultBranch,</span><br><span class="line">      &#125;),</span><br><span class="line">      <span class="attr">projectName</span>: prefix,</span><br><span class="line">      <span class="attr">environment</span>: &#123;</span><br><span class="line">        <span class="attr">buildImage</span>: <span class="title class_">LinuxBuildImage</span>.<span class="property">STANDARD_7_0</span>,</span><br><span class="line">        <span class="attr">computeType</span>: <span class="title class_">ComputeType</span>.<span class="property">MEDIUM</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">environmentVariables</span>: &#123;</span><br><span class="line">        <span class="comment">// This token is required to pull private npm packages from GH</span></span><br><span class="line">        <span class="attr">NPM_TOKEN</span>: &#123;</span><br><span class="line">          <span class="attr">type</span>: <span class="title class_">BuildEnvironmentVariableType</span>.<span class="property">SECRETS_MANAGER</span>,</span><br><span class="line">          <span class="attr">value</span>: <span class="string">&#x27;github-npm-token&#x27;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">buildSpec</span>: <span class="title class_">BuildSpec</span>.<span class="title function_">fromObject</span>(&#123;</span><br><span class="line">        <span class="attr">version</span>: <span class="number">0.2</span>,</span><br><span class="line">        <span class="attr">phases</span>: &#123;</span><br><span class="line">          <span class="attr">build</span>: &#123;</span><br><span class="line">            <span class="attr">commands</span>: [</span><br><span class="line">              <span class="string">&#x27;echo $NPM_TOKEN&#x27;</span>,</span><br><span class="line">              <span class="string">&#x27;npm config set //npm.pkg.github.com/:_authToken=$NPM_TOKEN&#x27;</span>,</span><br><span class="line">              <span class="string">&#x27;npm i&#x27;</span>,</span><br><span class="line">              <span class="string">&#x27;npm run deploy:deployer&#x27;</span>, <span class="comment">// Remember this script from earlier?</span></span><br><span class="line">            ],</span><br><span class="line">          &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;),</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> &#123; account, region &#125; = <span class="title class_">Stack</span>.<span class="title function_">of</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="comment">// Allows CodeBuild to execute CDK commands</span></span><br><span class="line">    project.<span class="title function_">addToRolePolicy</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">        <span class="attr">actions</span>: [<span class="string">&#x27;sts:AssumeRole&#x27;</span>],</span><br><span class="line">        <span class="attr">resources</span>: [</span><br><span class="line">          <span class="string">`arn:aws:iam::<span class="subst">$&#123;account&#125;</span>:role/cdk-hnb659fds-deploy-role-<span class="subst">$&#123;account&#125;</span>-<span class="subst">$&#123;region&#125;</span>`</span>,</span><br><span class="line">          <span class="string">`arn:aws:iam::<span class="subst">$&#123;account&#125;</span>:role/cdk-hnb659fds-image-publishing-role-<span class="subst">$&#123;account&#125;</span>-<span class="subst">$&#123;region&#125;</span>`</span>,</span><br><span class="line">          <span class="string">`arn:aws:iam::<span class="subst">$&#123;account&#125;</span>:role/cdk-hnb659fds-file-publishing-role-<span class="subst">$&#123;account&#125;</span>-<span class="subst">$&#123;region&#125;</span>`</span>,</span><br><span class="line">        ],</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I really like the declarative nature of CloudFormation - you describe an end-state, and it computes the diffs. CDK gives me even more</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
    <category term="TypeScript" scheme="https://bliskavka.com/tags/TypeScript/"/>
    
  </entry>
  
  <entry>
    <title>AWS Certified AI Practitioner Certification</title>
    <link href="https://bliskavka.com/2024/09/26/ai-practitioner-cert/"/>
    <id>https://bliskavka.com/2024/09/26/ai-practitioner-cert/</id>
    <published>2024-09-26T14:09:20.000Z</published>
    <updated>2024-10-08T14:14:58.356Z</updated>
    
    <content type="html"><![CDATA[<p>I am now a certified <a href="https://www.credly.com/badges/924ec4aa-c2e9-4f29-99a9-8a4383d97b48/linked_in?t=skga96">AWS AI Practitioner</a>!</p><p>This is a fairly new certification from AWS focusing on MLOps and GenAI workloads on AWS. I highly recommend these <a href="https://www.udemy.com/share/10bJEn3@HDMayJVO0StovmOdHET_QLo9L6b2CoyvxqBEQqpLSZq_tOhr9wwgeOOuyEFQ0zis/">practice exams on Udemy</a>.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I am now a certified &lt;a href=&quot;https://www.credly.com/badges/924ec4aa-c2e9-4f29-99a9-8a4383d97b48/linked_in?t=skga96&quot;&gt;AWS AI</summary>
        
      
    
    
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>CDK Construct for Athena DynamoDB Connector</title>
    <link href="https://bliskavka.com/2024/09/18/cdk-athena-ddb-connector/"/>
    <id>https://bliskavka.com/2024/09/18/cdk-athena-ddb-connector/</id>
    <published>2024-09-18T12:11:43.000Z</published>
    <updated>2024-11-19T13:55:27.043Z</updated>
    
    <content type="html"><![CDATA[<p>The AWS Athena connector for DynamoDB enables you to query data stored in Amazon DynamoDB using Amazon Athena, which is typically used to query structured data in S3 using standard SQL. Since DynamoDB is a NoSQL database, querying it with SQL isn’t straightforward by default. This connector provides a bridge between the two, allowing you to leverage Athena’s SQL-based querying on data stored in DynamoDB.</p><h2 id="CDK-Construct"><a href="#CDK-Construct" class="headerlink" title="CDK Construct"></a>CDK Construct</h2><p>The DataLake I am building requires that all data be encrypted with KMS, and deployed through CDK. This construct deploys the SAM template and configures all the necessary permissions. The OOTB role was too permissive, so I injected my own.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; aws_sam <span class="keyword">as</span> sam, <span class="title class_">Stack</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Construct</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;constructs&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Bucket</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-s3&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">IKey</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-kms&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">IGrantable</span>,</span><br><span class="line">  <span class="title class_">ManagedPolicy</span>,</span><br><span class="line">  <span class="title class_">PolicyStatement</span>,</span><br><span class="line">  <span class="title class_">Role</span>,</span><br><span class="line">  <span class="title class_">ServicePrincipal</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-iam&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ITable</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-dynamodb&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Constants</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;../../constants&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Function</span>, <span class="title class_">IFunction</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-lambda&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">AthenaConnector</span> &#123;</span><br><span class="line">  <span class="attr">grantRead</span>: <span class="function">(<span class="params">grantee: IGrantable</span>) =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">DynamoConnectorProps</span> &#123;</span><br><span class="line">  <span class="attr">prefix</span>: <span class="built_in">string</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * What ddb tables it has access to</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">tables</span>: <span class="title class_">ITable</span>[];</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * What encryption key to use</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="attr">encryptionKey</span>: <span class="title class_">IKey</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Provides Athena access to Dynamo tables</span></span><br><span class="line"><span class="comment"> * Source: https://us-east-1.console.aws.amazon.com/lambda/home?region=us-east-1#/create/app?applicationId=arn:aws:serverlessrepo:us-east-1:292517598671:applications/AthenaDynamoDBConnector</span></span><br><span class="line"><span class="comment"> * <span class="doctag">NOTE:</span> You must manually register the connector: https://docs.aws.amazon.com/athena/latest/ug/connect-data-source-serverless-app-repo.html</span></span><br><span class="line"><span class="comment"> * <span class="doctag">NOTE:</span> You must manually add this environment variable to the lambda: spill_put_request_headers=&#123;&quot;x-amz-server-side-encryption&quot; : &quot;aws:kms&quot;&#125;</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">DynamoConnector</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Construct</span> <span class="keyword">implements</span> <span class="title class_">AthenaConnector</span> &#123;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">bucket</span>: <span class="title class_">Bucket</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">lambda</span>: <span class="title class_">IFunction</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span>, props: DynamoConnectorProps</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id);</span><br><span class="line">    <span class="keyword">const</span> &#123; prefix, tables, encryptionKey &#125; = props;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> name = <span class="string">`<span class="subst">$&#123;prefix&#125;</span>-ddb-connector`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">bucket</span> = <span class="keyword">new</span> <span class="title class_">Bucket</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Bucket&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">bucketName</span>: name,</span><br><span class="line">      encryptionKey,</span><br><span class="line">      <span class="comment">// Reduce KMS Costs</span></span><br><span class="line">      <span class="attr">bucketKeyEnabled</span>: <span class="literal">true</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="comment">// Ensure all bucket data is encrypted</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">bucket</span>.<span class="title function_">addToResourcePolicy</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">        <span class="attr">sid</span>: <span class="string">&#x27;Deny Unencrypted Objects&#x27;</span>,</span><br><span class="line">        <span class="attr">actions</span>: [<span class="string">&#x27;s3:PutObject&#x27;</span>],</span><br><span class="line">        <span class="attr">effect</span>: <span class="title class_">Effect</span>.<span class="property">DENY</span>,</span><br><span class="line">        <span class="attr">resources</span>: [<span class="variable language_">this</span>.<span class="title function_">arnForObjects</span>(<span class="string">&#x27;*&#x27;</span>)],</span><br><span class="line">        <span class="attr">principals</span>: [<span class="keyword">new</span> <span class="title class_">AnyPrincipal</span>()],</span><br><span class="line">        <span class="attr">conditions</span>: &#123;</span><br><span class="line">          <span class="title class_">StringNotEquals</span>: &#123;</span><br><span class="line">            <span class="string">&#x27;s3:x-amz-server-side-encryption&#x27;</span>: <span class="string">&#x27;aws:kms&#x27;</span>,</span><br><span class="line">          &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> role = <span class="keyword">new</span> <span class="title class_">Role</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Role&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">roleName</span>: name,</span><br><span class="line">      <span class="attr">assumedBy</span>: <span class="keyword">new</span> <span class="title class_">ServicePrincipal</span>(<span class="string">&#x27;lambda.amazonaws.com&#x27;</span>),</span><br><span class="line">      <span class="attr">managedPolicies</span>: [</span><br><span class="line">        <span class="title class_">ManagedPolicy</span>.<span class="title function_">fromAwsManagedPolicyName</span>(</span><br><span class="line">          <span class="string">&#x27;service-role/AWSLambdaBasicExecutionRole&#x27;</span></span><br><span class="line">        ),</span><br><span class="line">      ],</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// KMS Permissions</span></span><br><span class="line">    encryptionKey.<span class="title function_">grantEncryptDecrypt</span>(role);</span><br><span class="line">    role.<span class="title function_">addToPolicy</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">        <span class="attr">actions</span>: [<span class="string">&#x27;kms:GenerateRandom&#x27;</span>],</span><br><span class="line">        <span class="comment">// GenerateRandom does not use any account-specific resources, such as KMS keys.</span></span><br><span class="line">        <span class="attr">resources</span>: [<span class="string">&#x27;*&#x27;</span>],</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// S3 Permissions</span></span><br><span class="line">    role.<span class="title function_">addToPolicy</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">        <span class="attr">actions</span>: [<span class="string">&#x27;s3:ListAllMyBuckets&#x27;</span>],</span><br><span class="line">        <span class="attr">resources</span>: [<span class="string">&#x27;*&#x27;</span>],</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line">    role.<span class="title function_">addToPolicy</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">        <span class="attr">actions</span>: [</span><br><span class="line">          <span class="string">&#x27;s3:GetObject&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:ListBucket&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:GetBucketLocation&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:GetObjectVersion&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:PutObject&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:PutObjectAcl&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:DeleteObject&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:GetLifecycleConfiguration&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;s3:PutLifecycleConfiguration&#x27;</span>,</span><br><span class="line">        ],</span><br><span class="line">        <span class="attr">resources</span>: [<span class="variable language_">this</span>.<span class="property">bucket</span>.<span class="property">bucketArn</span>, <span class="variable language_">this</span>.<span class="property">bucket</span>.<span class="title function_">arnForObjects</span>(<span class="string">&#x27;*&#x27;</span>)],</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// DDB Permissions</span></span><br><span class="line">    tables.<span class="title function_">forEach</span>(<span class="function">(<span class="params">t</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// Allows connector to perform queries</span></span><br><span class="line">      t.<span class="title function_">grantReadWriteData</span>(role);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Glue/Athena Permissions</span></span><br><span class="line">    role.<span class="title function_">addToPolicy</span>(</span><br><span class="line">      <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">        <span class="attr">actions</span>: [</span><br><span class="line">          <span class="string">&#x27;athena:GetQueryExecution&#x27;</span>,</span><br><span class="line"></span><br><span class="line">          <span class="string">&#x27;glue:GetTableVersions&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;glue:GetPartitions&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;glue:GetTables&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;glue:GetTableVersion&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;glue:GetDatabases&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;glue:GetTable&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;glue:GetPartition&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;glue:GetDatabase&#x27;</span>,</span><br><span class="line"></span><br><span class="line">          <span class="comment">// Allows Athena Query Explorer to show schema. Required on first run for Athena to discover schemas</span></span><br><span class="line">          <span class="comment">// Can probably be commented out later to avoid oversharing data</span></span><br><span class="line">          <span class="string">&#x27;dynamodb:ListTables&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;dynamodb:DescribeTable&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;dynamodb:ListSchemas&#x27;</span>,</span><br><span class="line">          <span class="string">&#x27;dynamodb:Scan&#x27;</span>,</span><br><span class="line">        ],</span><br><span class="line">        <span class="attr">resources</span>: [<span class="string">&#x27;*&#x27;</span>],</span><br><span class="line">      &#125;)</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> &#123; region &#125; = <span class="title class_">Stack</span>.<span class="title function_">of</span>(scope);</span><br><span class="line">    <span class="keyword">new</span> sam.<span class="title class_">CfnApplication</span>(<span class="variable language_">this</span>, <span class="string">&#x27;App&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">location</span>: &#123;</span><br><span class="line">        <span class="attr">applicationId</span>: <span class="string">`arn:aws:serverlessrepo:<span class="subst">$&#123;region&#125;</span>:292517598671:applications/AthenaDynamoDBConnector`</span>,</span><br><span class="line">        <span class="attr">semanticVersion</span>: <span class="string">&#x27;2024.25.1&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">parameters</span>: &#123;</span><br><span class="line">        <span class="title class_">LambdaRole</span>: role.<span class="property">roleArn</span>,</span><br><span class="line">        <span class="comment">// This is the name of the lambda function that will be created. This name must satisfy the pattern ^[a-z0-9-_]&#123;1,64&#125;$</span></span><br><span class="line">        <span class="title class_">AthenaCatalogName</span>: name,</span><br><span class="line">        <span class="title class_">DisableSpillEncryption</span>: <span class="string">&#x27;false&#x27;</span>,</span><br><span class="line">        <span class="title class_">SpillBucket</span>: <span class="variable language_">this</span>.<span class="property">bucket</span>.<span class="property">bucketName</span>,</span><br><span class="line">        <span class="title class_">KMSKeyId</span>: <span class="variable language_">this</span>.<span class="property">bucket</span>.<span class="property">encryptionKey</span>.<span class="property">keyId</span>,</span><br><span class="line">        <span class="title class_">LambdaMemory</span>: <span class="string">&#x27;512&#x27;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lambda</span> = <span class="title class_">Function</span>.<span class="title function_">fromFunctionName</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Connector&#x27;</span>, name);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">grantRead</span>(<span class="params">grantee: IGrantable</span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">bucket</span>.<span class="title function_">grantRead</span>(grantee);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">lambda</span>.<span class="title function_">grantInvoke</span>(grantee);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Manual-Steps"><a href="#Manual-Steps" class="headerlink" title="Manual Steps"></a>Manual Steps</h3><ul><li>You must manually <a href="https://docs.aws.amazon.com/athena/latest/ug/connect-data-source-serverless-app-repo.html">register the connector with Athena</a></li><li>To use KMS you must manually add this environment variable to the lambda: <code>spill_put_request_headers=&#123;&quot;x-amz-server-side-encryption&quot; : &quot;aws:kms&quot;&#125;</code></li></ul><h2 id="Drawbacks"><a href="#Drawbacks" class="headerlink" title="Drawbacks"></a>Drawbacks</h2><p>This worked perfectly for small, homogeneous and relatively flat tables, but you may run into problems if:</p><ul><li>You have nested schemas<ul><li>The connector could not infer nested object schemas well and would fail</li></ul></li><li>You have a lot of columns. I had a flattened table with ~1,000 columns, whenever I ran <code>SELECT *</code> the query failed. When I queried specific columns it succeeded.<ul><li>Under the hood, the connector creates Projection Expressions. ~1000 columns created massive expressions that were rejected by DynamoDB.</li></ul></li></ul><h2 id="Alternative-for-Large-Tables"><a href="#Alternative-for-Large-Tables" class="headerlink" title="Alternative for Large Tables"></a>Alternative for Large Tables</h2><p>I still use the connector in a few places, but for the really wide table I went with another approach.</p><p>I am using DDB for operational processes during the day. The reporting data does not need to be realtime, and could be rebuilt nightly.</p><ul><li>A nightly Lambda triggers a DDB Table Export to S3 ($.50&#x2F;GB)</li><li>When the export drops, another Lambda<ul><li>Unmarshalls the JSON data and stores it in a S3 staging prefix</li><li>Invokes an Athena Query to <code>INSERT * INTO optimized_table SELECT * FROM json_table</code><ul><li><code>json_table</code> is a Glue table definition over the staged unmarshalled JSON data</li><li><code>optimized_table</code> is a Glue table definition using Parquet, Snappy, and Partitioning</li><li>NOTE: The query also performs some deduping, which I omitted for brevity</li></ul></li></ul></li><li>Reporting is done on <code>optimized_table</code> which is super compressed and fast.</li></ul><h3 id="Other-Tips"><a href="#Other-Tips" class="headerlink" title="Other Tips"></a>Other Tips</h3><ul><li>If your data is strictly transactional, you may be able to delete the DDB source data to reduce costs<ul><li>If you have a lot of data, the Table <code>Scan</code> + <code>BatchDelete</code> operation may be too slow. In my case, the Lambda was timing out after 15 minutes</li><li>Solution: Delete and recreate the table using the SDK.</li></ul></li></ul><h2 id="Troubleshooting"><a href="#Troubleshooting" class="headerlink" title="Troubleshooting"></a>Troubleshooting</h2><h3 id="Athena-Dynamo-DB-Connector-returns-0-rows"><a href="#Athena-Dynamo-DB-Connector-returns-0-rows" class="headerlink" title="Athena Dynamo DB Connector returns 0 rows"></a>Athena Dynamo DB Connector returns 0 rows</h3><p>Re-run your query with <code>LIMIT 10</code>, if the query succeeds check your lambda logs. You may be getting a permission denied when writing to the spill bucket.</p><p>Solution: Manually add this environment variable to the lambda:</p><ul><li><code>spill_put_request_headers</code>&#x3D;<code>&#123;&quot;x-amz-server-side-encryption&quot; : &quot;aws:kms&quot;&#125;</code></li></ul><h3 id="Athena-Dynamo-DB-Connector-Error-Invalid-ProjectionExpression"><a href="#Athena-Dynamo-DB-Connector-Error-Invalid-ProjectionExpression" class="headerlink" title="Athena Dynamo DB Connector Error: Invalid ProjectionExpression"></a>Athena Dynamo DB Connector Error: Invalid ProjectionExpression</h3><p>Selecting certain fields works<br><code>SELECT foo, bar FROM &quot;default&quot;.&quot;datalake&quot; limit 10;</code></p><p>Selecting all fields fails. I am guessing this is because connector puts all the columns into the ProjectionExpression.</p><p><code>SELECT * FROM &quot;default&quot;.&quot;datalake&quot; limit 10;</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">GENERIC_USER_ERROR: Encountered an exception[software.amazon.awssdk.services.dynamodb.model.DynamoDbException] from your LambdaFunction[arn:aws:lambda:&#123;region&#125;:&#123;account&#125;:<span class="keyword">function</span>:ddb-connector] executed <span class="keyword">in</span> context[S3SpillLocation&#123;bucket=<span class="string">&#x27;ddb-connector&#x27;</span>, key=<span class="string">&#x27;athena-spill/xxx&#x27;</span>, directory=<span class="literal">true</span>&#125;] with message[Invalid ProjectionExpression: Expression size has exceeded the maximum allowed size; (Service: DynamoDb, Status Code: 400, Request ID: xxx)]</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;The AWS Athena connector for DynamoDB enables you to query data stored in Amazon DynamoDB using Amazon Athena, which is typically used</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
    <category term="TypeScript" scheme="https://bliskavka.com/tags/TypeScript/"/>
    
  </entry>
  
  <entry>
    <title>Initialize Terraform AWS State Resources</title>
    <link href="https://bliskavka.com/2024/09/12/initialize-terraform-aws-state/"/>
    <id>https://bliskavka.com/2024/09/12/initialize-terraform-aws-state/</id>
    <published>2024-09-12T19:09:52.000Z</published>
    <updated>2024-09-18T12:49:29.987Z</updated>
    
    <content type="html"><![CDATA[<p>So, I have a funny story. I was testing a new app in Terraform, and thought it would be a good idea to define my Terraform State bucket and lock table in the Terraform code. Everything went well, all my infrastructure was defined as code, and for a while everything was perfect…</p><span id="more"></span><p>Then I decided to move my app from <code>us-east-2</code> to <code>us-east-1</code>, so I ran <code>terraform destroy</code> and go walk my dog.</p><p>Lo and behold - that was a <strong>Noob</strong> move. Terraform deleted my state bucket and table too early, and the operation failed.</p><p>There is probably a way to make Terraform delete those resources last, but I think its safest to simply create them outside of your IaC. Here is a quick CLI script to make it simple.</p><h2 id="Create-Terraform-State-Resources-Commands"><a href="#Create-Terraform-State-Resources-Commands" class="headerlink" title="Create Terraform State Resources Commands"></a>Create Terraform State Resources Commands</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">aws s3api create-bucket --bucket my-tf-state</span><br><span class="line">aws s3api put-bucket-versioning --bucket my-tf-state --versioning-configuration Status=Enabled</span><br><span class="line">aws s3api put-bucket-encryption --bucket my-tf-state --server-side-encryption-configuration <span class="string">&#x27;&#123;</span></span><br><span class="line"><span class="string">  &quot;Rules&quot;: [</span></span><br><span class="line"><span class="string">    &#123;</span></span><br><span class="line"><span class="string">      &quot;ApplyServerSideEncryptionByDefault&quot;: &#123;</span></span><br><span class="line"><span class="string">        &quot;SSEAlgorithm&quot;: &quot;AES256&quot;</span></span><br><span class="line"><span class="string">      &#125;</span></span><br><span class="line"><span class="string">    &#125;</span></span><br><span class="line"><span class="string">  ]</span></span><br><span class="line"><span class="string">&#125;&#x27;</span></span><br><span class="line">aws dynamodb create-table \</span><br><span class="line">    --table-name my-tf-lock \</span><br><span class="line">    --attribute-definitions AttributeName=LockID,AttributeType=S \</span><br><span class="line">    --key-schema AttributeName=LockID,KeyType=HASH \</span><br><span class="line">    --billing-mode PAY_PER_REQUEST</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;So, I have a funny story. I was testing a new app in Terraform, and thought it would be a good idea to define my Terraform State bucket and lock table in the Terraform code. Everything went well, all my infrastructure was defined as code, and for a while everything was perfect…&lt;/p&gt;</summary>
    
    
    
    <category term="DevOps" scheme="https://bliskavka.com/categories/DevOps/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Terraform" scheme="https://bliskavka.com/tags/Terraform/"/>
    
  </entry>
  
  <entry>
    <title>Dynamic AWS IAM Policies</title>
    <link href="https://bliskavka.com/2024/03/02/dynamic-iam-policies/"/>
    <id>https://bliskavka.com/2024/03/02/dynamic-iam-policies/</id>
    <published>2024-03-02T12:00:34.000Z</published>
    <updated>2024-03-02T12:16:27.211Z</updated>
    
    <content type="html"><![CDATA[<p>We maintain a CloudFormation custom resource provider for Amazon Connect. The provider has grown organically, and as new features were added, the default role policy has become large.</p><p>The provider can do simple low-security tasks like <code>associateLambda</code>, or complex tasks like <code>createInstance</code>, which requires access to security-sensitive resources like <code>kms</code> and <code>iam</code>.</p><p>During a recent security review, we discovered that the same role policy was being used across all provider instances. This meant that if we used a low-security operation, such as <code>associateLambda</code>, the role would be granted access to high-security resources like <code>kms</code> and <code>iam</code>.</p><h2 id="Solution-1-Inject-a-Pre-Built-Role"><a href="#Solution-1-Inject-a-Pre-Built-Role" class="headerlink" title="Solution 1 - Inject a Pre-Built Role"></a>Solution 1 - Inject a Pre-Built Role</h2><p>For the current project, we resolved the issue by introducing an optional role prop. This allowed the developer to select specific IAM permissions.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PSEUDO-CODE</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConnectProvider</span> &#123;</span><br><span class="line">  <span class="attr">role</span>: <span class="title class_">IRole</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props: &#123;role?: IRole&#125;</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span>(!props.<span class="property">role</span>)&#123;</span><br><span class="line">      <span class="comment">// Configures the default (overly permissive) permissions</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = <span class="keyword">new</span> <span class="title class_">Role</span>(...);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="comment">// Uses the injected role</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = props.<span class="property">role</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Custom resource handler</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handler</span> = <span class="keyword">new</span> <span class="title class_">Function</span>(... &#123;<span class="attr">role</span>: <span class="variable language_">this</span>.<span class="property">role</span>&#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> role = <span class="keyword">new</span> <span class="title class_">Role</span>(...);</span><br><span class="line">role.<span class="title function_">addToPrincipalPolicy</span>(</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">    <span class="attr">effect</span>: <span class="title class_">Effect</span>.<span class="property">ALLOW</span>,</span><br><span class="line">    <span class="attr">actions</span>: [</span><br><span class="line">      <span class="string">&#x27;connect:AssociateLambdaFunction&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;connect:DisassociateLambdaFunction&#x27;</span>],</span><br><span class="line">    <span class="attr">resources</span>: [instanceArn]</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br><span class="line"><span class="keyword">const</span> provider = <span class="keyword">new</span> <span class="title class_">ConnectProvider</span>(&#123;role&#125;);</span><br><span class="line">provider.<span class="title function_">associateLambda</span>(...)</span><br></pre></td></tr></table></figure><h3 id="Pros"><a href="#Pros" class="headerlink" title="Pros"></a>Pros</h3><ul><li>We were able to quickly patch the current app</li></ul><h3 id="Cons"><a href="#Cons" class="headerlink" title="Cons"></a>Cons</h3><ul><li>Each dependent app would have to be updated manually. We have A LOT!</li><li>The app developer must know exactly which IAM permissions are required.</li></ul><h2 id="Solution-2-Dynamically-Generate-the-Role"><a href="#Solution-2-Dynamically-Generate-the-Role" class="headerlink" title="Solution 2 - Dynamically Generate the Role"></a>Solution 2 - Dynamically Generate the Role</h2><p>I updated the custom resource constructs to dynamically build up the policy based on which resources are used, so I could roll out the update in a backward-compatible way.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PSEUDO-CODE</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConnectProvider</span> &#123;</span><br><span class="line">  <span class="attr">role</span>: <span class="title class_">IRole</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props: &#123;role?: IRole&#125;</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span>(!props.<span class="property">role</span>)&#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = <span class="keyword">new</span> <span class="title class_">Role</span>(...);</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (props.<span class="property">role</span> <span class="keyword">instanceof</span> <span class="title class_">Role</span>)&#123;</span><br><span class="line">      <span class="comment">// Convert to IRole to avoid manipulating the role</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = <span class="title class_">Role</span>.<span class="title function_">fromArn</span>(props.<span class="property">role</span>.<span class="property">roleArn</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = props.<span class="property">role</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Custom resource handler</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handler</span> = <span class="keyword">new</span> <span class="title class_">Function</span>(... &#123;<span class="attr">role</span>: <span class="variable language_">this</span>.<span class="property">role</span>&#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Users call helper functions to create the custom resource</span></span><br><span class="line">  <span class="title function_">associateLambda</span>(<span class="params">id, instanceArn, lambda</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="variable language_">this</span>.<span class="property">role</span> <span class="keyword">instanceof</span> <span class="title class_">Role</span>)&#123;</span><br><span class="line">      <span class="comment">// Dynamically update self-managed role</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span>.<span class="title function_">addToPrincipalPolicy</span>(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">          <span class="attr">effect</span>: <span class="title class_">Effect</span>.<span class="property">ALLOW</span>,</span><br><span class="line">          <span class="attr">actions</span>: [</span><br><span class="line">            <span class="string">&#x27;connect:AssociateLambdaFunction&#x27;</span>,</span><br><span class="line">            <span class="string">&#x27;connect:DisassociateLambdaFunction&#x27;</span>],</span><br><span class="line">          <span class="attr">resources</span>: [instanceArn]</span><br><span class="line">        &#125;)</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CustomResource</span>(&#123;</span><br><span class="line">      <span class="attr">serviceToken</span>: <span class="variable language_">this</span>.<span class="property">handler</span>.<span class="property">functionArn</span>,</span><br><span class="line">      <span class="attr">properties</span>: &#123;</span><br><span class="line">        instanceArn,</span><br><span class="line">        <span class="attr">functionArn</span>: lambda.<span class="property">functionArn</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Pros-1"><a href="#Pros-1" class="headerlink" title="Pros"></a>Pros</h3><ul><li>No manual intervention is needed for dependent apps. Simply upgrade the NPM package and redeploy.</li></ul><h3 id="Cons-1"><a href="#Cons-1" class="headerlink" title="Cons"></a>Cons</h3><ul><li>Resource deletion does not work properly.<ul><li>If you had a custom resource like <code>associateLambda</code>, everything works fine because the role policy is updated before the resource is created.</li><li>But if you remove the custom resource in a future release, CloudFormation will update the role policy first (and remove the associated permission) before cleaning up the resource.</li><li>As a result, you encounter a permission error when cleaning up the <code>associateLambda</code> resource</li></ul></li><li>Circular dependencies<ul><li>If you used the provider to <code>createInstance</code> and then used the instance ARN in another construct like <code>associateLambda</code> you will encounter a circular reference</li><li>Details<ul><li>Invoke <code>createInstance</code> and get instance ARN</li><li>Invoke <code>associateLambda</code> using instance ARN<ul><li>Instance ARN is used in the dynamic policy, resulting in a circular reference</li></ul></li></ul></li></ul></li></ul><h2 id="Solution-3-Mix-of-both"><a href="#Solution-3-Mix-of-both" class="headerlink" title="Solution 3 - Mix of both"></a>Solution 3 - Mix of both</h2><p>In the end, I decided to use a combination of both solutions. I created a <code>ConnectProviderRoleBuilder</code> to make it easier for developers to build the role.</p><p>Additionally, I also updated the <code>ConnectProvider</code> to automatically use the builder if a role is not provided.</p><p>This means that we can update existing apps without any manual intervention. If the app encounters the issues described in Solution 2 during ongoing development, the team can use the <code>ConnectProviderRoleBuilder</code> to generate an appropriate role quickly.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// PSEUDO-CODE</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConnectProviderRoleBuilder</span> &#123;</span><br><span class="line">  <span class="attr">role</span>: <span class="title class_">IRole</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">    * Tracks if the provider was used to create an instance.</span></span><br><span class="line"><span class="comment">    * If so, we cannot limit role permissions to a specific instance</span></span><br><span class="line"><span class="comment">    * due to circular dependency.</span></span><br><span class="line"><span class="comment">    */</span></span><br><span class="line">  <span class="keyword">private</span> <span class="attr">createdInstance</span>: <span class="built_in">boolean</span> = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props: &#123;existingRole?: IRole&#125;</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span>(!props.<span class="property">role</span>)&#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = <span class="keyword">new</span> <span class="title class_">Role</span>(...)</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span>(props.<span class="property">role</span> <span class="keyword">instanceof</span> <span class="title class_">Role</span>)&#123;</span><br><span class="line">      <span class="comment">// Ensures role is not manipulated by the builder</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = <span class="title class_">Role</span>.<span class="title function_">fromArn</span>(props.<span class="property">existingRole</span>.<span class="property">roleArn</span>)</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span> = props.<span class="property">existingRole</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">  * Create an instance ARN for permission filtering</span></span><br><span class="line"><span class="comment">  * If the provider was used to create the instance the ARN will be</span></span><br><span class="line"><span class="comment">  * `instance/*` to avoid circular dependency error</span></span><br><span class="line"><span class="comment">  * Assumes this provider will operate on a single instance.</span></span><br><span class="line"><span class="comment">  */</span></span><br><span class="line">  <span class="title function_">instanceArn</span>(<span class="attr">instanceId</span>: <span class="built_in">string</span>): <span class="built_in">string</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">createdInstance</span>) &#123;</span><br><span class="line">      <span class="comment">// We can&#x27;t reference the instanceId (circular ref)</span></span><br><span class="line">      <span class="keyword">return</span> <span class="string">`arn:aws:connect:<span class="subst">$&#123;region&#125;</span>:<span class="subst">$&#123;account&#125;</span>:instance/*`</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">`arn:aws:connect:<span class="subst">$&#123;region&#125;</span>:<span class="subst">$&#123;account&#125;</span>:instance/<span class="subst">$&#123;instanceId&#125;</span>`</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">allow</span>(<span class="params">actions, resources</span>)&#123;</span><br><span class="line">    <span class="keyword">if</span>(<span class="variable language_">this</span>.<span class="property">role</span> <span class="keyword">instanceof</span> <span class="title class_">Role</span>)&#123;</span><br><span class="line">      <span class="comment">// Only add permissions if the role is being managed by the construct.</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">role</span>.<span class="title function_">addToPrincipalPolicy</span>(</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">          <span class="attr">effect</span>: <span class="title class_">Effect</span>.<span class="property">ALLOW</span>,</span><br><span class="line">          actions,</span><br><span class="line">          resources</span><br><span class="line">        &#125;)</span><br><span class="line">      );</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Helpers to add policy statements</span></span><br><span class="line">  <span class="title function_">allowAssociateLambda</span>(<span class="params">instanceId, ...functionArns</span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">allow</span>([</span><br><span class="line">      <span class="string">&#x27;connect:AssociateLambdaFunction&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;connect:DisassociateLambdaFunction&#x27;</span>],</span><br><span class="line">      [<span class="variable language_">this</span>.<span class="title function_">instanceArn</span>(instanceId)]</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Update lambda resource policy to allow connect invoke</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">allowCreateInstance</span>(<span class="params"></span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">createdInstance</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">allow</span>(...)</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">ConnectProvider</span> &#123;</span><br><span class="line">  <span class="attr">builder</span>: <span class="title class_">ConnectProviderRoleBuilder</span>;</span><br><span class="line">  <span class="attr">role</span>: <span class="title class_">IRole</span>;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">props: &#123;role?: IRole&#125;</span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">builder</span> = <span class="keyword">new</span> <span class="title class_">ConnectProviderRoleBuilder</span>(&#123;<span class="attr">role</span>: props.<span class="property">role</span>&#125;)</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">role</span> = <span class="variable language_">this</span>.<span class="property">builder</span>.<span class="property">role</span>;</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">handler</span> = <span class="keyword">new</span> <span class="title class_">Function</span>(... &#123;<span class="attr">role</span>: <span class="variable language_">this</span>.<span class="property">role</span>&#125;)</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">associateLambda</span>(<span class="params">instanceId, lambda</span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">builder</span>.<span class="title function_">allowAssociateLambda</span>(instanceId, lambda.<span class="property">functionArn</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">CustomResource</span>(&#123;</span><br><span class="line">      <span class="attr">serviceToken</span>: <span class="variable language_">this</span>.<span class="property">handler</span>.<span class="property">functionArn</span>,</span><br><span class="line">      <span class="attr">properties</span>: &#123;</span><br><span class="line">        instanceId,</span><br><span class="line">        <span class="attr">functionArn</span>: lambda.<span class="property">functionArn</span></span><br><span class="line">      &#125;</span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">myLambda</span>: <span class="title class_">IFunction</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Pre-build the role</span></span><br><span class="line"><span class="keyword">const</span> builder = <span class="keyword">new</span> <span class="title class_">ConnectProviderRoleBuilder</span>()</span><br><span class="line">builder.<span class="title function_">allowAssociateLambda</span>(instanceId, myLambda.<span class="property">functionArn</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> provider = <span class="keyword">new</span> <span class="title class_">ConnectProvider</span>(&#123;<span class="attr">role</span>: builder.<span class="property">role</span>&#125;)</span><br><span class="line">provider.<span class="title function_">associateLambda</span>(instanceId, myLambda)</span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>The simplest solution would have been to simply force the developer to inject a role but it would have created unnecessary developer friction because:</p><ul><li>My app used to deploy fine, but now I have to manually create a new role.</li><li>I have no idea what is happening under the hood and which permissions are required, resulting in even more friction.</li></ul><p>This solution was certainly more work, but it solved the problem with the least effort from the downstream developers.</p><p>No, go build secure and elegant tools!</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;We maintain a CloudFormation custom resource provider for Amazon Connect. The provider has grown organically, and as new features were</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Connect" scheme="https://bliskavka.com/tags/Connect/"/>
    
    <category term="Security" scheme="https://bliskavka.com/tags/Security/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>My Blogging Tools</title>
    <link href="https://bliskavka.com/2024/02/25/blogging-platform/"/>
    <id>https://bliskavka.com/2024/02/25/blogging-platform/</id>
    <published>2024-02-25T15:54:44.000Z</published>
    <updated>2024-02-26T18:53:53.126Z</updated>
    
    <content type="html"><![CDATA[<p>If you are looking for a low-cost blogging platform on AWS, look no further!</p><p>I wanted something very easy to use, which has a lot of features and uses my core tools (AWS, Node, CDK, Markdown).</p><p>For this blog, I settled on <a href="https://hexo.io/docs/index.html">Hexo</a>, a static site generator. I have also used the more popular <a href="https://gohugo.io/">Hugo</a> which solves the same problem.</p><p>For hosting, I am using AWS S3 with CloudFront and Route53. Deployed using CDK.</p><p>This setup is pretty easy if you are comfortable with Node and CDK. If you are not, you can refer to <a href="https://github.com/ibliskavka/bliskavka-com/tree/main">my blog repo on GitHub</a>.</p><h2 id="Prerequisites"><a href="#Prerequisites" class="headerlink" title="Prerequisites"></a>Prerequisites</h2><ul><li>AWS Account</li><li>AWS CLI Access</li><li>Familiarity with NodeJS</li><li>Familiarity with CDK is a plus</li></ul><h2 id="Highlights"><a href="#Highlights" class="headerlink" title="Highlights"></a>Highlights</h2><ul><li><a href="https://hexo.io/docs/index.html">Init a new Hexo app</a></li><li><code>npm install aws-cdk aws-cdk-lib</code></li><li>Create a <a href="https://github.com/ibliskavka/bliskavka-com/blob/main/cdk/index.ts">cdk&#x2F;index.ts</a> entry file<ul><li>Change the string parameters to match your environment</li><li>Assuming Route53 domain and ACM Cert already exist. If not, comment out those resources to use the CloudFront URL.</li></ul></li><li>Create a <a href="https://github.com/ibliskavka/bliskavka-com/blob/main/cdk.json">cdk.json</a> file</li><li>Create the helper scripts in <a href="https://github.com/ibliskavka/bliskavka-com/blob/main/package.json">package.json</a><ul><li>The publish script parameters (BucketName &amp; DistributionId) will not be available until you run the deploy</li><li><a href="https://bliskavka.com/2023/03/02/cdk-package-scripts/">More info on CDK package scripts</a></li></ul></li></ul><h2 id="Extensions"><a href="#Extensions" class="headerlink" title="Extensions"></a>Extensions</h2><p>Be sure to install a VS Code extension like <a href="https://marketplace.visualstudio.com/items?itemName=znck.grammarly">Grammarly</a>! I thought I was a pretty good writer, but after I installed it I had to go back and touch almost every post!</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;If you are looking for a low-cost blogging platform on AWS, look no further!&lt;/p&gt;
&lt;p&gt;I wanted something very easy to use, which has a lot</summary>
        
      
    
    
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
    <category term="Writing" scheme="https://bliskavka.com/tags/Writing/"/>
    
  </entry>
  
  <entry>
    <title>CDK Resolution error: PolicySynthesizer should be created in the scope of a Stack, but no Stack found</title>
    <link href="https://bliskavka.com/2024/02/16/cdk-customize-roles-resolution-error-policy-synthesizer/"/>
    <id>https://bliskavka.com/2024/02/16/cdk-customize-roles-resolution-error-policy-synthesizer/</id>
    <published>2024-02-16T15:47:00.000Z</published>
    <updated>2024-02-27T13:21:36.846Z</updated>
    
    <content type="html"><![CDATA[<p>We occasionally have a client who does not allow us to create IAM roles in their AWS account. In this scenario we must define the roles in CloudFormation, they create them, and we inject the role ARNs into the app.</p><p>In the CloudFormation days, this would have been a significant overhaul if your app was not already set up for it. But with CDK, this is pretty easy with the <a href="https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide#using-the-customize-roles-feature-to-generate-a-report-and-supply-role-names">Role.customizeRoles</a> method.</p><p>While working on such a project, we bumped into this weird error:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Error: Resolution error: Resolution error: PolicySynthesizer at &#x27;PolicySynthesizer&#x27; should be created in the scope of a Stack, but no Stack found.</span><br><span class="line">Object creation stack:</span><br><span class="line">  at stack traces disabled.</span><br><span class="line">Object creation stack:</span><br><span class="line">  at Execute again with CDK_DEBUG=true to capture stack traces</span><br><span class="line">    at _lookup (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/stack.js:1:3005)</span><br><span class="line">    at _lookup (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/stack.js:1:3178)</span><br><span class="line">    at Function.of (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/stack.js:1:2736)</span><br><span class="line">    at Object.produce (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/resource.js:1:4264)</span><br><span class="line">    at Reference.resolve (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/resource.js:1:4877)</span><br><span class="line">    at DefaultTokenResolver.resolveToken (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/resolvable.js:1:1401)</span><br><span class="line">    at resolve (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/private/resolve.js:1:2711)</span><br><span class="line">    at Object.resolve [as mapToken] (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/private/resolve.js:1:1079)</span><br><span class="line">    at TokenizedStringFragments.mapTokens (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/string-fragments.js:1:1475)</span><br><span class="line">    at DefaultTokenResolver.resolveString (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/resolvable.js:4:362)</span><br></pre></td></tr></table></figure><p>The above error doesn’t provide any meaningful info, but if you follow the instructions like so:</p><p><code>CDK_DEBUG=true npm run cdk -- synth</code></p><p>You get a much more meaningful error</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">Error: Resolution error: Resolution error: PolicySynthesizer at &#x27;PolicySynthesizer&#x27; should be created in the scope of a Stack, but no Stack found.</span><br><span class="line">Object creation stack:</span><br><span class="line">  at new Intrinsic (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/private/intrinsic.js:1:942)</span><br><span class="line">  at new Reference (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/reference.js:1:599)</span><br><span class="line">  at new &lt;anonymous&gt; (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/resource.js:1:4806)</span><br><span class="line">  at mimicReference (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/resource.js:1:4802)</span><br><span class="line">  at CacheTable.getResourceArnAttribute (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/resource.js:1:4185)</span><br><span class="line">  at new Table (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/aws-dynamodb/lib/table.js:1:18313)</span><br><span class="line">  at new CacheTable (/home/ibliskavka/git/data-lakedata/packages/caching/src/constructs/CacheTable.ts:20:5)</span><br><span class="line">  ... (truncated)</span><br><span class="line">Object creation stack:</span><br><span class="line">  at Function.string (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/core/lib/lazy.js:1:953)</span><br><span class="line">  at CacheTable.combinedGrant (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/aws-dynamodb/lib/table.js:1:13211)</span><br><span class="line">  at CacheTable.grantReadWriteData (/home/ibliskavka/git/data-lakedata/node_modules/aws-cdk-lib/aws-dynamodb/lib/table.js:1:6157)</span><br><span class="line">  ... (truncated)</span><br></pre></td></tr></table></figure><p>The <code>CacheTable.grantReadWriteData</code> stack trace deals with permissions (which we are customizing), so I traced it down to this bit of code</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">CachingLambdaRole</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Role</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span>, props: CachingLambdaRoleProps</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id, &#123;</span><br><span class="line">      <span class="attr">assumedBy</span>: <span class="keyword">new</span> <span class="title class_">ServicePrincipal</span>(<span class="string">&#x27;lambda.amazonaws.com&#x27;</span>),</span><br><span class="line">    &#125;);</span><br><span class="line">    props.<span class="property">table</span>.<span class="title function_">grantReadWriteData</span>(<span class="variable language_">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>When I replaced <code>props.table.grantReadWriteData(this)</code> with policy statements in the role <code>inlinePolicy</code> the error went away and I was able to deploy.</p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR;"></a>TL;DR;</h2><ol><li>Enable CDK_DEBUG</li><li>Check if your stack trace uses the <code>grant</code> API. If so, try replacing it with policy statements.</li></ol><h2 id="More-Info"><a href="#More-Info" class="headerlink" title="More Info"></a>More Info</h2><p>I was able to find only a few articles about this error message and none of them dealt with <code>customizeRoles</code>. I suspect this may be a bug in the CDK code because my app uses the <code>grant</code> API elsewhere in the code and it works as-is.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;We occasionally have a client who does not allow us to create IAM roles in their AWS account. In this scenario we must define the roles</summary>
        
      
    
    
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/Troubleshooting/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>Bliskavka.com Syndication Policy</title>
    <link href="https://bliskavka.com/2024/01/01/syndication-policy/"/>
    <id>https://bliskavka.com/2024/01/01/syndication-policy/</id>
    <published>2024-01-01T18:37:41.000Z</published>
    <updated>2024-02-26T18:42:20.913Z</updated>
    
    <content type="html"><![CDATA[<p>If you found a post useful and would like to repost it on your site: Go for it!</p><p>All I ask is that you attribute <a href="https://bliskavka.com/">bliskavka.com</a> as the original source of the article. If it’s on the web, please provide a link back to the original article on <a href="https://bliskavka.com/">bliskavka.com</a>.</p><span id="more"></span><p>I think that’s fair. Free content for your website in exchange for a link back.</p><p>Thanks!<br>Ivan Bliskavka<br>Author</p><hr><p><em>Syndication policy idea attribution: <a href="https://www.universetoday.com/81610/universe-today-syndication-policy-steal-our-content-please/">Universe Today</a></em> ;)</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;If you found a post useful and would like to repost it on your site: Go for it!&lt;/p&gt;
&lt;p&gt;All I ask is that you attribute &lt;a href=&quot;https://bliskavka.com/&quot;&gt;bliskavka.com&lt;/a&gt; as the original source of the article. If it’s on the web, please provide a link back to the original article on &lt;a href=&quot;https://bliskavka.com/&quot;&gt;bliskavka.com&lt;/a&gt;.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Writing" scheme="https://bliskavka.com/tags/Writing/"/>
    
  </entry>
  
  <entry>
    <title>Achieved: 250lb Bench Press Goal</title>
    <link href="https://bliskavka.com/2023/10/26/achieved-250lb-bench-press-goal/"/>
    <id>https://bliskavka.com/2023/10/26/achieved-250lb-bench-press-goal/</id>
    <published>2023-10-26T13:03:40.000Z</published>
    <updated>2024-02-25T19:57:57.966Z</updated>
    
    <content type="html"><![CDATA[<p>For my 2022 New Year’s resolution, I decided on: 250 lb for 1 rep on the bench press. Almost 2 years later, I finally got it.</p><span id="more"></span><h2 id="Retrospective"><a href="#Retrospective" class="headerlink" title="Retrospective"></a>Retrospective</h2><ul><li><p>Milestones (PR &amp; body weight)</p><ul><li>12&#x2F;30&#x2F;21 - 212.5 @ ~163lb</li><li>In March ‘22, my friend Noel commented that if I wanted to hit my goal should be eating even if I had no appetite.</li><li>6&#x2F;16&#x2F;22 - 215 @ ~175lb</li><li>11&#x2F;28&#x2F;22 - 225 @ ~176lb</li><li>3&#x2F;16&#x2F;23 - 230 @ ~176lb</li><li>6&#x2F;5&#x2F;23 - 235 @ ~176lb</li><li>9&#x2F;7&#x2F;23 - 240 @ ~176lb</li><li>10&#x2F;3&#x2F;23 - 242.5 @ ~176lb</li><li>10&#x2F;26&#x2F;23 - 250 @ ~179lb</li></ul></li><li><p>I tried to maintain a lean physique which slowed down my gains</p></li><li><p>I have been doing 100+ pushups every day since last August. I think has kept me near my peak performance, so I hit PRs more often.</p><ul><li>Previously my PRs were 3-6 months apart because I would slack off for a bit after each milestone</li></ul></li><li><p>It’s super important to keep a log and a consistent routine</p></li><li><p>It’s less important to work near your maximum weight when not working towards a PR.</p><ul><li>Instead do 5 sets at 5 reps at 80% of max. Increase reps or weight slowly over a couple of weeks.</li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;For my 2022 New Year’s resolution, I decided on: 250 lb for 1 rep on the bench press. Almost 2 years later, I finally got it.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Fun" scheme="https://bliskavka.com/tags/Fun/"/>
    
  </entry>
  
  <entry>
    <title>TechHub Hackathon 2023</title>
    <link href="https://bliskavka.com/2023/10/02/tech-hub-hackathon-2023/"/>
    <id>https://bliskavka.com/2023/10/02/tech-hub-hackathon-2023/</id>
    <published>2023-10-02T14:27:04.000Z</published>
    <updated>2024-02-25T22:19:02.090Z</updated>
    
    <content type="html"><![CDATA[<p>We pulled together a team on Friday night, built an app on Saturday, and presented on Sunday. Not a win, but what a great experience!</p><span id="more"></span><h2 id="The-Team"><a href="#The-Team" class="headerlink" title="The Team"></a>The Team</h2><p><img src="store-front.jpg" alt="Juliana, Divyesh, Pavan, Ivan, Daniel"></p><h2 id="Project-Description"><a href="#Project-Description" class="headerlink" title="Project Description"></a>Project Description</h2><p>Habitat for Humanity, a renowned non-profit organization dedicated to building homes and better communities, faces several operational challenges that your hackathon team can help address:</p><p><strong>Targeting New Audiences&#x2F;Real-Time Inventory Management</strong>: Implement a solution that updates the online inventory in real-time, ensuring that customers are aware of available items and reducing discrepancies.</p><h2 id="Background"><a href="#Background" class="headerlink" title="Background"></a>Background</h2><p>Before starting any design work, our team decided to take a field trip to the Broward Restore, which was 30 minutes away.</p><p>The store manager Mark, was very gracious and spent 30 minutes talking about the store, the mission, the volunteers, and the amazing items that pass through his store.</p><p><img src="store-manager.jpg" alt="Selfie with Mark, the Store Manager"></p><h3 id="Some-interesting-points"><a href="#Some-interesting-points" class="headerlink" title="Some interesting points"></a>Some interesting points</h3><ul><li>Each store operates like a franchise and typically manages its own outreach, website, etc.</li><li>Mark just came back from a conference with other ReStores where they shared ideas and approaches.</li><li>Some stores (Los Angeles) have an excellent social media presence including viral videos on TikTok.</li><li>Independent stores can have their own website, but there is no central platform for posting or managing inventory.</li><li>The Broward store does not have a website, but they have a Facebook page.</li><li>They move a lot of furniture, and the entire sales floor is typically replaced in a couple of weeks. Because each item is unique, maintaining a website inventory is a lot of overhead.</li></ul><h3 id="Donation-Receiving"><a href="#Donation-Receiving" class="headerlink" title="Donation Receiving"></a>Donation Receiving</h3><p>Mark said that 54% of donations are corporate. Typically the item is scratched, dented, or discontinued.</p><p>He typically uses Google Lens to scan the item to get a feel for what the item sells for. From this price, he discounts about 50%.</p><h2 id="Proposal"><a href="#Proposal" class="headerlink" title="Proposal"></a>Proposal</h2><ul><li>A SaaS-like platform that is owned and managed by the Habitat for Humanity “Corporate”</li><li>Each store is a “tenant” on the platform and can manage its own inventory.</li><li>The inventory is streamlined using a web app, on a mobile phone<ul><li>Receiving takes a photo and adds a quality rating.</li><li>The app uploads a photo to the cloud and uses image recognition to identify the product.</li><li>If the product is IDed, meta-data and trending price is returned to the app.<ul><li>Price is automatically discounted based on quality rating: 3-50%, 2-60%, 1-70%</li></ul></li><li>If the item is not IDed, the app prompts for metadata and price.</li><li>Idea: If the current user is not the manager, the item will need to be approved by the manager.<ul><li>Once approved, the item automatically shows up on the website and in the Point of Sale system.</li></ul></li><li>A QR Code sticker is printed and attached to the item. (Bluetooth printer)</li><li>When the item is sold, the sticker is scanned, and it is automatically removed from the website.</li></ul></li><li>The SaaS platform dynamically builds a storefront for each tenant<ul><li>Adds the ability to search all stores in the vicinity.</li><li>Customers can buy online and pick up in the store.<ul><li>Orders not picked up within X days are a donation.</li><li>Online customer contact data can be imported into Outreach programs.</li></ul></li></ul></li></ul><h3 id="Technical-Architecture"><a href="#Technical-Architecture" class="headerlink" title="Technical Architecture"></a>Technical Architecture</h3><p>With 900 stores across the country and hundreds of thousands of customers, we need to consider scalability. We chose to build using managed services in the cloud for resilience, durability, and scalability.</p><p><img src="architecture.png" alt="Architecture"></p><h2 id="Result"><a href="#Result" class="headerlink" title="Result"></a>Result</h2><iframe width="560" height="315" src="https://www.youtube.com/embed/u8ERNvwUoOA?si=S43T59aDLuo4ddoD" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe><p>Unfortunately we didn’t win. Here are some presentation takeaways for future reference.</p><ul><li><del>Going first was a disadvantage. We didn’t know what to expect, and probably the judges didn’t either.</del><ul><li><del>Even if our presentation was a 10&#x2F;10, there is no way a judge would assign that to the first presentation without seeing all the other stuff. And there is no way they remembered ours after 16 presentations.</del></li><li>[Correction 10&#x2F;3&#x2F;23]: Going first was a challenge, we were up for it, and we did great!</li></ul></li><li>We should have figured out how to screen share an iPhone. Seeing it on a phone would be more impressive.</li><li>Personally, I was more focused on the practicality of the idea and our ability to execute (in 8-12) hours<ul><li>Since only 2 of the judges were asking technical questions, I assume the others were more business-minded. They weren’t judging on “can these people do it”, they were judging on “as described, will this idea impact a lot of people?”</li><li>Go for the big idea - not on the technical feasibility.</li></ul></li><li>Reverse Image Search using an AI model, we should have thrown in that extra jargon.</li></ul><h2 id="Links"><a href="#Links" class="headerlink" title="Links"></a>Links</h2><ul><li><a href="https://d8ftny2i4nqz0.cloudfront.net/capture">Demo Site</a></li><li><a href="ScreenRecording.mp4">App Recording</a></li><li><a href="https://github.com/PradatiusD/habitat-restore">Code Repo</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;We pulled together a team on Friday night, built an app on Saturday, and presented on Sunday. Not a win, but what a great experience!&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Hackathon" scheme="https://bliskavka.com/tags/Hackathon/"/>
    
    <category term="TechHub" scheme="https://bliskavka.com/tags/TechHub/"/>
    
  </entry>
  
  <entry>
    <title>10x API Start-up Boost</title>
    <link href="https://bliskavka.com/2023/09/20/10x-api-start-time-boost/"/>
    <id>https://bliskavka.com/2023/09/20/10x-api-start-time-boost/</id>
    <published>2023-09-21T00:03:19.000Z</published>
    <updated>2024-02-25T19:57:57.961Z</updated>
    
    <content type="html"><![CDATA[<p>I while ago I optimized my <a href="https://bliskavka.com/Screen-Saver-Gallery">Screen Saver Gallery</a> API by loading a flat data file into lambda memory. A nightly job selects a random subset of the database and stores it in S3, and the API uses that file for the next 24 hours.</p><p>This simplified my code, reduced costs, and improved performance.</p><p>The average API calls took 50-100ms! …but the start-up time was <strong>5 seconds</strong>!!!</p><p>I was using a lambda warmer, so most clients didn’t hit the 5-second latency, but I wondered if I could reduce it.</p><p><strong>TL;DR;</strong> Store an uncompressed JSON file in a Lambda Layer and rebuild it periodically</p><h2 id="Before"><a href="#Before" class="headerlink" title="Before"></a>Before</h2><ol><li><p>A nightly process reads data from Dynamo DB and generates a zip file that looks like this:</p><ul><li>cache.zip<ul><li>tableA.json</li><li>tableB.json</li></ul></li></ul></li><li><p>The zip file is stored in S3</p></li><li><p>When the lambda starts, it loads the zip file from S3, and loads it into memory (Duration: 4-5 seconds)</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> <span class="attr">cache</span>: <span class="title class_">Record</span>&lt;<span class="built_in">string</span>, <span class="title class_">ITable</span>&gt;;</span><br></pre></td></tr></table></figure></li><li><p>All future requests are served from the in-memory cache (50-100ms)</p></li></ol><h2 id="Attempt-1"><a href="#Attempt-1" class="headerlink" title="Attempt 1"></a>Attempt 1</h2><p>Instead of loading from S3, store the zip file in a lambda layer, and rebuild the layer every night.</p><p>Result: This reduced the start-up by 1 second.</p><h2 id="Attempt-2"><a href="#Attempt-2" class="headerlink" title="Attempt 2"></a>Attempt 2</h2><p>The layer improved the load time, so it was a good start, but I felt like it should be faster.</p><p>I added some basic timers to my warmup function and discovered that almost 3 seconds were spent parsing the 217 files from the zip file.</p><p>I updated the code to parse the table files in parallel but it only shaved off 500ms.</p><h2 id="Attempt-3"><a href="#Attempt-3" class="headerlink" title="Attempt 3"></a>Attempt 3</h2><p>Armed with the timing information, I understood that my load issue was computationally constrained - decompressing zip files requires CPU cycles.</p><p>I refactored my lambda layer cache to be a single <code>cache.json</code> file that looks like this</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;tableA&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;tableB&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>The lambda can load the data using <code>readFileSync(&#39;/opt/cache.json&#39;)</code> and parse it immediately.</p><p>Presto: Start-up load time is 500ms - a 10x improvement!</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>If your data is relatively static you can cache it as a flat file in a Lambda layer to reduce app complexity, costs, and startup times. Use a Lambda to periodically query a new data set and publish a new Lambda Layer Version.</p><p>When using layers, your maximum package size is 250MB. By compressing your data files you can cache A LOT of data, but you will experience decompression latency.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I while ago I optimized my &lt;a href=&quot;https://bliskavka.com/Screen-Saver-Gallery&quot;&gt;Screen Saver Gallery&lt;/a&gt; API by loading a flat data file</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Lambda" scheme="https://bliskavka.com/tags/Lambda/"/>
    
  </entry>
  
  <entry>
    <title>FloridaJS: Amazon Connect and Lex</title>
    <link href="https://bliskavka.com/2023/08/22/floridajs-usergroup-amazon-connect-and-lex/"/>
    <id>https://bliskavka.com/2023/08/22/floridajs-usergroup-amazon-connect-and-lex/</id>
    <published>2023-08-22T15:26:01.000Z</published>
    <updated>2024-02-25T19:57:57.970Z</updated>
    
    <content type="html"><![CDATA[<p>I will be presenting at FloridaJS on Amazon Connect and Lex.</p><p><a href="https://www.meetup.com/floridajs/events/295750011/">Sign up here</a></p><h2 id="Details"><a href="#Details" class="headerlink" title="Details"></a>Details</h2><p>This is an IN-PERSON event</p><p>AI from the big three providers can make it a snap to quickly create what you need before trying to roll it yourselves.</p><p>This month: Ivan Bliskavka, a 7x Amazon Web Services Certified, will show us Amazon Lex and Connect to see what you can do with these services.</p><p>Amazon Lex is a Natural Language Understanding chat&#x2F;voice bot that can simplify a complex inbound phone menu system by matching common phrases (and their variants) with user intents.</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><ul><li>Create an Amazon Connect instance and claim a phone number.</li><li>Create a simple touch-tone menu that can route the call to the appropriate agent.<ul><li>i.e.: Press 1 for purchasing, press 2 for technical support, and press 3 for the billing department.</li></ul></li><li>Enhance the menu by replacing it with an Amazon Lex Bot, which understands natural language to route to the appropriate agent.<ul><li>i.e.: “Connect me to purchasing”, “I want to buy ${x}”, or “I need a quote on ${x}” will route to the purchasing department.</li><li>i.e.: “I need help”, “My ${x} is not working”, or “This computer is stupid” will route to the helpdesk.</li></ul></li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I will be presenting at FloridaJS on Amazon Connect and Lex.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.meetup.com/floridajs/events/295750011/&quot;&gt;Sign up</summary>
        
      
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Training" scheme="https://bliskavka.com/tags/Training/"/>
    
  </entry>
  
  <entry>
    <title>AWS Certified SysOps Associate</title>
    <link href="https://bliskavka.com/2023/07/27/sysops-associate/"/>
    <id>https://bliskavka.com/2023/07/27/sysops-associate/</id>
    <published>2023-07-27T15:08:41.000Z</published>
    <updated>2024-02-25T16:15:12.023Z</updated>
    
    <content type="html"><![CDATA[<p>I am now an <a href="https://www.credly.com/badges/5e49b619-d67d-4768-9ff3-a7ce4f33db1b">AWS Certified SysOps Administrator - Associate</a>!</p><p><img src="cert%C3%A7.png" alt="AWS Certified SysOps Administrator"></p><p>5 years of AWS experience adds up. I only studied about 5 hours for the test using these <a href="https://www.udemy.com/course/practice-exams-aws-certified-sysops-administrator-associate">Udemy practice exams</a>.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I am now an &lt;a href=&quot;https://www.credly.com/badges/5e49b619-d67d-4768-9ff3-a7ce4f33db1b&quot;&gt;AWS Certified SysOps Administrator -</summary>
        
      
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>AWS SAM Multi-Region Packaging Script</title>
    <link href="https://bliskavka.com/2023/07/20/aws-sam-multi-region-packaging-script/"/>
    <id>https://bliskavka.com/2023/07/20/aws-sam-multi-region-packaging-script/</id>
    <published>2023-07-20T15:19:24.000Z</published>
    <updated>2024-02-25T16:15:12.001Z</updated>
    
    <content type="html"><![CDATA[<p>I use this bash loop to package an AWS SAM template to multiple regions.</p><p>This script stages the CloudFormation template and assets in a regional bucket. You can share the bucket with other accounts via Bucket Policy.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Pass stage as command line parameter</span></span><br><span class="line">STAGE=<span class="variable">$1</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Load package version</span></span><br><span class="line">VERSION=$(jq -r .version package.json)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Listing all regions that support Amazon Connect</span></span><br><span class="line"><span class="built_in">declare</span> -a regions=(<span class="string">&quot;us-east-1&quot;</span> <span class="string">&quot;us-west-2&quot;</span> <span class="string">&quot;ap-southeast-1&quot;</span> <span class="string">&quot;ap-southeast-2&quot;</span> <span class="string">&quot;ap-northeast-1&quot;</span> <span class="string">&quot;ca-central-1&quot;</span> <span class="string">&quot;eu-central-1&quot;</span> <span class="string">&quot;eu-west-2&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> REGION <span class="keyword">in</span> <span class="string">&quot;<span class="variable">$&#123;regions[@]&#125;</span>&quot;</span></span><br><span class="line"><span class="keyword">do</span></span><br><span class="line"></span><br><span class="line">   <span class="keyword">if</span> [ <span class="variable">$STAGE</span> == <span class="string">&quot;dev&quot;</span> ] &amp;&amp; [ <span class="variable">$REGION</span> != <span class="string">&quot;us-east-1&quot;</span> ]; <span class="keyword">then</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Only deploy to us-east-1 in dev</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$STAGE</span>: Skipping <span class="variable">$REGION</span>&quot;</span></span><br><span class="line"></span><br><span class="line">   <span class="keyword">else</span></span><br><span class="line"></span><br><span class="line">    BUCKET=<span class="string">&quot;my-app-<span class="variable">$&#123;STAGE&#125;</span>-<span class="variable">$&#123;REGION&#125;</span>&quot;</span></span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$STAGE</span>: Publishing <span class="variable">$BUCKET</span>&quot;</span></span><br><span class="line"></span><br><span class="line">    sam package -t template.yaml --s3-bucket <span class="variable">$BUCKET</span> --s3-prefix <span class="variable">$VERSION</span> --output-template-file .aws-sam/packaged.yaml --region <span class="variable">$REGION</span></span><br><span class="line">    aws s3 <span class="built_in">cp</span> .aws-sam/packaged.yaml s3://<span class="variable">$BUCKET</span>/<span class="variable">$VERSION</span>/template.yaml --region <span class="variable">$REGION</span></span><br><span class="line"></span><br><span class="line">   <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br></pre></td></tr></table></figure><p>That it! Now you can deploy your Serverless application in any region, using only the CloudFormation console.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I use this bash loop to package an AWS SAM template to multiple regions.&lt;/p&gt;
&lt;p&gt;This script stages the CloudFormation template and</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="AWS SAM CLI" scheme="https://bliskavka.com/tags/AWS-SAM-CLI/"/>
    
  </entry>
  
  <entry>
    <title>My ChatGPT Reading List</title>
    <link href="https://bliskavka.com/2023/06/17/chat-gpt-reading-list/"/>
    <id>https://bliskavka.com/2023/06/17/chat-gpt-reading-list/</id>
    <published>2023-06-17T18:57:50.000Z</published>
    <updated>2024-02-25T19:57:57.969Z</updated>
    
    <content type="html"><![CDATA[<p>There has been a lot of HYPE at the office around AI and ChatGPT. Organizational leaders and clients are looking for the AI angle.</p><span id="more"></span><p>I usually don’t jump on the latest tech right away because there is just too much new stuff all the time! It’s been almost 4 months since ChatGPT 4 was released and having seen some very impressive examples, I am ready to jump in and learn something new.</p><p>I work in the Contact Center space, and we build voice and chat self-service bots for clients, so I have some vested interest in this technology.</p><h2 id="Reading-List"><a href="#Reading-List" class="headerlink" title="Reading List"></a>Reading List</h2><h3 id="ChatGPT-for-Dummies-Pam-Baker"><a href="#ChatGPT-for-Dummies-Pam-Baker" class="headerlink" title="ChatGPT for Dummies - Pam Baker"></a>ChatGPT for Dummies - Pam Baker</h3><p>I found this book to be an excellent introduction. It offered a good background in layman’s terms and was very easy to follow. It’s also very explicit about the limitations of the models. GPT is great at generating content, but it may be lies ;)</p><h3 id="Developing-Apps-with-GPT-4-and-Chat-GPT-Oliveier-Caelen-and-Marie-Alice-Blete"><a href="#Developing-Apps-with-GPT-4-and-Chat-GPT-Oliveier-Caelen-and-Marie-Alice-Blete" class="headerlink" title="Developing Apps with GPT-4 and Chat GPT - Oliveier Caelen and Marie-Alice Blete"></a>Developing Apps with GPT-4 and Chat GPT - Oliveier Caelen and Marie-Alice Blete</h3><p>This was a great follow-up to the “For Dummies” book. The authors dive deeper into the differences between the OpenAI models and how they affect pricing.</p><p>As a developer, I found the code samples useful. The authors showed examples of using <code>Completion</code> and <code>Insertion</code> commands and covered some cost control topics.</p><h3 id="The-ChatGPT-Gold-Rush-Profiting-from-the-AI-Revolution-Online-Prompt-Engineering-Mastery-with-ChatGPT-Mark-Adelson"><a href="#The-ChatGPT-Gold-Rush-Profiting-from-the-AI-Revolution-Online-Prompt-Engineering-Mastery-with-ChatGPT-Mark-Adelson" class="headerlink" title="The ChatGPT Gold Rush: Profiting from the AI Revolution Online: Prompt Engineering Mastery with ChatGPT - Mark Adelson"></a>The ChatGPT Gold Rush: Profiting from the AI Revolution Online: Prompt Engineering Mastery with ChatGPT - Mark Adelson</h3><p>This book might be useful if you are looking to use ChatGPT to create a bunch of ChatGPT content for your blog.</p><p>The title looks like clickbait for a reason (it is). The book is full of lists that include a heading, and a small paragraph. Along with a list of tools and a prompt dump.</p><p>I felt like some sections were written by ChatGPT.</p><h3 id="Modern-Generative-AI-with-ChatGPT-and-OpenAI-Models-Valentina-Alto"><a href="#Modern-Generative-AI-with-ChatGPT-and-OpenAI-Models-Valentina-Alto" class="headerlink" title="Modern Generative AI with ChatGPT and OpenAI Models - Valentina Alto"></a>Modern Generative AI with ChatGPT and OpenAI Models - Valentina Alto</h3><p>This is my favorite book so far. It covers some history, ethics, and limitations. The book also has code and use case examples.</p><p>Chapter 10 had a use case which is right up my alley. Using an Azure OpenAI instance to analyze call center transcripts.</p><h4 id="Prompt"><a href="#Prompt" class="headerlink" title="Prompt"></a>Prompt</h4><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&#123;&#123;transcript&#125;&#125;</span><br><span class="line">&quot;Extract the following information from the above text:</span><br><span class="line">Name and Surname</span><br><span class="line">Reason for calling</span><br><span class="line">Policy Number</span><br><span class="line">Resolution</span><br><span class="line">Initial Sentiment</span><br><span class="line">Reason for Initial Sentiment</span><br><span class="line">Final sentiment</span><br><span class="line">Reason for final sentiment</span><br><span class="line">Contact center improvement&quot;</span><br></pre></td></tr></table></figure><h4 id="Output"><a href="#Output" class="headerlink" title="Output"></a>Output</h4><p><img src="./chat-gpt-call-center-analysis.png" alt="ChatGPT Output"></p><h2 id="What’s-Next"><a href="#What’s-Next" class="headerlink" title="What’s Next?"></a>What’s Next?</h2><p>I am going to read a few more books on the topic. Blogs and the internet may have more current content, but I like the curated feel of a book.</p><p>In the meantime, I’m making lists for potential Proof of Concepts. This technology is amazing.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;There has been a lot of HYPE at the office around AI and ChatGPT. Organizational leaders and clients are looking for the AI angle.&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="ChatGPT" scheme="https://bliskavka.com/tags/ChatGPT/"/>
    
    <category term="Training" scheme="https://bliskavka.com/tags/Training/"/>
    
  </entry>
  
  <entry>
    <title>AWS Certified - Database Specialty</title>
    <link href="https://bliskavka.com/2023/06/08/AWS-Database-Specialty-Cert/"/>
    <id>https://bliskavka.com/2023/06/08/AWS-Database-Specialty-Cert/</id>
    <published>2023-06-08T14:38:24.000Z</published>
    <updated>2024-02-25T16:15:11.999Z</updated>
    
    <content type="html"><![CDATA[<p>I got my <a href="https://www.credly.com/badges/7eb2c840-eb62-42ac-bada-a1ea502ae8f2">AWS Database Specialty Certification</a>!</p><p>I already have 5 years of experience, my Solution Architect Pro, and Data Analytics certificates so this one wasn’t very difficult.</p><span id="more"></span><h2 id="Study-Guide"><a href="#Study-Guide" class="headerlink" title="Study Guide"></a>Study Guide</h2><p>I watched the video course and did the following practice exams. Preparation took about 2 weeks.</p><ul><li><a href="https://www.udemy.com/share/103o6K3@n89E79M2Kktz9dEyb6fag7hNjDaOiuETdPjZ51VMg1PPyW9XCiNEuNH3znqhhwg0/">Video Course - Stephane Maarek and Riyaz Sayyad</a></li><li><a href="https://www.udemy.com/share/103t3o3@hVHDdFC2lkVLuKPkUBuqT3YEqXVjeXVnb80zyjVll667JP5Rprgpl8edtUA_iYB_/">Practice Exam - Stephane Maarek and Abhishek Singh</a></li><li><a href="https://www.udemy.com/share/103uFE3@_GM3sU8RyKnREmpRigY4L3lLSOLruL8-A1eYjCamEdyRf7hcFEY9FUGWZavJlXC_/">Practice Exam - Jon Bonso</a></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;I got my &lt;a href=&quot;https://www.credly.com/badges/7eb2c840-eb62-42ac-bada-a1ea502ae8f2&quot;&gt;AWS Database Specialty Certification&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I already have 5 years of experience, my Solution Architect Pro, and Data Analytics certificates so this one wasn’t very difficult.&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>DDB Streams Event Source Mapping Already Exists Error</title>
    <link href="https://bliskavka.com/2023/06/01/cdk-event-source-mapping-exists/"/>
    <id>https://bliskavka.com/2023/06/01/cdk-event-source-mapping-exists/</id>
    <published>2023-06-01T19:45:30.000Z</published>
    <updated>2024-02-25T19:57:57.968Z</updated>
    
    <content type="html"><![CDATA[<p>When updating a lambda with a DynamoDB Streams event source I got a mapping already exists error</p><span id="more"></span><h2 id="Error"><a href="#Error" class="headerlink" title="Error"></a>Error</h2><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">10:54:08 AM | CREATE_FAILED | AWS::Lambda::EventSourceMapping | *</span><br><span class="line">Resource handler returned message: &quot;The event source arn * and function * provided mapping already exists. Please update or delete the existing mapping with UUID *.</span><br></pre></td></tr></table></figure><h2 id="Resolution"><a href="#Resolution" class="headerlink" title="Resolution"></a>Resolution</h2><p>Check if your CDK Metadata path has changed. Changing the stack or construct logical ID will change the metadata path.</p><p>If the logical ID change was intentional, manually delete the mapping and redeploy.</p><h2 id="More-Info"><a href="#More-Info" class="headerlink" title="More Info"></a>More Info</h2><p>When the path changes CDK attempts to replace the mapping. Since CloudFormation must create a new resource before deleting the old, this threw a mapping already exists error.</p><p>In my case, I changed the stack logical ID, which triggered a replacement on the event source mapping.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;When updating a lambda with a DynamoDB Streams event source I got a mapping already exists error&lt;/p&gt;</summary>
    
    
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/Troubleshooting/"/>
    
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>Adding Assets to CDK Staging Bucket</title>
    <link href="https://bliskavka.com/2023/05/31/adding-assets-to-cdk-staging-bucket/"/>
    <id>https://bliskavka.com/2023/05/31/adding-assets-to-cdk-staging-bucket/</id>
    <published>2023-05-31T15:45:30.000Z</published>
    <updated>2024-02-25T19:57:57.967Z</updated>
    
    <content type="html"><![CDATA[<p>Today I discovered that you can deploy arbitrary files to the CDK staging bucket with a human-readable file name!</p><p>This feature is awesome if you are <a href="/2022/02/07/synth-cdk-to-custom-bucket">pre-synthing CDK apps to CloudFormation Templates</a>.</p><span id="more"></span><p>My first attempt was to use <a href="https://docs.aws.amazon.com/cdk/v2/guide/assets.html">Asset</a> but it used the file hash for the file name, which could change over time and is not user-friendly.</p><p>After playing around with a few methods, I discovered that you can do this.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">MyStack</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Stack</span>&#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">...</span>)&#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">synthesizer</span>.<span class="title function_">addFileAsset</span>(&#123;</span><br><span class="line">      <span class="attr">sourceHash</span>: <span class="string">&#x27;RELEASE_NOTES&#x27;</span>,</span><br><span class="line">      <span class="attr">fileName</span>: <span class="title function_">join</span>(rootDir, <span class="string">&#x27;RELEASE_NOTES.md&#x27;</span>),</span><br><span class="line">      <span class="attr">packaging</span>: <span class="title class_">FileAssetPackaging</span>.<span class="property">FILE</span></span><br><span class="line">    &#125;)</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>In the above sample, the <code>sourceHash</code> is used to create the file name: <code>RELEASE_NOTES.md</code>. Since I publish templates under their version prefix, the file hash is unimportant to me!</p><h2 id="More-Context"><a href="#More-Context" class="headerlink" title="More Context"></a>More Context</h2><p>We generate pre-synthed templates for very complex applications which are:</p><ul><li>shared with clients using a S3 bucket policy</li><li>installed by a non-developer using CloudFormation QuickStart URLs</li><li>capable of automatically updating itself by checking the bucket for a newer version and initiating a <code>cloudformation update</code></li></ul><p>Including a <code>RELEASE_NOTES.md</code> allows the template consumer to make a more informed decision on when to upgrade.</p><p>Cheers!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Today I discovered that you can deploy arbitrary files to the CDK staging bucket with a human-readable file name!&lt;/p&gt;
&lt;p&gt;This feature is awesome if you are &lt;a href=&quot;/2022/02/07/synth-cdk-to-custom-bucket&quot;&gt;pre-synthing CDK apps to CloudFormation Templates&lt;/a&gt;.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>My First Sky Dive</title>
    <link href="https://bliskavka.com/2023/05/23/my-first-sky-dive/"/>
    <id>https://bliskavka.com/2023/05/23/my-first-sky-dive/</id>
    <published>2023-05-23T13:40:22.000Z</published>
    <updated>2024-02-25T19:57:57.971Z</updated>
    
    <content type="html"><![CDATA[<p>I am afraid of heights so I never thought I would do it. My wife never thought I would do it. Yet somehow I did it.</p><span id="more"></span><p>To be honest - it was peer pressure. My wife has always wanted to try skydiving, and our friends gifted her a ticket on her birthday.</p><p>So that question is settled - If my friends were jumping out of a perfectly good airplane, I would too 🤣.</p><iframe width="560" height="315" src="https://www.youtube.com/embed/EWhvT_6gV1k" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>]]></content>
    
    
    <summary type="html">&lt;p&gt;I am afraid of heights so I never thought I would do it. My wife never thought I would do it. Yet somehow I did it.&lt;/p&gt;</summary>
    
    
    
    
    <category term="Fun" scheme="https://bliskavka.com/tags/Fun/"/>
    
  </entry>
  
  <entry>
    <title>Speaking at Boca Code Bootcamp</title>
    <link href="https://bliskavka.com/2023/03/07/speaking-at-boca-code-bootcamp/"/>
    <id>https://bliskavka.com/2023/03/07/speaking-at-boca-code-bootcamp/</id>
    <published>2023-03-08T00:14:13.000Z</published>
    <updated>2024-02-25T19:57:57.971Z</updated>
    
    <content type="html"><![CDATA[<p>I met <a href="https://www.linkedin.com/in/toddalbert/">Todd Albert</a>, the founder of <a href="https://bocacode.com/">Boca Code</a> at an AWS networking event hosted by <a href="https://www.cloudhesive.com/">CloudHesive</a>. He got a kick out of the fact that I was a plumber before I got into IT, so he invited me to speak to the 9th cohort of the Boca Code Developer Bootcamp</p><span id="more"></span><p>It was a great experience! The facility is cozy and everyone was super friendly. They also had great coffee and an awesome selection of laptop stickers!</p><p>I spoke about my journey from being a plumber to being a Sr. Architect at the leading Amazon Connect integrator, there were jokes, laughter, and most importantly - questions!</p><h2 id="Questions"><a href="#Questions" class="headerlink" title="Questions"></a>Questions</h2><h3 id="How-long-did-it-take-before-you-finally-felt-confident-as-a-developer"><a href="#How-long-did-it-take-before-you-finally-felt-confident-as-a-developer" class="headerlink" title="How long did it take before you finally felt confident as a developer?"></a>How long did it take before you finally felt confident as a developer?</h3><p>About 3 years - but you can get there sooner! The first 2 years I worked alone and didn’t have anyone to compare to, so I assumed I wasn’t very good but I kept studying and kept at it.</p><p>The 3rd year I worked with a team of sub-contractors on a project that got scrapped - so I figured that wasn’t a very good metric.</p><p>In my fourth year, I moved to another company with a full internal dev team - and I realized that in many respects I was ahead of guys with 10-15 years of experience. They knew A LOT, but much of that tech was already dead. I knew a lot MORE than them about the current tech.</p><p>Don’t forget that as a boot camp graduate, your education may be more current than the other devs. You still have to get experience, but I promise you are doing much better than your imposter syndrome is telling you.</p><h3 id="What-makes-a-good-impression-for-new-employees"><a href="#What-makes-a-good-impression-for-new-employees" class="headerlink" title="What makes a good impression for new employees?"></a>What makes a good impression for new employees?</h3><p>Typically the onboarding material includes reading, and assignments to get you familiar with the toolset. If you are spinning your wheels for 2 days on something - and the answer is in the reading, this is a strong negative.</p><ol><li>You did not read the assignment and you just wasted 2 days because you are afraid to ask for help.</li><li>I would rather you do the reading, do some googling, and when you are stuck for 4-8 hours, reach out for help. This work requires communication - maybe the requirements were unclear - being able to clear that up on your own initiative is very impressive!</li></ol><p>Corollary - don’t ask for help for every little thing. Do your due diligence. Also, take notes - don’t ask the same thing over and over again.</p><h3 id="What-type-of-questions-do-you-usually-ask-at-an-interview"><a href="#What-type-of-questions-do-you-usually-ask-at-an-interview" class="headerlink" title="What type of questions do you usually ask at an interview?"></a>What type of questions do you usually ask at an interview?</h3><p>I like to gauge a candidate’s skill level, and then dive a little deeper based on their response.</p><ul><li>For a Jr role, I may ask what is the difference between an RDBMS and a NoSQL DB.<ul><li>Followup: When would you choose one over the other and why?</li></ul></li><li>For a Mid role, I would ask them to name the 3 types of testing (Unit, Integration, Manual).<ul><li>What is the difference?</li><li>How would you design your code to make testing easier? (expecting mocks &amp; dependency injection)</li></ul></li><li>For a Sr role, I would have a conversation about design patterns.</li><li>I always love to ask about personal or favorite projects. You have to love this work to be exceptional at it. If you don’t have any code you are proud of - it’s a turn-off.</li><li>What is the most impactful project you have participated in? What was the impact - social, financial, etc? You have to be able to speak about your work in terms of return-on-investment</li></ul><h3 id="What-tips-do-you-have-for-interviewing"><a href="#What-tips-do-you-have-for-interviewing" class="headerlink" title="What tips do you have for interviewing?"></a>What tips do you have for interviewing?</h3><p>Be collaborative. If you are asked design questions they may be intentionally vague - ask the interviewer clarifying questions.</p><p>If the question requires a succinct factual response, and you don’t know the answer immediately, you can ask questions to get partial credit.</p><p>For example:</p><ul><li>I am not familiar with the term X, would you provide a short description and I may be able to able to answer the rest of the question.</li><li>I have never used that in my work, but I think I know what it does based on your description - can I try to guess?</li></ul><p>Remember not to B.S. during a technical interview. I prefer someone says honestly “I don’t know the answer, but am willing to try anyway if you give me some hints”.</p><p>In the real world, you will rarely have all the facts. You will have to reach out to the client who reported the bug and ask them to do a screen share so that you can see what is happening. Asking good questions is part of the job.</p><h3 id="Would-you-recommend-I-get-cloud-certs"><a href="#Would-you-recommend-I-get-cloud-certs" class="headerlink" title="Would you recommend I get cloud certs?"></a>Would you recommend I get cloud certs?</h3><p>I work in an AWS shop. I would take an AWS certified bootcamp grad over a non-certified grad every day. It’s probably less relevant if the company is not using AWS, but it still shows personal initiative and a willingness to learn.</p><p>If you are applying to work for an AWS Partner, they have to have a minimum amount of AWS Certified engineers to maintain their partner status. Having a cert in this case is extremely useful.</p><h2 id="Understanding-your-job-economics"><a href="#Understanding-your-job-economics" class="headerlink" title="Understanding your job economics"></a>Understanding your job economics</h2><p>During my talk, I mentioned a couple of concepts and a student asked me to elaborate on them.</p><h3 id="Understanding-the-economics-of-your-business"><a href="#Understanding-the-economics-of-your-business" class="headerlink" title="Understanding the economics of your business"></a>Understanding the economics of your business</h3><p>I work in the call center space - for each minute a customer is on a call it costs the company $1. If you have thousands of agents and tens of thousands of calls per month, these figures start to add up!</p><p>Reducing the average call handle time for a call by 1% for a sufficiently large business can reduce costs by millions over several years. If I can contribute that improvement consistently - my career is made.</p><h3 id="Understanding-the-economics-of-your-position"><a href="#Understanding-the-economics-of-your-position" class="headerlink" title="Understanding the economics of your position"></a>Understanding the economics of your position</h3><p>Typically IT is a cost center, not a profit center. <em>You</em> cost money. If you are doing your job well, you cost a <em>lot</em> of money.</p><p><em>You</em> should <strong>NOT</strong> be doing work that a computer can do faster, better, and more consistently. For this reason, you should be on the lookout for automation, DevOps, scripts, or processes that reduce the amount of repetitive&#x2F;manual work that you do, so you can focus on real innovations.</p><p>Example: If some nightly job runs for 3 hours - don’t bother spending 3 days speeding it up. It starts at 12:00, finishes by 3:00, and the first person that needs it is at 8:00. There is a 5-hour buffer - but you just cost the company 3 days for no economic benefit.</p><p>Not everything needs to be a script. If some work only happens once a month and takes 15 minutes - a <a href="/categories/HowTo">How-To</a> guide may be more appropriate. Next time you have to do the task, you can do it in 10 minutes, or simply hand it off to the new guy :)</p><p>If you can make yourself (and others) more productive through automation and&#x2F;or documentation you will also go far.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>It was fun to reflect on my journey and share some personal experiences with budding new developers!</p><blockquote><p><a href="https://bocacode.com/">Boca Code</a> is an intense 40&#x2F;hrs a week, for 10 weeks, software development bootcamp. Having spoke with the class and founders - I highly recommend them!</p></blockquote>]]></content>
    
    
    <summary type="html">&lt;p&gt;I met &lt;a href=&quot;https://www.linkedin.com/in/toddalbert/&quot;&gt;Todd Albert&lt;/a&gt;, the founder of &lt;a href=&quot;https://bocacode.com/&quot;&gt;Boca Code&lt;/a&gt; at an AWS networking event hosted by &lt;a href=&quot;https://www.cloudhesive.com/&quot;&gt;CloudHesive&lt;/a&gt;. He got a kick out of the fact that I was a plumber before I got into IT, so he invited me to speak to the 9th cohort of the Boca Code Developer Bootcamp&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
  </entry>
  
  <entry>
    <title>CDK package.json Scripts</title>
    <link href="https://bliskavka.com/2023/03/02/cdk-package-scripts/"/>
    <id>https://bliskavka.com/2023/03/02/cdk-package-scripts/</id>
    <published>2023-03-02T22:54:46.000Z</published>
    <updated>2024-02-25T19:57:57.968Z</updated>
    
    <content type="html"><![CDATA[<p>I found the following package.json scripts very convenient when managing a complex CDK app. The key is the <code>--</code> operator, which allows us to append additional parameters.</p><span id="more"></span><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc --noEmit&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;cdk&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm run build &amp;&amp; cdk&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;cdk:pipeline&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm run build &amp;&amp; cdk --app &#x27;npx ts-node --prefer-ts-exts bin/pipeline.ts&#x27;&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;test&quot;</span><span class="punctuation">:</span> <span class="string">&quot;jest&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;diff&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm run cdk -- diff&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;synth&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm run cdk -- synth --quiet&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deploy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm run cdk -- deploy --all --require-approval never&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deploy:pipeline&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npm run cdk:pipeline -- deploy --all --require-approval never&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>This configuration allows us to create convenient scripts like deploy&#x2F;diff&#x2F;synth, but we still have the ability to pass in additional parameters like:</p><p><code>npm run deploy -- --no-rollback --profile default</code></p><p>I can also define multiple entry points</p><ul><li><code>cdk</code>: Interact with the stack directly.</li><li><code>cdk:pipeline</code>: Deploy a CDK Pipeline which is able to patch the CDK app when new changes are pushed.</li></ul><p>A pipeline execution can be slow, so being able to circumvent the pipeline in a dev&#x2F;sandbox environment is extremely useful.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I found the following package.json scripts very convenient when managing a complex CDK app. The key is the &lt;code&gt;--&lt;/code&gt; operator, which allows us to append additional parameters.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>Speed up CDK Pipelines Deployment</title>
    <link href="https://bliskavka.com/2022/09/25/speed-up-cdk-pipelines/"/>
    <id>https://bliskavka.com/2022/09/25/speed-up-cdk-pipelines/</id>
    <published>2022-09-25T21:23:01.000Z</published>
    <updated>2024-02-25T19:57:57.971Z</updated>
    
    <content type="html"><![CDATA[<p>I recently converted a large CDK app to use CDK Pipelines. I LOVE how quickly it was to get working but it generated a UGLY and SLOW pipeline.</p><span id="more"></span><ul><li>The large app had ~50 assets, so a Publish Assets CodeBuild was generated for each asset (UGLY).</li><li>Often the publish step took longer than the deploy because the CodeBuilds were queued and only 5-10 ran at one time.</li><li>One of our client accounts hit a CodeBuild soft limit :(</li></ul><h2 id="Solution"><a href="#Solution" class="headerlink" title="Solution"></a>Solution</h2><p>After some Google-fu, I found the solution on the <a href="https://cdk.dev/">cdk.dev</a> Slack channel!</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">new</span> <span class="title class_">CodePipeline</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Pipeline&#x27;</span>, &#123;</span><br><span class="line">  <span class="comment">// Set this to false!</span></span><br><span class="line">  <span class="attr">publishAssetsInParallel</span>: <span class="literal">false</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>By default, <code>publishAssetsInParallel</code> is on, creating the issues listed above. After I turned it off I had immediate gratification!</p><h2 id="Results"><a href="#Results" class="headerlink" title="Results"></a>Results</h2><ul><li>Only 1 CodeBuild was generated to publish assets (prettier).</li><li>Deployment time for the ~50 asset project was reduced by 20 minutes.</li><li>On another project, the self-mutate step was reduced from ~60 to 6 minutes!</li></ul><p>Your mileage may vary, but it’s a very simple change that you can validate quickly. Good Luck!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I recently converted a large CDK app to use CDK Pipelines. I LOVE how quickly it was to get working but it generated a UGLY and SLOW pipeline.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>AWS Certified - Data Analytics Specialty</title>
    <link href="https://bliskavka.com/2022/09/06/AWS-Data-Analytics-Cert/"/>
    <id>https://bliskavka.com/2022/09/06/AWS-Data-Analytics-Cert/</id>
    <published>2022-09-06T15:43:24.000Z</published>
    <updated>2024-02-25T16:15:11.999Z</updated>
    
    <content type="html"><![CDATA[<p>I got my <a href="https://www.credly.com/badges/c47ce3e4-9f4b-4c4d-ad13-227f6a8b1b47/linked_in?t=rhu502">AWS Data Analytics Specialty</a>!</p><p>I already had a lot of experience with Kinesis, Athena, and RedShift but there is A LOT of material in this exam. I highly recommend <a href="https://www.udemy.com/course/practice-exams-aws-certified-data-analytics-specialty/">practice exams by Stephane Maarek</a> because they explain why you got something wrong, and you can adjust before taking the real thing!</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I got my &lt;a href=&quot;https://www.credly.com/badges/c47ce3e4-9f4b-4c4d-ad13-227f6a8b1b47/linked_in?t=rhu502&quot;&gt;AWS Data Analytics</summary>
        
      
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>Write config.json to S3 with AWS CDK</title>
    <link href="https://bliskavka.com/2022/02/10/cdk-website-config-file/"/>
    <id>https://bliskavka.com/2022/02/10/cdk-website-config-file/</id>
    <published>2022-02-10T14:11:45.000Z</published>
    <updated>2024-02-25T19:57:57.969Z</updated>
    
    <content type="html"><![CDATA[<p>To make prebuilt SPA installers with CDK I like to keep environment configuration outside of the minified code via a <code>config.json</code> file. This allows me to build the app once and move it between environments and stages.</p><span id="more"></span><p>The file contains settings like Cognito Pool ID, region, and&#x2F;or branding information.</p><p>I used to write my own Custom Resource for building this file but recently discovered a simpler CDKV2 way! Credit: <a href="https://www.freecodecamp.org/news/aws-cdk-v2-three-tier-serverless-application/">aws-cdk-v2-three-tier-serverless-application&#x2F;</a></p><p>I am copy-pasting the relevant code for future reference.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  <span class="title class_">AwsCustomResource</span>,</span><br><span class="line">  <span class="title class_">AwsCustomResourcePolicy</span>,</span><br><span class="line">  <span class="title class_">PhysicalResourceId</span>,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/custom-resources&#x27;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">PolicyStatement</span> &#125; <span class="keyword">from</span> <span class="string">&#x27;aws-cdk-lib/aws-iam&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> myConfig = &#123;</span><br><span class="line">  <span class="attr">prefix</span>: <span class="string">&#x27;some-static-value&#x27;</span>,</span><br><span class="line">  <span class="attr">userPoolId</span>: userPool.<span class="property">userPoolId</span>,</span><br><span class="line">  <span class="comment">// Call toString() on tokens</span></span><br><span class="line">  <span class="attr">region</span>: <span class="title class_">Aws</span>.<span class="property">REGION</span>.<span class="title function_">toString</span>(),</span><br><span class="line">  etc,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">AwsCustomResource</span>(<span class="variable language_">this</span>, <span class="string">&#x27;SpaConfig&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">logRetention</span>: <span class="title class_">RetentionDays</span>.<span class="property">ONE_DAY</span>,</span><br><span class="line">  <span class="attr">onUpdate</span>: &#123;</span><br><span class="line">    <span class="attr">action</span>: <span class="string">&#x27;putObject&#x27;</span>,</span><br><span class="line">    <span class="attr">parameters</span>: &#123;</span><br><span class="line">      <span class="title class_">Body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(myConfig),</span><br><span class="line">      <span class="title class_">Bucket</span>: websiteBucket.<span class="property">bucketName</span>,</span><br><span class="line">      <span class="title class_">CacheControl</span>: <span class="string">&#x27;max-age=0, no-cache, no-store, must-revalidate&#x27;</span>,</span><br><span class="line">      <span class="title class_">ContentType</span>: <span class="string">&#x27;application/json&#x27;</span>,</span><br><span class="line">      <span class="title class_">Key</span>: <span class="string">&#x27;config.json&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">physicalResourceId</span>: <span class="title class_">PhysicalResourceId</span>.<span class="title function_">of</span>(<span class="string">&#x27;config&#x27;</span>),</span><br><span class="line">    <span class="attr">service</span>: <span class="string">&#x27;S3&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">policy</span>: <span class="title class_">AwsCustomResourcePolicy</span>.<span class="title function_">fromStatements</span>([</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">      <span class="attr">actions</span>: [<span class="string">&#x27;s3:PutObject&#x27;</span>],</span><br><span class="line">      <span class="attr">resources</span>: [websiteBucket.<span class="title function_">arnForObjects</span>(<span class="string">&#x27;config.json&#x27;</span>)],</span><br><span class="line">    &#125;),</span><br><span class="line">  ]),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="Pitfall"><a href="#Pitfall" class="headerlink" title="Pitfall"></a>Pitfall</h2><p>I have a <code>config.json</code> in the SPA deploy assets also, so with each rebuild and redeploy, CloudFormation was replacing and overwriting the <code>config.json</code> from SPA assets. I added this to the <code>myConfig</code> object above to force the custom resource to execute with each <code>cdk deploy</code></p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">buildTime</span>: <span class="keyword">new</span> <span class="title class_">Date</span>().<span class="title function_">toISOString</span>();</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;To make prebuilt SPA installers with CDK I like to keep environment configuration outside of the minified code via a &lt;code&gt;config.json&lt;/code&gt; file. This allows me to build the app once and move it between environments and stages.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>Synth CDK app to Custom Bucket</title>
    <link href="https://bliskavka.com/2022/02/07/synth-cdk-to-custom-bucket/"/>
    <id>https://bliskavka.com/2022/02/07/synth-cdk-to-custom-bucket/</id>
    <published>2022-02-07T22:04:53.000Z</published>
    <updated>2024-02-25T19:57:57.972Z</updated>
    
    <content type="html"><![CDATA[<p>Some AWS customers don’t use the CLI, and will not grant an external contractor CLI access. Trying to get access is a waste of time and resources. Do not fear, there is a solution!</p><span id="more"></span><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><ol><li>Create a client-specific staging bucket</li><li>Share the bucket with the client account via the Bucket Policy</li><li>Synth the stack to the staging bucket</li><li>Share template URL with client</li><li>The client can install using the URL in CloudFormation web console with their user credentials</li></ol><h2 id="App-Staging-Bucket-Policy"><a href="#App-Staging-Bucket-Policy" class="headerlink" title="App Staging Bucket Policy"></a>App Staging Bucket Policy</h2><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;Sid&quot;</span><span class="punctuation">:</span> <span class="string">&quot;MyClient&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Effect&quot;</span><span class="punctuation">:</span> <span class="string">&quot;Allow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Principal&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;AWS&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;arn:aws:iam::DEV_ACCOUNT_ID:root&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;arn:aws:iam::PROD_ACCOUNT_ID:root&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Action&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;s3:GetObject&quot;</span><span class="punctuation">,</span> <span class="string">&quot;s3:GetObjectVersion&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;Resource&quot;</span><span class="punctuation">:</span> <span class="string">&quot;arn:aws:s3:::app-staging-bucket/*&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Usage"><a href="#Usage" class="headerlink" title="Usage"></a>Usage</h2><ol><li><p>Install CDK Assets <code>npm i -D cdk-assets</code></p></li><li><p>Customize the stack synthesizer to use your custom staging bucket</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> cdk.<span class="title class_">App</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">new</span> <span class="title class_">MyApp</span>(app, <span class="string">&#x27;template&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">someParam</span>: <span class="string">&#x27;someValue&#x27;</span>,</span><br><span class="line">  <span class="attr">synthesizer</span>: <span class="keyword">new</span> <span class="title class_">DefaultStackSynthesizer</span>(&#123;</span><br><span class="line">    <span class="attr">fileAssetsBucketName</span>: <span class="string">&#x27;app-staging-bucket&#x27;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Use a custom role which has access to the asset bucket</span></span><br><span class="line">    <span class="attr">fileAssetPublishingRoleArn</span>: <span class="string">&#x27;my-client-staging-role&#x27;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Consider using a build date or version</span></span><br><span class="line">    <span class="attr">bucketPrefix</span>: <span class="string">&#x27;2.4.1&#x27;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// The client account does not need to be bootstrapped</span></span><br><span class="line">    <span class="attr">generateBootstrapVersionRule</span>: <span class="literal">false</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">synth</span>();</span><br></pre></td></tr></table></figure></li><li><p>Run <code>cdk synth</code> to generate your assets.</p></li><li><p>Modify <code>cdk.out/template.assets.json</code> to make the template file name more predictable</p><ul><li>find the entry with <code>sourcePath</code>&#x3D;<code>template.template.json</code></li><li>modify its <code>objectKey</code> to something like <code>2.4.1/template.json</code></li><li>(you should probably write some code to automate this)</li></ul></li><li><p>Run <code>cdk-assets -v -p ./cdk.out/template.assets.json publish</code></p></li><li><p>Share your template URL with the client. It will look something like:<br><code>https://app-staging-bucket.s3.amazonaws.com/2.4.1/template.json</code></p></li><li><p>Client can install the app using the CloudFormation web console.</p></li></ol><h2 id="Simpler-Template-Output"><a href="#Simpler-Template-Output" class="headerlink" title="Simpler Template Output"></a>Simpler Template Output</h2><p>Not sure what the side effects of these are, but this produces a simpler template with less CDK metadata.</p><p><code>cdk synth --path-metadata false --version-reporting false</code></p><h3 id="cdk-json"><a href="#cdk-json" class="headerlink" title="cdk.json"></a>cdk.json</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;context&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;@aws-cdk/core:newStyleStackSynthesis&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This has been very helpful for creating installers that are accessible to non-developers and usable in beginner AWS environments. I hope it saved you some head-scratching!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Some AWS customers don’t use the CLI, and will not grant an external contractor CLI access. Trying to get access is a waste of time and resources. Do not fear, there is a solution!&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
  </entry>
  
  <entry>
    <title>AWS Certified - DevOps Professional</title>
    <link href="https://bliskavka.com/2022/01/20/AWS-DevOps-Pro-Cert/"/>
    <id>https://bliskavka.com/2022/01/20/AWS-DevOps-Pro-Cert/</id>
    <published>2022-01-20T16:54:47.000Z</published>
    <updated>2024-02-25T19:57:57.962Z</updated>
    
    <content type="html"><![CDATA[<p>I am now an <a href="https://www.credly.com/badges/2055bc33-bf5d-4808-ba30-0fbfe09ca650">AWS Certified DevOps Engineer Professional</a>!</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I am now an &lt;a href=&quot;https://www.credly.com/badges/2055bc33-bf5d-4808-ba30-0fbfe09ca650&quot;&gt;AWS Certified DevOps Engineer</summary>
        
      
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>AWS Athena SAM Policies</title>
    <link href="https://bliskavka.com/2021/12/24/aws-athena-sam-policies/"/>
    <id>https://bliskavka.com/2021/12/24/aws-athena-sam-policies/</id>
    <published>2021-12-24T17:04:07.000Z</published>
    <updated>2024-02-25T16:15:12.001Z</updated>
    
    <content type="html"><![CDATA[<p>AWS Athena provides SQL queries over S3 data. The service depends on S3, Glue, and Athena itself so getting permissions set up can be tricky. Here is what worked for me.</p><span id="more"></span><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">StartQueryFunction:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">AWS::Serverless::Function</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Handler:</span> <span class="string">src/lambda/search.start</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">S3ReadPolicy:</span></span><br><span class="line">          <span class="attr">BucketName:</span> <span class="type">!Ref</span> <span class="string">DataBucket</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">S3CrudPolicy:</span></span><br><span class="line">          <span class="attr">BucketName:</span> <span class="type">!Ref</span> <span class="string">AthenaResultsBucket</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">AthenaQueryPolicy:</span></span><br><span class="line">          <span class="attr">WorkGroupName:</span> <span class="type">!Ref</span> <span class="string">AthenaWorkGroup</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">Statement:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">          <span class="attr">Action:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">glue:GetTable</span></span><br><span class="line">          <span class="attr">Resource:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">arn:aws:glue:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:catalog</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">arn:aws:glue:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:database/$&#123;GlueDatabase&#125;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">arn:aws:glue:$&#123;AWS::Region&#125;:$&#123;AWS::AccountId&#125;:table/$&#123;GlueDatabase&#125;/*</span></span><br><span class="line"></span><br><span class="line"><span class="attr">GetResultFunction:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">AWS::Serverless::Function</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Handler:</span> <span class="string">src/lambda/search.results</span></span><br><span class="line">    <span class="attr">Policies:</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">S3CrudPolicy:</span></span><br><span class="line">          <span class="attr">BucketName:</span> <span class="type">!Ref</span> <span class="string">AthenaResultsBucket</span></span><br><span class="line">      <span class="bullet">-</span> <span class="attr">AthenaQueryPolicy:</span></span><br><span class="line">          <span class="attr">WorkGroupName:</span> <span class="type">!Ref</span> <span class="string">AthenaWorkGroup</span></span><br></pre></td></tr></table></figure><p>Cheers!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;AWS Athena provides SQL queries over S3 data. The service depends on S3, Glue, and Athena itself so getting permissions set up can be tricky. Here is what worked for me.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="Security" scheme="https://bliskavka.com/tags/Security/"/>
    
    <category term="AWS SAM CLI" scheme="https://bliskavka.com/tags/AWS-SAM-CLI/"/>
    
  </entry>
  
  <entry>
    <title>Fargate with EFS CDK</title>
    <link href="https://bliskavka.com/2021/10/21/AWS-CDK-Fargate-with-EFS/"/>
    <id>https://bliskavka.com/2021/10/21/AWS-CDK-Fargate-with-EFS/</id>
    <published>2021-10-21T21:55:11.000Z</published>
    <updated>2024-02-25T19:57:57.962Z</updated>
    
    <content type="html"><![CDATA[<p>I struggled WAY too long trying to sort out the permissions for EFS. Turns out, there are 2 layers. The IAM role, and the Posix permissions. Both throw a similar-looking access denied. Finally!</p><span id="more"></span><p>Don’t judge me on the single AZ. I am running a single task in Fargate and only need one instance.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; vpc, az, region, account &#125; = props;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> fileSystem = <span class="keyword">new</span> <span class="title class_">FileSystem</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Efs&#x27;</span>, &#123;</span><br><span class="line">  vpc,</span><br><span class="line">  <span class="attr">performanceMode</span>: <span class="title class_">PerformanceMode</span>.<span class="property">GENERAL_PURPOSE</span>,</span><br><span class="line">  <span class="attr">vpcSubnets</span>: &#123;</span><br><span class="line">    <span class="attr">subnetType</span>: ec2.<span class="property">SubnetType</span>.<span class="property">PUBLIC</span>,</span><br><span class="line">    <span class="attr">onePerAz</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">availabilityZones</span>: [az],</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> accessPoint = <span class="keyword">new</span> <span class="title class_">AccessPoint</span>(<span class="variable language_">this</span>, <span class="string">&#x27;AccessPoint&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">fileSystem</span>: fileSystem,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> task = <span class="keyword">new</span> ecs.<span class="title class_">FargateTaskDefinition</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Task&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">cpu</span>: <span class="number">256</span>,</span><br><span class="line">  <span class="attr">memoryLimitMiB</span>: <span class="number">512</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> volumeName = <span class="string">&#x27;efs-volume&#x27;</span>;</span><br><span class="line"></span><br><span class="line">task.<span class="title function_">addVolume</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: volumeName,</span><br><span class="line">  <span class="attr">efsVolumeConfiguration</span>: &#123;</span><br><span class="line">    <span class="attr">fileSystemId</span>: fileSystem.<span class="property">fileSystemId</span>,</span><br><span class="line">    <span class="attr">transitEncryption</span>: <span class="string">&#x27;ENABLED&#x27;</span>,</span><br><span class="line">    <span class="attr">authorizationConfig</span>: &#123;</span><br><span class="line">      <span class="attr">accessPointId</span>: accessPoint.<span class="property">accessPointId</span>,</span><br><span class="line">      <span class="attr">iam</span>: <span class="string">&#x27;ENABLED&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> container = task.<span class="title function_">addContainer</span>(<span class="string">&#x27;Container&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">image</span>: ecs.<span class="property">ContainerImage</span>.<span class="title function_">fromAsset</span>(<span class="string">&#x27;./container&#x27;</span>),</span><br><span class="line">  <span class="attr">portMappings</span>: [&#123; <span class="attr">hostPort</span>: <span class="number">80</span>, <span class="attr">containerPort</span>: <span class="number">80</span> &#125;],</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">container.<span class="title function_">addMountPoints</span>(&#123;</span><br><span class="line">  <span class="attr">containerPath</span>: <span class="string">&#x27;/mount/data&#x27;</span>,</span><br><span class="line">  <span class="attr">sourceVolume</span>: volumeName,</span><br><span class="line">  <span class="attr">readOnly</span>: <span class="literal">false</span>,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">task.<span class="title function_">addToTaskRolePolicy</span>(</span><br><span class="line">  <span class="keyword">new</span> iam.<span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">    <span class="attr">actions</span>: [</span><br><span class="line">      <span class="string">&#x27;elasticfilesystem:ClientRootAccess&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;elasticfilesystem:ClientWrite&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;elasticfilesystem:ClientMount&#x27;</span>,</span><br><span class="line">      <span class="string">&#x27;elasticfilesystem:DescribeMountTargets&#x27;</span>,</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">resources</span>: [</span><br><span class="line">      <span class="string">`arn:aws:elasticfilesystem:<span class="subst">$&#123;region&#125;</span>:<span class="subst">$&#123;account&#125;</span>:file-system/<span class="subst">$&#123;fileSystem.fileSystemId&#125;</span>`</span>,</span><br><span class="line">    ],</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">task.<span class="title function_">addToTaskRolePolicy</span>(</span><br><span class="line">  <span class="keyword">new</span> iam.<span class="title class_">PolicyStatement</span>(&#123;</span><br><span class="line">    <span class="attr">actions</span>: [<span class="string">&#x27;ec2:DescribeAvailabilityZones&#x27;</span>],</span><br><span class="line">    <span class="attr">resources</span>: [<span class="string">&#x27;*&#x27;</span>],</span><br><span class="line">  &#125;)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>I hope this saves someone a headache!</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I struggled WAY too long trying to sort out the permissions for EFS. Turns out, there are 2 layers. The IAM role, and the Posix permissions. Both throw a similar-looking access denied. Finally!&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>Help: Logical ID Changed in my CDK App</title>
    <link href="https://bliskavka.com/2021/09/17/cdk-logical-id-changed/"/>
    <id>https://bliskavka.com/2021/09/17/cdk-logical-id-changed/</id>
    <published>2021-09-17T14:19:49.000Z</published>
    <updated>2024-02-28T15:23:15.089Z</updated>
    
    <content type="html"><![CDATA[<p>I recently came across a weird error in a private package CDK deployment:</p><p><code>&#123;dynamo table name&#125; already exists in stack &#123;current stack arn&#125;</code></p><p>Upon further investigation it turned out that one of our internal CDK modules for building the dynamo tables had changed how it was naming the construct, resulting in a different CloudFormation Logical ID. Since we were explicitly naming the table, the stack update could not complete, because the table already exists.</p><p>This app is a self-contained NPM module, so I wasn’t able to modify the construct, but I came up with a workaround.</p><ol><li>Back up the dynamo table data</li><li>Copy the raw stack YAML from CloudFormation and save it on your computer</li><li>Delete the table resource and references from the template</li><li>Update the stack with the new template using the CloudFormation web console</li><li>Verify that the table was deleted (and not retained) by CloudFormation</li><li>Redeploy the CDK app, which will create a new table</li><li>Restore the table data</li></ol>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I recently came across a weird error in a private package CDK deployment:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;#123;dynamo table name&amp;#125; already exists in</summary>
        
      
    
    
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/Troubleshooting/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
  </entry>
  
  <entry>
    <title>Missing Amplify UI Styles in Electron App</title>
    <link href="https://bliskavka.com/2021/09/15/amplify-ui-css-in-electron-boilerplate/"/>
    <id>https://bliskavka.com/2021/09/15/amplify-ui-css-in-electron-boilerplate/</id>
    <published>2021-09-15T18:37:38.000Z</published>
    <updated>2024-02-25T19:57:57.967Z</updated>
    
    <content type="html"><![CDATA[<p>I recently discovered the <a href="https://electron-react-boilerplate.js.org/">Electron React Boilerplate</a> project and wanted to use the <a href="https://docs.amplify.aws/ui/auth/authenticator/q/framework/react/">AWS Amplify Authenticator</a>. Lo and Behold: it’s ugly…</p><span id="more"></span><p><img src="missing-amplify-styles.png" alt="Missing Amplify Styles"></p><p>I had used the typical <code>index.tsx</code> import and I could see the CSS output, but for some reason, it did not work.</p><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// index.tsx</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&#x27;@aws-amplify/ui/dist/style.css&#x27;</span>;</span><br></pre></td></tr></table></figure><p>After much Googling, it turns out that Electron React Boiler plate does some webpack-ing magic under the hood.</p><p>The correct way to add the css is to import it in <code>App.global.css</code>:</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* App.global.css */</span></span><br><span class="line"><span class="keyword">@import</span> <span class="string">&#x27;~@aws-amplify/ui/dist/style.css&#x27;</span>;</span><br></pre></td></tr></table></figure><p><img src="working-amplify-styles.png" alt="Working Amplify Styles"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I recently discovered the &lt;a href=&quot;https://electron-react-boilerplate.js.org/&quot;&gt;Electron React Boilerplate&lt;/a&gt; project and wanted to use the &lt;a href=&quot;https://docs.amplify.aws/ui/auth/authenticator/q/framework/react/&quot;&gt;AWS Amplify Authenticator&lt;/a&gt;. Lo and Behold: it’s ugly…&lt;/p&gt;</summary>
    
    
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/Troubleshooting/"/>
    
    
    <category term="React" scheme="https://bliskavka.com/tags/React/"/>
    
    <category term="AWS Amplify" scheme="https://bliskavka.com/tags/AWS-Amplify/"/>
    
    <category term="Electron" scheme="https://bliskavka.com/tags/Electron/"/>
    
  </entry>
  
  <entry>
    <title>Synth CDK app to Portable CloudFormation (obsolete)</title>
    <link href="https://bliskavka.com/2021/09/13/synth-cdk-to-cloudformation/"/>
    <id>https://bliskavka.com/2021/09/13/synth-cdk-to-cloudformation/</id>
    <published>2021-09-13T11:47:58.000Z</published>
    <updated>2024-02-25T19:57:57.971Z</updated>
    
    <content type="html"><![CDATA[<p>Update 2&#x2F;7&#x2F;2022: <a href="/2022/02/07/synth-cdk-to-custom-bucket/">Read Synth CDK app to Custom Bucket instead</a>.</p><p>Consulting requires you to work within the client’s parameters. Some clients have internal standards and want you to deliver your white-label CDK app as CloudFormation. Call me old fashioned but…</p><span id="more"></span><blockquote><p>I dont expect Apple to rewrite their products in TypeScript because that is my current favorite.</p></blockquote><p>Joking aside, in consulting this is a pretty common ask, so you must be prepared to deal with it.</p><p>On a related note, some AWS Clients have compliance or security restrictions that make it very difficult to get CLI access to deploy using CDK.</p><p>Fortunately, you can synth CDK apps into plain old CloudFormation, package it to an S3 bucket, and deploy it from the CloudFormation web console.</p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR;"></a>TL;DR;</h2><p><a href="https://github.com/ibliskavka/synth-cdk-to-cloudformation">Check out the demo project on GitHub</a>.</p><h2 id="Initial-Product-Stack"><a href="#Initial-Product-Stack" class="headerlink" title="Initial Product Stack"></a>Initial Product Stack</h2><p>Our demo stack will create a bucket, and name it based on the account, region, and client name.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">MyProductProps</span> <span class="keyword">extends</span> <span class="title class_">StackProps</span> &#123;</span><br><span class="line">  <span class="attr">client</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">MyProduct</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Stack</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span>, props: MyProductProps</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id);</span><br><span class="line">    <span class="keyword">const</span> bucketName = <span class="string">`<span class="subst">$&#123;props.env.account&#125;</span>-<span class="subst">$&#123;props.env.region&#125;</span>-<span class="subst">$&#123;props.client&#125;</span>`</span>;</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Bucket</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Bucket&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">bucketName</span>: bucketName,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="The-env-Field"><a href="#The-env-Field" class="headerlink" title="The env Field"></a>The env Field</h2><p>Typically you will pass an <code>env</code> field to your CDK stack props like this:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> cdk.<span class="title class_">App</span>();</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">MyProduct</span>(app, <span class="string">&#x27;my-product&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">env</span>: &#123;</span><br><span class="line">    <span class="attr">account</span>: <span class="string">&#x27;123456879123&#x27;</span>,</span><br><span class="line">    <span class="attr">region</span>: <span class="string">&#x27;us-east-1&#x27;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">client</span>: <span class="string">&#x27;foo&#x27;</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>And it’s very easy to access <code>account</code> and <code>region</code> from props like this:</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bucketName = <span class="string">`<span class="subst">$&#123;props.env.account&#125;</span>-<span class="subst">$&#123;props.env.region&#125;</span>-<span class="subst">$&#123;props.client&#125;</span>`</span>;</span><br></pre></td></tr></table></figure><p>The synthesized template will look like this:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">BucketName:</span> <span class="string">&#x27;123456789123-us-east-1-foo&#x27;</span></span><br></pre></td></tr></table></figure><p>This is very intuitive and works great for CDK deploys, but <em><em>IS NOT PORTABLE</em></em>. The synthesized template will contain hard-code account and region values.</p><p>The above props are evaluated at <code>synth-time</code>. We want the account and region values to be evaluated at <code>deploy-time</code>.</p><h3 id="Introducing-Stack-of"><a href="#Introducing-Stack-of" class="headerlink" title="Introducing Stack.of()"></a>Introducing Stack.of()</h3><p>In plain CloudFormation you wouldn’t hard-code the account and region information, you would use pseudo-functions like: <code>!Ref AWS::AccountId</code> and <code>!Ref AWS::Region</code> which get evaluated at deploy-time.</p><p>Let’s rewrite our stack params and exclude the optional <code>env</code> field.<br>Also, let’s rewrite our stack to use Stack.of when env is not available.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">MyProduct</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Stack</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span>, props: MyProductProps</span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> stack = props.<span class="property">env</span> || <span class="title class_">Stack</span>.<span class="title function_">of</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="keyword">const</span> bucketName = <span class="string">`<span class="subst">$&#123;stack.account&#125;</span>-<span class="subst">$&#123;stack.region&#125;</span>-<span class="subst">$&#123;props.client&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Bucket</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Bucket&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">bucketName</span>: bucketName,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>If you look at the synthesized CloudFormation template, the result should look familiar:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">BucketName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::AccountId&#125;-$&#123;AWS::Region&#125;-foo&#x27;</span></span><br></pre></td></tr></table></figure><p>Bonus: The above approach works whether <code>env</code> is passed in or not, so we should use it by default.</p><h2 id="CloudFormation-Parameters-and-Tokens"><a href="#CloudFormation-Parameters-and-Tokens" class="headerlink" title="CloudFormation Parameters and Tokens"></a>CloudFormation Parameters and Tokens</h2><p>The other major requirement for portable apps is CloudFormation Parameters. So far, we have passed in our <code>client</code> name as a string. This is very convenient for CDK deploys so we want to keep this format, but let’s rewrite our stack to use CloudFormation parameters so that we can have a deploy-time parameter.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">MyProduct</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Stack</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Separate build step from constructor to allow inheriting stack to add properties.</span></span><br><span class="line">  <span class="keyword">protected</span> <span class="title function_">build</span>(<span class="params">props: MyProductProps</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> stack = props.<span class="property">env</span> || <span class="title class_">Stack</span>.<span class="title function_">of</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="keyword">const</span> bucketName = <span class="string">`<span class="subst">$&#123;stack.account&#125;</span>-<span class="subst">$&#123;stack.region&#125;</span>-<span class="subst">$&#123;props.client&#125;</span>`</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">Bucket</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Bucket&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">bucketName</span>: bucketName,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">MyPortableProduct</span> <span class="keyword">extends</span> <span class="title class_ inherited__">MyProduct</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params">scope: Construct, id: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>(scope, id);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Add CloudFormation parameter</span></span><br><span class="line">    <span class="keyword">const</span> client = <span class="keyword">new</span> <span class="title class_">CfnParameter</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Client&#x27;</span>, &#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">&#x27;String&#x27;</span>,</span><br><span class="line">      <span class="attr">description</span>: <span class="string">&#x27;Used for naming&#x27;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Build the base stack, using the client parameter as a string token</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">build</span>(&#123;</span><br><span class="line">      <span class="attr">client</span>: client.<span class="property">valueAsString</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>We just extended our product stack to make it portable, with just a slight modification in the base stack! If you were to synthesize <code>MyPortableStack</code>, your <code>bucketName</code> would look something like this:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">BucketName:</span> <span class="type">!Sub</span> <span class="string">&#x27;$&#123;AWS::AccountId&#125;-$&#123;AWS::Region&#125;-$&#123;Client&#125;&#x27;</span></span><br></pre></td></tr></table></figure><h3 id="Warning"><a href="#Warning" class="headerlink" title="Warning!"></a>Warning!</h3><p><code>valueAsString</code> generates a token. Tokens don’t represent your data, so don’t perform string manipulations or conditionals with tokens. I will cover how to deal with this in another post.</p><h2 id="Synthesizing-a-Template"><a href="#Synthesizing-a-Template" class="headerlink" title="Synthesizing a Template"></a>Synthesizing a Template</h2><p>So far we have been making our CDK app portable, but we want CDK to generate a plain CloudFormation template.</p><p>Create a new <code>bin/product.ts</code> file</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> cdk.<span class="title class_">App</span>();</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">MyPortableProduct</span>(app, <span class="string">&#x27;my-product&#x27;</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> output = app.<span class="title function_">synth</span>();</span><br><span class="line"><span class="keyword">const</span> outStack = output.<span class="property">stacks</span>[<span class="number">0</span>];</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> templatePath = path.<span class="title function_">resolve</span>(<span class="string">&#x27;./cdk.out/template.yaml&#x27;</span>);</span><br><span class="line">fs.<span class="title function_">writeFileSync</span>(templatePath, <span class="variable constant_">YAML</span>.<span class="title function_">stringify</span>(outStack.<span class="property">template</span>));</span><br></pre></td></tr></table></figure><p>Create a new synth script in <code>package.json</code>. Notice this script excludes metadata and version reporting, this is not required for plain CloudFormation and makes our output template a lot cleaner.</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;synth:product&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tsc &amp;&amp; npx cdk --app &#x27;npx ts-node --prefer-ts-exts bin/product.ts&#x27; --path-metadata false --version-reporting false synth --quiet&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>Run the script <code>npm run synth:product</code></p><p>Your <code>cdk.out/template.yaml</code> should look like this:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">Client:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">Used</span> <span class="string">for</span> <span class="string">naming</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Bucket83908E77:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::S3::Bucket</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">BucketName:</span></span><br><span class="line">        <span class="attr">Fn::Join:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="bullet">-</span> <span class="attr">Ref:</span> <span class="string">AWS::AccountId</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;-&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Ref:</span> <span class="string">AWS::Region</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;-&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Ref:</span> <span class="string">Client</span></span><br><span class="line">    <span class="attr">UpdateReplacePolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Retain</span></span><br></pre></td></tr></table></figure><h2 id="CloudFormation-Logical-Ids"><a href="#CloudFormation-Logical-Ids" class="headerlink" title="CloudFormation Logical Ids"></a>CloudFormation Logical Ids</h2><p>If you are upgrading an existing CloudFormation or SAM app to CDK, you will notice that the bucket logical ID (<code>Bucket83908E77</code>) is auto-generated.</p><p>If our legacy template logical ID was <code>Bucket</code>, this would force the bucket to be recreated if you updated the stack.</p><p>We must update the <code>build</code> step to use <code>overrideLogicalId</code> to specify our logical ID.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> bucket = <span class="keyword">new</span> <span class="title class_">Bucket</span>(<span class="variable language_">this</span>, <span class="string">&#x27;Bucket&#x27;</span>, &#123;</span><br><span class="line">  <span class="attr">bucketName</span>: bucketName,</span><br><span class="line">&#125;);</span><br><span class="line">(bucket.<span class="property">node</span>.<span class="property">defaultChild</span> <span class="keyword">as</span> <span class="title class_">CfnBucket</span>).<span class="title function_">overrideLogicalId</span>(<span class="string">&#x27;Bucket&#x27;</span>);</span><br></pre></td></tr></table></figure><p>Now the template has our expected logical ID, and we can update our stack without losing our data.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">Client:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">Used</span> <span class="string">for</span> <span class="string">naming</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">Bucket:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::S3::Bucket</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">BucketName:</span></span><br><span class="line">        <span class="attr">Fn::Join:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">          <span class="bullet">-</span> <span class="bullet">-</span> <span class="attr">Ref:</span> <span class="string">AWS::AccountId</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;-&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Ref:</span> <span class="string">AWS::Region</span></span><br><span class="line">            <span class="bullet">-</span> <span class="string">&#x27;-&#x27;</span></span><br><span class="line">            <span class="bullet">-</span> <span class="attr">Ref:</span> <span class="string">Client</span></span><br><span class="line">    <span class="attr">UpdateReplacePolicy:</span> <span class="string">Retain</span></span><br><span class="line">    <span class="attr">DeletionPolicy:</span> <span class="string">Retain</span></span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This is a lot of extra work if you are building internal AWS apps, but if you are upgrading or building a product that will be installed in client accounts, you need the flexibility to support different deployment mechanisms.</p><p>Check out the <a href="https://github.com/ibliskavka/synth-cdk-to-cloudformation">GitHub Repo</a> for full project setup.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Update 2&amp;#x2F;7&amp;#x2F;2022: &lt;a href=&quot;/2022/02/07/synth-cdk-to-custom-bucket/&quot;&gt;Read Synth CDK app to Custom Bucket instead&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Consulting requires you to work within the client’s parameters. Some clients have internal standards and want you to deliver your white-label CDK app as CloudFormation. Call me old fashioned but…&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="CDK" scheme="https://bliskavka.com/tags/CDK/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
  </entry>
  
  <entry>
    <title>&quot;Unable to move or rename S3 object using web console&quot;
</title>
    <link href="https://bliskavka.com/2021/09/06/unable-to-rename-s3-object-in-web-console/"/>
    <id>https://bliskavka.com/2021/09/06/unable-to-rename-s3-object-in-web-console/</id>
    <published>2021-09-06T18:12:06.000Z</published>
    <updated>2024-02-25T19:57:57.972Z</updated>
    
    <content type="html"><![CDATA[<p>I was deploying an IAM user policy using AWS CloudFormation and granted CRUD access to the bucket, but the user could not rename or move a file using the web console.</p><span id="more"></span><p>I was trying to move files and kept getting denied. Then I simply tried renaming files and also got an access denied. I was able to upload and delete files though.</p><p>I looked at CloudTrail, and there was no obvious access denied.</p><p>I enabled S3 full control <code>s:*</code>, but I was still getting the same error.</p><p>I then tried to rename and move files using the CLI, which worked just fine.</p><p>Finally, I opened the IAM visual editor and created an entirely new policy for the user, which worked. After inspecting the policy, I noticed some permissions were scoped to the <code>*</code> resource, and not scoped to any ARN. After I added those to my CloudFormation, the user was able to move files using the web console.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># S3 CRUD policy</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">  <span class="attr">Action:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:GetObject</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:GetObjectAcl</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListBucket</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:GetBucketLocation</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:GetObjectVersion</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:PutObject</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:PutObjectAcl</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:GetLifecycleConfiguration</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:PutLifecycleConfiguration</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:DeleteObject</span></span><br><span class="line">  <span class="attr">Resource:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">arn:$&#123;AWS::Partition&#125;:s3:::$&#123;DataBucket&#125;</span></span><br><span class="line">    <span class="bullet">-</span> <span class="type">!Sub</span> <span class="string">arn:$&#123;AWS::Partition&#125;:s3:::$&#123;DataBucket&#125;/*</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Additional S3 Permissions</span></span><br><span class="line"><span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">  <span class="attr">Action:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListStorageLensConfigurations</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListAccessPointsForObjectLambda</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:GetAccessPoint</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:PutAccountPublicAccessBlock</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:GetAccountPublicAccessBlock</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListAllMyBuckets</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListAccessPoints</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListJobs</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:PutStorageLensConfiguration</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListMultiRegionAccessPoints</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:CreateJob</span></span><br><span class="line">  <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br></pre></td></tr></table></figure><p>After some testing, turns out that <code>s3:ListAllMyBuckets</code> permission is required to be able to move and rename files using the web UI!</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">  <span class="attr">Action:</span></span><br><span class="line">    <span class="bullet">-</span> <span class="string">s3:ListAllMyBuckets</span></span><br><span class="line">  <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br></pre></td></tr></table></figure><p>This is weird because you might not want to list all your buckets if you have multiple clients&#x2F;departments on the same account.</p><p>I hope this helps someone :)</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I was deploying an IAM user policy using AWS CloudFormation and granted CRUD access to the bucket, but the user could not rename or move a file using the web console.&lt;/p&gt;</summary>
    
    
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/Troubleshooting/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
    <category term="AWS SAM CLI" scheme="https://bliskavka.com/tags/AWS-SAM-CLI/"/>
    
  </entry>
  
  <entry>
    <title>AWS Dynamo DB Batch Write Retry</title>
    <link href="https://bliskavka.com/2021/06/25/dynamo-db-batch-write-retry/"/>
    <id>https://bliskavka.com/2021/06/25/dynamo-db-batch-write-retry/</id>
    <published>2021-06-25T22:48:29.000Z</published>
    <updated>2024-02-25T16:15:12.006Z</updated>
    
    <content type="html"><![CDATA[<p>BatchWrite is a great way to speed up data loads and updates but it has some caveats that you <strong>MUST</strong> know about.</p><p><strong>Partial success will not throw an error. You must check the unprocessed items and retry the operation</strong></p><p>I ran into this due to API throttling. I was using <code>PAY_PER_REQUEST</code>, so I expected the provisioning to scale automatically. DDB does scale automatically, but the process is not immediate, so I was losing records.</p><p>I created a wrapper function to check for and retry the operation. I was running multiple loads in parallel so I added some randomness to the retry wait time.</p><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">batchWrite</span>(<span class="params"></span></span><br><span class="line"><span class="params">  table: <span class="built_in">string</span>,</span></span><br><span class="line"><span class="params">  request: WriteRequests</span></span><br><span class="line"><span class="params"></span>): <span class="title class_">Promise</span>&lt;<span class="built_in">void</span>&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> <span class="attr">params</span>: <span class="title class_">BatchWriteItemInput</span> = &#123;</span><br><span class="line">    <span class="title class_">RequestItems</span>: &#123;</span><br><span class="line">      [table]: request,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">let</span> attempts = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">do</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="variable language_">this</span>.<span class="property">ddb</span>.<span class="title function_">batchWrite</span>(params).<span class="title function_">promise</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">      response.<span class="property">UnprocessedItems</span> &amp;&amp;</span><br><span class="line">      response.<span class="property">UnprocessedItems</span>[table] &amp;&amp;</span><br><span class="line">      response.<span class="property">UnprocessedItems</span>[table].<span class="property">length</span> &gt; <span class="number">0</span></span><br><span class="line">    ) &#123;</span><br><span class="line">      params.<span class="property">RequestItems</span> = response.<span class="property">UnprocessedItems</span>;</span><br><span class="line">      attempts++;</span><br><span class="line"></span><br><span class="line">      <span class="keyword">const</span> waitSeconds = <span class="title class_">Util</span>.<span class="title function_">getRandomInt</span>(attempts * <span class="number">5</span>);</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">debug</span>(</span><br><span class="line">        <span class="string">`Batch write was throttled, waiting <span class="subst">$&#123;waitSeconds&#125;</span> seconds to retry`</span></span><br><span class="line">      );</span><br><span class="line">      <span class="keyword">await</span> <span class="title class_">Util</span>.<span class="title function_">wait</span>(waitSeconds);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      params.<span class="property">RequestItems</span> = <span class="literal">null</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">while</span> (params.<span class="property">RequestItems</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>Also, don’t forget to batch your requests by 25. I usually use <a href="https://lodash.com/docs/#chunk">lodash chunk</a>.</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;BatchWrite is a great way to speed up data loads and updates but it has some caveats that you &lt;strong&gt;MUST&lt;/strong&gt; know</summary>
        
      
    
    
    
    
  </entry>
  
  <entry>
    <title>AWS Serverless Workshop at FloridaJS</title>
    <link href="https://bliskavka.com/2021/02/23/fljs-aws-workshop-2021/"/>
    <id>https://bliskavka.com/2021/02/23/fljs-aws-workshop-2021/</id>
    <published>2021-02-23T17:40:53.000Z</published>
    <updated>2024-02-25T19:57:57.970Z</updated>
    
    <content type="html"><![CDATA[<p>I had the honor of presenting an AWS Workshop at my local JavaScript User Group - FloridaJS.</p><p>It was a short session on building serverless web applications on AWS.</p><span id="more"></span><ul><li>Frontend - React w&#x2F; AWS Amplify</li><li>Hosting: S3 with CloudFront and Route53 for DNS</li><li>Authentication: AWS Cognito with self sign-up</li><li>Backend: Lambda with API Gateway</li><li>Storage: S3 + Dynamo DB</li></ul><iframe width="560" height="315" src="https://www.youtube.com/embed/pqcSUxIXYb4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>]]></content>
    
    
    <summary type="html">&lt;p&gt;I had the honor of presenting an AWS Workshop at my local JavaScript User Group - FloridaJS.&lt;/p&gt;
&lt;p&gt;It was a short session on building serverless web applications on AWS.&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="Training" scheme="https://bliskavka.com/tags/Training/"/>
    
  </entry>
  
  <entry>
    <title>My CloudFormation IDE</title>
    <link href="https://bliskavka.com/2021/02/03/My-CloudFormation-IDE/"/>
    <id>https://bliskavka.com/2021/02/03/My-CloudFormation-IDE/</id>
    <published>2021-02-03T22:55:11.000Z</published>
    <updated>2024-02-25T19:57:57.965Z</updated>
    
    <content type="html"><![CDATA[<p>Writing CloudFormation or SAM templates by hand is very powerful, but can also be quite frustrating without a good IDE setup…</p><span id="more"></span><h2 id="Update-2-19-2021"><a href="#Update-2-19-2021" class="headerlink" title="Update 2&#x2F;19&#x2F;2021"></a>Update 2&#x2F;19&#x2F;2021</h2><p>Check out <a href="https://marketplace.visualstudio.com/items?itemName=ThreadHeap.serverless-ide-vscode">Serverless IDE for VS Code</a> it has CFT Linting and Type Checking!!!</p><h2 id="VSCode-CloudFormation-Language-Support"><a href="#VSCode-CloudFormation-Language-Support" class="headerlink" title="VSCode CloudFormation Language Support"></a>VSCode CloudFormation Language Support</h2><p>I develop my CloudFormation templates in Visual Studio Code with the help of the <a href="https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml">redhat.vscode-yaml</a> extension.</p><p>You MUST add these custom tags to your VSCode settings.json file to get the best experience.</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;yaml.customTags&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;!And sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!If sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Not sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Equals sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Or sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!FindInMap sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Base64&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Cidr&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Cidr sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Ref&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Sub&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!GetAtt&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!GetAtt sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!GetAZs&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!ImportValue&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Select&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Select sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Split sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Join sequence&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;!Condition&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;yaml.format.enable&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h2 id="Prettier"><a href="#Prettier" class="headerlink" title="Prettier"></a>Prettier</h2><p>Also, check out <a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">Prettier</a> to help format your code.</p><p>With YAML, whitespace MATTERS! Consistently formatted code in general helps you read&#x2F;scan code quicker and makes code merges much easier.</p><h2 id="SAM-Validate"><a href="#SAM-Validate" class="headerlink" title="SAM Validate"></a>SAM Validate</h2><p>Don’t forget to add <code>sam validate</code> or <code>aws cloudformation validate-template</code> to your build step. I find that it only catches a few bugs, but it runs so quickly that it’s worth including.</p><h2 id="TL-DR"><a href="#TL-DR" class="headerlink" title="TL;DR;"></a>TL;DR;</h2><p>Use VS Code with the YAML extension with custom tags. Add Prettier as a bonus.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Writing CloudFormation or SAM templates by hand is very powerful, but can also be quite frustrating without a good IDE setup…&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
  </entry>
  
  <entry>
    <title>Amplify + React Build - Uncaught TypeError</title>
    <link href="https://bliskavka.com/2021/01/22/React-Build-Cannot-Read-Of-Undefined/"/>
    <id>https://bliskavka.com/2021/01/22/React-Build-Cannot-Read-Of-Undefined/</id>
    <published>2021-01-22T23:18:12.000Z</published>
    <updated>2024-02-25T19:57:57.965Z</updated>
    
    <content type="html"><![CDATA[<p>My AWS Amplify + React app throws an <code>Uncaught TypeError</code> when running the production build, but works just fine with <code>react-scripts start</code>.</p><span id="more"></span><h3 id="Error-in-Chrome"><a href="#Error-in-Chrome" class="headerlink" title="Error in Chrome:"></a>Error in Chrome:</h3><blockquote><p><code>Uncaught TypeError: Cannot read property &#39;call&#39; of undefined</code></p></blockquote><h3 id="Error-in-FireFox"><a href="#Error-in-FireFox" class="headerlink" title="Error in FireFox:"></a>Error in FireFox:</h3><blockquote><p><code>Uncaught TypeError: e[t] is undefined</code></p></blockquote><p>After TOO much troubleshooting, I finally found the error. For some reason calling the main configure function would blow up in production but not dev - I assume some component got trimmed out by Webpack. I am only using the <code>Auth</code> module of the Amplify SDK, so this solution works for me.</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// This throws error in production</span></span><br><span class="line"><span class="title class_">Amplify</span>.<span class="title function_">configure</span>(options);</span><br><span class="line"></span><br><span class="line"><span class="comment">// This does not throw an error</span></span><br><span class="line"><span class="title class_">Auth</span>.<span class="title function_">configure</span>(options);</span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Somehow I managed to bump into this error twice a few months apart, and could not recall what I did to solve it the first time. Hopefully, this saves you (and me) some troubleshooting time in the future.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;My AWS Amplify + React app throws an &lt;code&gt;Uncaught TypeError&lt;/code&gt; when running the production build, but works just fine with &lt;code&gt;react-scripts start&lt;/code&gt;.&lt;/p&gt;</summary>
    
    
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/Troubleshooting/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="React" scheme="https://bliskavka.com/tags/React/"/>
    
    <category term="AWS Amplify" scheme="https://bliskavka.com/tags/AWS-Amplify/"/>
    
  </entry>
  
  <entry>
    <title>Workshop: Creating Installable, Serverless Apps on AWS (Video)</title>
    <link href="https://bliskavka.com/2021/01/13/Serverless-AWS-Workshop/"/>
    <id>https://bliskavka.com/2021/01/13/Serverless-AWS-Workshop/</id>
    <published>2021-01-14T00:47:22.000Z</published>
    <updated>2024-02-25T19:57:57.966Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>AWS is taking over the wold. So it’s time we had a workshop to learn it. -Damian Montero, FloridaJS</p></blockquote><span id="more"></span><p>I will be presenting a virtual workshop on how to create installable, serverless applications in AWS.</p><p>This will be a whirlwind tour covering:</p><ul><li>Frontend: React w&#x2F; AWS Amplify</li><li>Hosting: S3 with CloudFront and Route53 for DNS</li><li>Authentication: AWS Cognito with self-sign-up</li><li>Backend: Lambda with API GW</li><li>Storage: S3 + DynamoDB</li></ul><p>All of those topics deserve their own workshops - but fortunately, we only have to cover the highlights! With the magic of <strong>Infrastructure as Code</strong>, you can replicate the <strong>workshop project</strong> in your own AWS environment in about <em>15-30 minutes</em>.</p><p>Register on Meetup: <a href="https://www.meetup.com/floridajs/events/275711268/">FloridaJS - AWS Workshop by Ivan Bliskavka</a></p><h2 id="Session-Recording"><a href="#Session-Recording" class="headerlink" title="Session Recording"></a>Session Recording</h2><iframe width="560" height="315" src="https://www.youtube.com/embed/pqcSUxIXYb4" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe><p><a href="https://github.com/ibliskavka/fl-js-sam-starter">Sample Code</a></p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;AWS is taking over the wold. So it’s time we had a workshop to learn it. -Damian Montero, FloridaJS&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Training" scheme="https://bliskavka.com/categories/Training/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
    <category term="AWS SAM CLI" scheme="https://bliskavka.com/tags/AWS-SAM-CLI/"/>
    
  </entry>
  
  <entry>
    <title>&quot;Error: Unable to upload artifact referenced by Location parameter of resource. &#39;S3Uploader&#39; object is not subscriptable.&quot;
</title>
    <link href="https://bliskavka.com/2020/12/18/unable-to-upload-artifact-s3uploader-object-is-not-subscriptable/"/>
    <id>https://bliskavka.com/2020/12/18/unable-to-upload-artifact-s3uploader-object-is-not-subscriptable/</id>
    <published>2020-12-18T15:12:06.000Z</published>
    <updated>2024-02-25T16:15:12.116Z</updated>
    
    <content type="html"><![CDATA[<p>While working on a multi-stack AWS SAM package I came across this rather obscure error:</p><blockquote><p><strong>Error</strong>: Unable to upload artifact ..&#x2F;..&#x2F;spa-hosting.yaml referenced by Location parameter of Hosting resource. ‘S3Uploader’ object is not subscriptable</p></blockquote><span id="more"></span><p>After an embarrassing amount of troubleshooting, the culprit turns out to be the <code>Metadata[AWS::ServerlessRepo::Application]</code> element in the nested stack.</p><p>The nested stack is also published to the Serverless Application Repository, but I copied the template into this project for some additional customizations. As soon as I removed that element, the stack deployed correctly.</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># template.yaml</span></span><br><span class="line"></span><br><span class="line"><span class="attr">Hosting:</span></span><br><span class="line">  <span class="attr">Type:</span> <span class="string">AWS::Serverless::Application</span></span><br><span class="line">  <span class="attr">Properties:</span></span><br><span class="line">    <span class="attr">Location:</span> <span class="string">../../spa-hosting.yaml</span></span><br><span class="line">    <span class="attr">Parameters:</span></span><br><span class="line">      <span class="attr">BucketName:</span> <span class="type">!Ref</span> <span class="string">AWS::StackName</span></span><br></pre></td></tr></table></figure><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># spa-hosting.yaml</span></span><br><span class="line"></span><br><span class="line"><span class="attr">Metadata:</span></span><br><span class="line">  <span class="attr">AWS::ServerlessRepo::Application:</span></span><br><span class="line">    <span class="attr">Name:</span> <span class="string">spa-hosting</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">Creates</span> <span class="string">the</span> <span class="string">AWS</span> <span class="string">infrastructure</span> <span class="string">for</span> <span class="string">running</span> <span class="string">a</span> <span class="string">Single</span> <span class="string">Page</span> <span class="string">App</span>  <span class="string">in</span> <span class="string">AWS</span> <span class="string">using</span> <span class="string">S3</span> <span class="string">&amp;</span> <span class="string">CloudFront</span></span><br><span class="line">    <span class="attr">Author:</span> <span class="string">Ivan</span> <span class="string">Bliskavka</span></span><br><span class="line">    <span class="attr">ReadmeUrl:</span> <span class="string">README.md</span></span><br><span class="line">    <span class="attr">HomePageUrl:</span> <span class="string">https://bliskavka.com</span></span><br><span class="line">    <span class="attr">SemanticVersion:</span> <span class="number">1.0</span><span class="number">.0</span></span><br></pre></td></tr></table></figure><p>I hope this saves somebody some headaches!</p><p>-Ivan</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;While working on a multi-stack AWS SAM package I came across this rather obscure error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Error&lt;/strong&gt;: Unable to upload artifact ..&amp;#x2F;..&amp;#x2F;spa-hosting.yaml referenced by Location parameter of Hosting resource. ‘S3Uploader’ object is not subscriptable&lt;/p&gt;
&lt;/blockquote&gt;</summary>
    
    
    
    <category term="Troubleshooting" scheme="https://bliskavka.com/categories/Troubleshooting/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
    <category term="AWS SAM CLI" scheme="https://bliskavka.com/tags/AWS-SAM-CLI/"/>
    
  </entry>
  
  <entry>
    <title>AWS Solution Architect Pro Cert</title>
    <link href="https://bliskavka.com/2020/09/28/AWS-Solution-Architect-Pro-Cert/"/>
    <id>https://bliskavka.com/2020/09/28/AWS-Solution-Architect-Pro-Cert/</id>
    <published>2020-09-28T20:24:34.000Z</published>
    <updated>2024-02-25T19:57:57.964Z</updated>
    
    <content type="html"><![CDATA[<p>I am now a certified <a href="https://www.youracclaim.com/badges/dd8154b4-ccf9-46ce-a5c6-2bdb7dfc99fc/linked_in">AWS Solution Architect Professional</a>!</p><p>I am very proud of this. It’s probably the most difficult test I have ever taken. 2.5 years of AWS experience and 40+ hours of training video.</p><span id="more"></span><p>I do a fair amount of moonlighting after hours. If you need some expert assistance in Serverless, ECS, or CloudFormation, you can reach me on <a href="https://www.linkedin.com/in/ibliskavka">LinkedIn</a>.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I am now a certified &lt;a href=&quot;https://www.youracclaim.com/badges/dd8154b4-ccf9-46ce-a5c6-2bdb7dfc99fc/linked_in&quot;&gt;AWS Solution Architect Professional&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I am very proud of this. It’s probably the most difficult test I have ever taken. 2.5 years of AWS experience and 40+ hours of training video.&lt;/p&gt;</summary>
    
    
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>Roku - Is Live</title>
    <link href="https://bliskavka.com/2020/09/03/Roku-Released/"/>
    <id>https://bliskavka.com/2020/09/03/Roku-Released/</id>
    <published>2020-09-03T21:11:09.000Z</published>
    <updated>2024-02-25T19:57:57.965Z</updated>
    
    <content type="html"><![CDATA[<p>I am very excited to announce that Screen Saver Gallery is finally live on Roku!</p><p><a href="https://www.roku.com/products/roku-express">Roku</a> is a device that can turn any TV into a streaming smart TV for as little as $25!</p><h2 id="Get-it-Now"><a href="#Get-it-Now" class="headerlink" title="Get it Now"></a>Get it Now</h2><ol><li><a href="https://channelstore.roku.com/details/595150/screen-saver-gallery">Install Screen Saver Gallery</a></li><li>Leave a 5 Star Review</li><li>Share this with your friends</li><li>Enjoy!</li></ol><h2 id="Great-new-features"><a href="#Great-new-features" class="headerlink" title="Great new features"></a>Great new features</h2><ul><li>Auto Start<ul><li>Unlike Windows and Xbox - Roku supports screensaver apps, so the screen saver will auto-start when the TV is inactive.</li></ul></li><li>4 Free Photo Categories<ul><li>I am making the most popular categories free, with no ads, and no subscription required.</li></ul></li><li>75 Photo Categories Total<ul><li>I expanded the category selection to 75! Mix and match the categories to get a photo mix that fits your personality</li></ul></li><li>Channel Mode<ul><li>Open the app at any time to browse photos, play&#x2F;pause, and configure settings</li></ul></li></ul>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I am very excited to announce that Screen Saver Gallery is finally live on Roku!&lt;/p&gt;
&lt;p&gt;&lt;a</summary>
        
      
    
    
    
    
    <category term="Screen Saver Gallery" scheme="https://bliskavka.com/tags/Screen-Saver-Gallery/"/>
    
    <category term="Roku" scheme="https://bliskavka.com/tags/Roku/"/>
    
  </entry>
  
  <entry>
    <title>Assume Role with MFA</title>
    <link href="https://bliskavka.com/2020/02/19/assume-role-mfa/"/>
    <id>https://bliskavka.com/2020/02/19/assume-role-mfa/</id>
    <published>2020-02-19T17:41:58.000Z</published>
    <updated>2024-02-25T19:57:57.967Z</updated>
    
    <content type="html"><![CDATA[<h2 id="CLI-Assume-Role-with-MFA-assume-role-mfa-sh"><a href="#CLI-Assume-Role-with-MFA-assume-role-mfa-sh" class="headerlink" title="CLI Assume Role with MFA (assume-role-mfa.sh)"></a>CLI Assume Role with MFA (assume-role-mfa.sh)</h2><p>This script will assume a cross-account role using your MFA device and output the credentials into a named profile.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">ROLE_ARN=<span class="variable">$1</span></span><br><span class="line">OUTPUT_PROFILE=<span class="variable">$2</span></span><br><span class="line">MFA_SERIAL=<span class="variable">$3</span></span><br><span class="line">MFA_TOKEN=<span class="variable">$4</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Assuming role <span class="variable">$ROLE_ARN</span>&quot;</span></span><br><span class="line">sts=$(aws sts assume-role \</span><br><span class="line">  --role-arn <span class="string">&quot;<span class="variable">$ROLE_ARN</span>&quot;</span> \</span><br><span class="line">  --role-session-name <span class="string">&quot;<span class="variable">$OUTPUT_PROFILE</span>&quot;</span> \</span><br><span class="line">  --query <span class="string">&#x27;Credentials.[AccessKeyId,SecretAccessKey,SessionToken]&#x27;</span> \</span><br><span class="line">  --output text \</span><br><span class="line">  --serial-number <span class="variable">$MFA_SERIAL</span> \</span><br><span class="line">  --token-code <span class="string">&quot;<span class="variable">$MFA_TOKEN</span>&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Converting sts to array&quot;</span></span><br><span class="line">sts=(<span class="variable">$sts</span>)</span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;AWS_ACCESS_KEY_ID is <span class="variable">$&#123;sts[0]&#125;</span>&quot;</span></span><br><span class="line">aws configure <span class="built_in">set</span> aws_access_key_id <span class="variable">$&#123;sts[0]&#125;</span> --profile <span class="variable">$OUTPUT_PROFILE</span></span><br><span class="line">aws configure <span class="built_in">set</span> aws_secret_access_key <span class="variable">$&#123;sts[1]&#125;</span> --profile <span class="variable">$OUTPUT_PROFILE</span></span><br><span class="line">aws configure <span class="built_in">set</span> aws_session_token <span class="variable">$&#123;sts[2]&#125;</span> --profile <span class="variable">$OUTPUT_PROFILE</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;credentials stored in the profile named <span class="variable">$OUTPUT_PROFILE</span>&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Usage-Example"><a href="#Usage-Example" class="headerlink" title="Usage Example"></a>Usage Example</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./assume-role-mfa.sh <span class="variable">$CLIENT_ROLE_ARN</span> client arn:aws:iam::&#123;YOUR_ACCOUNT&#125;:mfa/ivan &#123;MFA_CODE&#125;</span><br><span class="line">aws s3 <span class="built_in">ls</span> --profile client --region us-east-1</span><br></pre></td></tr></table></figure><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>I hope this saves you a few searches!</p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;h2 id=&quot;CLI-Assume-Role-with-MFA-assume-role-mfa-sh&quot;&gt;&lt;a href=&quot;#CLI-Assume-Role-with-MFA-assume-role-mfa-sh&quot; class=&quot;headerlink&quot; title=&quot;CLI</summary>
        
      
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="Security" scheme="https://bliskavka.com/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>Debug Typescript with VS Code Jasmine-TS Extension</title>
    <link href="https://bliskavka.com/2020/01/06/debug-with-jasmine-ts/"/>
    <id>https://bliskavka.com/2020/01/06/debug-with-jasmine-ts/</id>
    <published>2020-01-06T16:59:36.000Z</published>
    <updated>2024-02-25T19:57:57.969Z</updated>
    
    <content type="html"><![CDATA[<p>At work, we develop a lot of AWS Lambda code and occasionally I see a junior developer deploy a Lambda EACH time they test, and they test manually in the Lambda UI!</p><span id="more"></span><p>This is extremely inefficient. You should run your code locally and here is a short video describing the process</p><iframe width="560" height="315" src="https://www.youtube.com/embed/YQW_NNFvMh8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe><h2 id="Caveats"><a href="#Caveats" class="headerlink" title="Caveats"></a>Caveats</h2><ul><li>If you develop on Windows, there may be some edge cases where the code executes fine locally but has issues in the Lambda (Linux) runtime.<ul><li>This is usually caused by line endings, forward vs back slashes, and exec&#x2F;spawn commands.</li><li>Solution: Use <a href="https://learn.microsoft.com/en-us/windows/wsl/install">Windows Subsystem for Linux</a> for Lambda development</li></ul></li></ul><h2 id="Update-2023-06-29"><a href="#Update-2023-06-29" class="headerlink" title="Update 2023-06-29"></a>Update 2023-06-29</h2><p>I switched to <code>ts-jest</code> and the <a href="https://marketplace.visualstudio.com/items?itemName=firsttris.vscode-jest-runner">Jest Runner</a> VS Code extension. It does the same thing as the above example but is a little simpler to set up.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;At work, we develop a lot of AWS Lambda code and occasionally I see a junior developer deploy a Lambda EACH time they test, and they test manually in the Lambda UI!&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="Lambda" scheme="https://bliskavka.com/tags/Lambda/"/>
    
    <category term="VS Code" scheme="https://bliskavka.com/tags/VS-Code/"/>
    
    <category term="Typescript" scheme="https://bliskavka.com/tags/Typescript/"/>
    
    <category term="NodeJS" scheme="https://bliskavka.com/tags/NodeJS/"/>
    
  </entry>
  
  <entry>
    <title>Smart Cities Hackathon - 1st Place</title>
    <link href="https://bliskavka.com/2019/10/14/hackathon-2019/"/>
    <id>https://bliskavka.com/2019/10/14/hackathon-2019/</id>
    <published>2019-10-14T14:21:28.000Z</published>
    <updated>2024-02-25T19:57:57.970Z</updated>
    
    <content type="html"><![CDATA[<p>Our team won the South Florida Tech Hub Hackathon against 20 teams. It was hard work but what an experience!</p><span id="more"></span><iframe width="560" height="315" src="https://www.youtube.com/embed/fXx_pysTPGo" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe><p>First place, $2.500 prize: Hurricane Helper</p><p>Hurricane Helper’s progressive AWS-based web application is called EDNA – Emergency Disaster Network Application. EDNA is an online platform that streamlines disaster management by using a national volunteer network in a cloud call center.</p><ul><li><p>Tips</p><ul><li>Don’t be greedy. More members on your team means you can get more done and build something impressive - we had the max at 8. It was an 8-way split, but I don’t think we would have won with 4.</li><li>Focus on what makes your app special. Don’t worry about the login screen and user management, you can fake that for the presentation.<ul><li>The goal of a hackathon is to pitch the idea and create a Proof-of-Concept.</li></ul></li><li>Exploit existing skills - not everyone needs to be a developer.<ul><li>You need someone to work on the pitch, someone to present, and someone to keep notes.</li><li>I had experience with Amazon Connect and experience leading a team, so I took that role.</li></ul></li><li>Check in often. We had a standup every 90 minutes to check progress.</li><li>If something isn’t working, it’s OK to pivot to an alternative.<ul><li>Initially, we were designing an Express API but the time to design and implement was taking too long. In the end, we used a single serverless function in AWS Lambda.</li></ul></li><li>Include business metrics in the presentation!<ul><li>We included an estimated cost of our platform vs a commercial contact center.</li><li>The decision-makers are typically not technical and don’t care about the tech, they care about the business results.</li></ul></li><li>Have fun and don’t take it too seriously.<ul><li>We called our submission EDNA, and our key persona was Grandma EDNA. It’s OK to work in some humor and be a little tongue-in-cheek.</li></ul></li></ul></li><li><p>Team Members</p><ul><li>Ivan Bliskavka - Lead</li><li>Derek Donev - Backend</li><li>Erik White - Frontend</li><li>Mike Tobin - Dev</li><li>Michael Roth - PM</li><li>Holden Gibler - Presenter</li><li>Taylor Gagne - Backend</li><li>Alex Ciccolella - Pitch Deck</li></ul></li><li><p>Links</p><ul><li><a href="https://techhubsouthflorida.org/smart-cities-hurricane-relief-hackathon/">South Florida Tech Hub Post</a></li><li><a href="./EDNA-Presentation.pdf">Presentation</a></li></ul></li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;Our team won the South Florida Tech Hub Hackathon against 20 teams. It was hard work but what an experience!&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Connect" scheme="https://bliskavka.com/tags/Connect/"/>
    
  </entry>
  
  <entry>
    <title>AWS Developer Certified Developer</title>
    <link href="https://bliskavka.com/2019/08/30/AWS-Developer-Cert/"/>
    <id>https://bliskavka.com/2019/08/30/AWS-Developer-Cert/</id>
    <published>2019-08-30T12:37:17.000Z</published>
    <updated>2024-02-25T19:57:57.963Z</updated>
    
    <content type="html"><![CDATA[<p>I am now an <a href="https://www.certmetrics.com/amazon/public/badge.aspx?i=2&t=c&d=2019-08-29&ci=AWS00430147">AWS Certified Developer - Associate</a>!</p><p><img src="aws-certified-developer.png" alt="AWS Certified Developer"></p><p>I had about 8 hours to kill so I bought some <a href="https://www.udemy.com/share/101WNqBEYadVtVRng=/">Udemy Practice Tests</a> - a week later I had my cert!</p><p>Let’s talk about how moving to the cloud can save you $$$! You can reach me on <a href="https://www.linkedin.com/in/ibliskavka">LinkedIn</a></p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I am now an &lt;a href=&quot;https://www.certmetrics.com/amazon/public/badge.aspx?i=2&amp;t=c&amp;d=2019-08-29&amp;ci=AWS00430147&quot;&gt;AWS Certified Developer -</summary>
        
      
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>Assume Cross Account AWS Role</title>
    <link href="https://bliskavka.com/2019/08/14/AWS-Assume-Role/"/>
    <id>https://bliskavka.com/2019/08/14/AWS-Assume-Role/</id>
    <published>2019-08-14T18:15:51.000Z</published>
    <updated>2024-02-25T16:15:11.998Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Unlike an embarrassing Facebook post, developers can’t simply say <em>“That wasn’t me, I got hacked”</em> and expect it all to go away…</p></blockquote><p>Sarcasm aside, security without passwords is not only convenient, it keeps the password from landing in the wrong hands.</p><span id="more"></span><h2 id="Scenario"><a href="#Scenario" class="headerlink" title="Scenario"></a>Scenario</h2><p>We (the vendor) like to ship our work to a client’s account with our Code Pipeline using a CodeBuild project. Rather than sharing access keys, we prefer to assume a cross-account role. This allows the client to control what permissions the role has access to, and we control who can access the role.</p><p>TL;DR; Scroll to the bottom if you simply want the <code>assume-role.sh</code> script!</p><h2 id="The-Process"><a href="#The-Process" class="headerlink" title="The Process"></a>The Process</h2><ol><li>We create a role on our vendor AWS account.</li><li>Client creates a role on their AWS account and allows the vendor role to assume their role.</li><li>The vendor role assumes the client role when we have to perform cross-account operations.<ul><li>We are using a role here because the process executes from CodeBuild. This works with IAM users also.</li><li>This article is about cross-account roles, but you can use this script to assume any role you have access to.</li></ul></li></ol><h2 id="Vendor-Role"><a href="#Vendor-Role" class="headerlink" title="Vendor Role"></a>Vendor Role</h2><p>This CloudFormation snippet is usually part of a larger pipeline template. I scaled it down to just the role</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&quot;2010-09-09&quot;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">  Creates a role that will be used to assume client account role</span></span><br><span class="line"><span class="string"></span><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">ClientRoleArn:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line">    <span class="attr">Default:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">      Will be blank initially.</span></span><br><span class="line"><span class="string">      Update with client&#x27;s role arn after it has been created.</span></span><br><span class="line"><span class="string"></span><span class="attr">Conditions:</span></span><br><span class="line">  <span class="attr">IsClientRoleArnSet:</span> <span class="type">!Not</span> [ <span class="type">!Equals</span> [<span class="type">!Ref</span> <span class="string">ClientRoleArn</span>, <span class="string">&#x27;&#x27;</span>]]</span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">VendorRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::IAM::Role</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="number">2012-10-17</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Principal:</span></span><br><span class="line">              <span class="attr">Service:</span> <span class="string">codebuild.amazonaws.com</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">sts:AssumeRole</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">assume-client-role</span></span><br><span class="line">          <span class="attr">PolicyDocument:</span></span><br><span class="line">            <span class="attr">Version:</span> <span class="string">&#x27;2012-10-17&#x27;</span></span><br><span class="line">            <span class="attr">Statement:</span></span><br><span class="line">              <span class="comment"># Add any additional permissions that you might need</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">Resource:</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">                <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">                <span class="attr">Action:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="string">logs:*</span></span><br><span class="line">              <span class="comment"># Will create this rule only once the ClientRoleArn is set</span></span><br><span class="line">              <span class="bullet">-</span> <span class="type">!If</span></span><br><span class="line">                <span class="bullet">-</span> <span class="string">IsClientRoleArnSet</span></span><br><span class="line">                <span class="bullet">-</span> <span class="attr">Resource:</span> <span class="type">!Ref</span> <span class="string">ClientRoleArn</span></span><br><span class="line">                  <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">                  <span class="attr">Action:</span></span><br><span class="line">                    <span class="bullet">-</span> <span class="string">sts:AssumeRole</span></span><br><span class="line">                <span class="bullet">-</span> <span class="type">!Ref</span> <span class="string">AWS::NoValue</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">VendorRoleArn:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">Pass</span> <span class="string">this</span> <span class="string">to</span> <span class="string">the</span> <span class="string">client&#x27;s</span> <span class="string">role</span> <span class="string">template.</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> [<span class="string">VendorRole</span>, <span class="string">Arn</span>]</span><br></pre></td></tr></table></figure><h2 id="Client’s-Role"><a href="#Client’s-Role" class="headerlink" title="Client’s Role"></a>Client’s Role</h2><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">AWSTemplateFormatVersion:</span> <span class="string">&quot;2010-09-09&quot;</span></span><br><span class="line"><span class="attr">Description:</span> <span class="string">&gt;</span></span><br><span class="line"><span class="string">  Creates a role that the vendor can assume</span></span><br><span class="line"><span class="string"></span><span class="attr">Parameters:</span></span><br><span class="line">  <span class="attr">VendorRoleArn:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">Should</span> <span class="string">be</span> <span class="string">set</span> <span class="string">to</span> <span class="string">the</span> <span class="string">VendorRoleArn</span> <span class="string">from</span> <span class="string">the</span> <span class="string">previous</span> <span class="string">stack.</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">String</span></span><br><span class="line"><span class="attr">Resources:</span></span><br><span class="line">  <span class="attr">ClientRole:</span></span><br><span class="line">    <span class="attr">Type:</span> <span class="string">AWS::IAM::Role</span></span><br><span class="line">    <span class="attr">Properties:</span></span><br><span class="line">      <span class="attr">Path:</span> <span class="string">/</span></span><br><span class="line">      <span class="attr">AssumeRolePolicyDocument:</span></span><br><span class="line">        <span class="attr">Version:</span> <span class="number">2012-10-17</span></span><br><span class="line">        <span class="attr">Statement:</span></span><br><span class="line">          <span class="bullet">-</span> <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">            <span class="attr">Principal:</span></span><br><span class="line">              <span class="attr">AWS:</span> <span class="type">!Ref</span> <span class="string">VendorRoleArn</span></span><br><span class="line">            <span class="attr">Action:</span> <span class="string">sts:AssumeRole</span></span><br><span class="line">      <span class="attr">Policies:</span></span><br><span class="line">        <span class="comment"># Add any additional policies to the role here</span></span><br><span class="line">        <span class="bullet">-</span> <span class="attr">PolicyName:</span> <span class="string">code-commit-access</span></span><br><span class="line">          <span class="attr">PolicyDocument:</span></span><br><span class="line">            <span class="attr">Version:</span> <span class="number">2012-10-17</span></span><br><span class="line">            <span class="attr">Statement:</span></span><br><span class="line">              <span class="bullet">-</span> <span class="attr">Resource:</span> <span class="string">&#x27;*&#x27;</span></span><br><span class="line">                <span class="attr">Effect:</span> <span class="string">Allow</span></span><br><span class="line">                <span class="attr">Action:</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="string">codecommit:GitPull</span></span><br><span class="line">                  <span class="bullet">-</span> <span class="string">codecommit:GitPush</span></span><br><span class="line"><span class="attr">Outputs:</span></span><br><span class="line">  <span class="attr">ClientRoleArn:</span></span><br><span class="line">    <span class="attr">Description:</span> <span class="string">Put</span> <span class="string">this</span> <span class="string">ARN</span> <span class="string">back</span> <span class="string">into</span> <span class="string">the</span> <span class="string">VendorRole</span> <span class="string">template</span></span><br><span class="line">    <span class="attr">Value:</span> <span class="type">!GetAtt</span> [<span class="string">ClientRole</span>, <span class="string">Arn</span>]</span><br></pre></td></tr></table></figure><p>NOTE: Don’t forget to put the ClientRoleArn into the vendor stack and redeploy!</p><h2 id="Assume-the-role-assume-role-sh"><a href="#Assume-the-role-assume-role-sh" class="headerlink" title="Assume the role (assume-role.sh)"></a>Assume the role (assume-role.sh)</h2><p>This script uses Simple Token Service to create a temporary credential that is stored in the <code>client</code> profile.</p><p>NOTE: This script uses jq, make sure it is installed on your system</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line">ROLE_ARN=<span class="variable">$1</span></span><br><span class="line">OUTPUT_PROFILE=<span class="variable">$2</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Assuming role <span class="variable">$ROLE_ARN</span>&quot;</span></span><br><span class="line">sts=$(aws sts assume-role \</span><br><span class="line">  --role-arn <span class="string">&quot;<span class="variable">$ROLE_ARN</span>&quot;</span> \</span><br><span class="line">  --role-session-name <span class="string">&quot;<span class="variable">$OUTPUT_PROFILE</span>&quot;</span> \</span><br><span class="line">  --query <span class="string">&#x27;Credentials.[AccessKeyId,SecretAccessKey,SessionToken]&#x27;</span> \</span><br><span class="line">  --output text)</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;Converting sts to array&quot;</span></span><br><span class="line">sts=(<span class="variable">$sts</span>)</span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;AWS_ACCESS_KEY_ID is <span class="variable">$&#123;sts[0]&#125;</span>&quot;</span></span><br><span class="line">aws configure <span class="built_in">set</span> aws_access_key_id <span class="variable">$&#123;sts[0]&#125;</span> --profile <span class="variable">$OUTPUT_PROFILE</span></span><br><span class="line">aws configure <span class="built_in">set</span> aws_secret_access_key <span class="variable">$&#123;sts[1]&#125;</span> --profile <span class="variable">$OUTPUT_PROFILE</span></span><br><span class="line">aws configure <span class="built_in">set</span> aws_session_token <span class="variable">$&#123;sts[2]&#125;</span> --profile <span class="variable">$OUTPUT_PROFILE</span></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;credentials stored in the profile named <span class="variable">$OUTPUT_PROFILE</span>&quot;</span></span><br></pre></td></tr></table></figure><p>Based on work from: <a href="https://rizvir.com/articles/AWS-cli-tips">rizvir.com</a></p><h2 id="Usage-Example"><a href="#Usage-Example" class="headerlink" title="Usage Example"></a>Usage Example</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./assume-role.sh <span class="variable">$CLIENT_ROLE_ARN</span> client</span><br><span class="line">aws s3 <span class="built_in">ls</span> --profile client --region us-east-1</span><br></pre></td></tr></table></figure><h2 id="Tips"><a href="#Tips" class="headerlink" title="Tips"></a>Tips</h2><ul><li>If you are using CodeBuild, assume-role.sh must be a separate file and not integrated into the <code>buildspec.yml</code>. This is because buildspec executes under SH rather than BASH so it does not support arrays.</li></ul><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This has been a huge help for me when deploying between AWS accounts - either our own or clients. I hope this helps you on your DevOps journey!</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Unlike an embarrassing Facebook post, developers can’t simply say &lt;em&gt;“That wasn’t me, I got hacked”&lt;/em&gt; and expect it all to go away…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sarcasm aside, security without passwords is not only convenient, it keeps the password from landing in the wrong hands.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="Security" scheme="https://bliskavka.com/tags/Security/"/>
    
  </entry>
  
  <entry>
    <title>Cold Transfer in AWS Connect Streams</title>
    <link href="https://bliskavka.com/2019/06/03/AWS-Connect-Cold-Transfer/"/>
    <id>https://bliskavka.com/2019/06/03/AWS-Connect-Cold-Transfer/</id>
    <published>2019-06-03T16:39:08.000Z</published>
    <updated>2024-02-25T16:15:11.999Z</updated>
    
    <content type="html"><![CDATA[<p>The <a href="https://github.com/aws/amazon-connect-streams">Amazon Connect Streams API</a> allows you to create custom interfaces for the soft-phone and provides many functions for interacting with the current connection. It is a powerful set of tools but some of the more sophisticated functions may not be obvious.</p><span id="more"></span><p>Check out my recent <a href="https://voicefoundry.com/blog">VoiceFoundry Blog</a> post on <a href="https://voicefoundry.com/aws_connect_streams_api">How to Implement Cold Transfer in AWS Connect Streams API</a>. </p>]]></content>
    
    
    <summary type="html">&lt;p&gt;The &lt;a href=&quot;https://github.com/aws/amazon-connect-streams&quot;&gt;Amazon Connect Streams API&lt;/a&gt; allows you to create custom interfaces for the soft-phone and provides many functions for interacting with the current connection. It is a powerful set of tools but some of the more sophisticated functions may not be obvious.&lt;/p&gt;</summary>
    
    
    
    <category term="HowTo" scheme="https://bliskavka.com/categories/HowTo/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Connect" scheme="https://bliskavka.com/tags/Connect/"/>
    
    <category term="HowTo" scheme="https://bliskavka.com/tags/HowTo/"/>
    
  </entry>
  
  <entry>
    <title>A*GAME Products</title>
    <link href="https://bliskavka.com/2019/01/24/A-Game-Products/"/>
    <id>https://bliskavka.com/2019/01/24/A-Game-Products/</id>
    <published>2019-01-25T01:08:33.000Z</published>
    <updated>2024-02-25T19:57:57.961Z</updated>
    
    <content type="html"><![CDATA[<p>Did you know how easy it is to create an online store these days? We configured a single product store and were taking orders in 4 hours!</p><span id="more"></span><p>One of my friends has been running an online supplement store using WordPress for a few years. Everything was running fine until he got hacked and had to pay someone to restore the site. To protect from future hacks, he added SiteLock and CodeGuard, which raised his $15&#x2F;mo site to $85&#x2F;mo.</p><h3 id="Moving-to-Shopify"><a href="#Moving-to-Shopify" class="headerlink" title="Moving to Shopify"></a>Moving to Shopify</h3><p>I recommended that he move to Shopify for the following reasons.</p><ul><li>He got hacked because his WordPress site was not receiving security patches. Shopify handles patching for you so that is one less thing to worry about.</li><li>Shopify is configured for SSL for free. This secures the transaction and eliminates the “This site is not secure” warning from modern browsers.</li><li>Shopify supports blogging, mailing lists, and printing labels directly from the admin area.</li><li>Shopify provides all of this for only <em>$15&#x2F;mo</em></li></ul><h3 id="Screenshots"><a href="#Screenshots" class="headerlink" title="Screenshots"></a>Screenshots</h3><p>During the transition, we used a starter template that already had good contrast and layouts that fit the original style. We simply updated the colors.</p><p><img src="a-game-home-page.png" alt="People don&#39;t read paragraphs on landing pages, you must catch their eye with a pitch."></p><p><img src="a-game-product-page.png" alt="The product page now looks more professional with an obvious &#39;call to action&#39;"></p><p><img src="a-game-cart-page.png" alt="The cart page has a much cleaner feel"></p><h2 id="Check-out-the-site-at-A-GameProducts-com"><a href="#Check-out-the-site-at-A-GameProducts-com" class="headerlink" title="Check out the site at A-GameProducts.com"></a>Check out the site at <a href="https://a-gameproducts.com/">A-GameProducts.com</a></h2>]]></content>
    
    
    <summary type="html">&lt;p&gt;Did you know how easy it is to create an online store these days? We configured a single product store and were taking orders in 4 hours!&lt;/p&gt;</summary>
    
    
    
    <category term="Entrepreneurship" scheme="https://bliskavka.com/categories/Entrepreneurship/"/>
    
    
    <category term="Shopify" scheme="https://bliskavka.com/tags/Shopify/"/>
    
    <category term="Entrepreneurship" scheme="https://bliskavka.com/tags/Entrepreneurship/"/>
    
    <category term="SEO" scheme="https://bliskavka.com/tags/SEO/"/>
    
  </entry>
  
  <entry>
    <title>How I Built a $700/mo Income Stream</title>
    <link href="https://bliskavka.com/2018/09/24/How-I-Built-a-700-mo-Income-Stream/"/>
    <id>https://bliskavka.com/2018/09/24/How-I-Built-a-700-mo-Income-Stream/</id>
    <published>2018-09-24T12:29:15.000Z</published>
    <updated>2024-02-25T19:57:57.964Z</updated>
    
    <content type="html"><![CDATA[<p>First and foremost – there are few things more awesome than getting a direct deposit every month for work you did years ago. What was once modest beer money at $50 a month is now almost serious alcoholic money at $700 a month!</p><p>How did I do it? Well, hard work for one. You must account for the fact that I have 10 years of software development experience and was able to put together the bulk of the app over a weekend. Don’t let that scare you off, these tips are not technical and apply to anything from an app to a blog.</p><span id="more"></span><h2 id="Optimize-for-Organic-Keyword-Traffic"><a href="#Optimize-for-Organic-Keyword-Traffic" class="headerlink" title="Optimize for Organic Keyword Traffic"></a>Optimize for Organic Keyword Traffic</h2><p>I wanted to build a screen saver for the Windows 10 store but there was one problem – there was no method for auto-starting a store app after the computer had been idle.</p><p>Screensaver is a very common keyword though and I really wanted to get that organic traffic. People love to see pretty pictures and will search for them. Additionally, less experienced users refer to desktop backgrounds as screensavers – so the word has some flux. I also wanted to indicate that the app uses photos so I named my app: Screen Saver Gallery.</p><p>The name felt awkward at first – but after some time it kind of rolls off the tongue (at least for me).</p><p>The app is currently being downloaded over 6,000 a month organically and I attribute that primarily to the name.</p><h2 id="Ask-for-Reviews"><a href="#Ask-for-Reviews" class="headerlink" title="Ask for Reviews"></a>Ask for Reviews</h2><p>I use social trust daily. If other people think a product is great – I am much more likely to consider it.</p><p>Ask your users for reviews – but only after they have had a chance to use the app. If you prompt them immediately they will give you 1-3 stars and say something like “Geez, let me use it first”.</p><p>I set my prompt to 7 days. I assume that if they use the app for 7 days they are more likely to give me a good review. Don’t nag – I allow users to snooze the review reminder for 30 days or forever.</p><p>Of course, you will always get people that dislike your app and post a bad review without being prompted. There is not much you can do about that except:</p><ol><li>Fix any reported issues and respond to their review</li><li>If they are complaining about a missing feature that you never implemented or advertised you may point that out so that the next user can see your response.</li><li>Try to be honest in your app description – you don’t want to mislead someone into thinking you are providing something you are not. (<strong>Screen Saver</strong> Gallery falls into this a bit but I am honest in my description)</li><li>Don’t sweat the trolls.</li></ol><h2 id="Don’t-Be-A-Leaky-Bucket"><a href="#Don’t-Be-A-Leaky-Bucket" class="headerlink" title="Don’t Be A Leaky Bucket"></a>Don’t Be A Leaky Bucket</h2><p>Take crash reports &amp; hangs seriously. Implement remote metrics and instrumentation like Application Insights, HockeyApp, or Google Analytics. Monitor and fix bugs. Make sure your app is 100% reliable or fails gracefully.</p><p>For a while, I had a leaky bucket. I would acquire users but my total engagement hours would stay the same. I spent a few weeks rewriting my app to use AWS S3 with CloudFront &amp; Lambda to improve my latency and availability. My crash rate came down 75% and my usage started to grow consistently.</p><p>Within the app itself, I added error checks for user inputs and error handlers with meaningful messages. If the app cannot download photos – it’s just about useless. Write code to try and determine the source of the problem, log it, and indicate to the user what is going on in a friendly, meaningful way.</p><h2 id="Look-for-Recurring-Revenues"><a href="#Look-for-Recurring-Revenues" class="headerlink" title="Look for Recurring Revenues"></a>Look for Recurring Revenues</h2><p>I am a big fan of paying for software once; I would prefer that for my app. Initially, I had a non-expiring add-on that removed advertising. I quickly realized though that I had monthly time &amp; money costs to provide quality content. I have hosting costs and I monitor image sources daily for poor quality or inappropriate content.</p><p>With that in mind, I created 2 add-ons that remove the advertising: 6 months and 12 months. I have plans to add an auto-renewing subscription that people can opt into.</p><h2 id="Optimize-Your-Costs"><a href="#Optimize-Your-Costs" class="headerlink" title="Optimize Your Costs"></a>Optimize Your Costs</h2><p>I reduced my server costs from $40&#x2F;mo to about $4&#x2F;mo this summer. My cost for running Web API on Azure was about $40&#x2F;month on a single server. This means that I was incurring latency costs for global users and if there was a problem with the server my app wouldn’t work.</p><p>After studying AWS I saw some excellent cost optimization opportunities that also improved API performance. Most of my content is static so I created a public S3 bucket with a CloudFront distribution. I have a Lambda function that runs throughout the day to refresh each content source once every 24 hours. CloudFront caches my content at edge locations throughout the world so I get much better latency. Additional dynamic APIs are handled using Lambda and API Gateway.</p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>Based on my experience with my modestly successful app, the above is what I consider important. My app does not change lives or cure cancer but it provides enough value to people for them to leave excellent reviews. It works quite well on Xbox and I like to leave it running along with Pandora or an audiobook.</p><p>Check out <a href="https://www.microsoft.com/en-us/store/apps/screen-saver-gallery/9nblggh5j8tx" title="Screen Saver Gallery Store Link">Screen Saver Gallery</a> in the Windows Store.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;First and foremost – there are few things more awesome than getting a direct deposit every month for work you did years ago. What was once modest beer money at $50 a month is now almost serious alcoholic money at $700 a month!&lt;/p&gt;
&lt;p&gt;How did I do it? Well, hard work for one. You must account for the fact that I have 10 years of software development experience and was able to put together the bulk of the app over a weekend. Don’t let that scare you off, these tips are not technical and apply to anything from an app to a blog.&lt;/p&gt;</summary>
    
    
    
    <category term="Entrepreneurship" scheme="https://bliskavka.com/categories/Entrepreneurship/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Entrepreneurship" scheme="https://bliskavka.com/tags/Entrepreneurship/"/>
    
    <category term="Screen Saver Gallery" scheme="https://bliskavka.com/tags/Screen-Saver-Gallery/"/>
    
    <category term="Career" scheme="https://bliskavka.com/tags/Career/"/>
    
  </entry>
  
  <entry>
    <title>AWS Angular Stack Automation</title>
    <link href="https://bliskavka.com/2018/08/16/AWS-Angular-Stack-Automation/"/>
    <id>https://bliskavka.com/2018/08/16/AWS-Angular-Stack-Automation/</id>
    <published>2018-08-16T06:56:28.000Z</published>
    <updated>2024-02-25T19:57:57.962Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Effort is not work. Carrying a piano up the stairs is the same amount of work as using an elevator, but tremendously more effort.</p></blockquote><p>Your focus should be on delivering quality code, not the intricacies of deployments. It should be as easy as:</p><ol><li>Deploy Infrastructure</li><li>Deploy Code</li><li>Repeat as necessary</li></ol><span id="more"></span><p>Until recently I worked in a SOX-compliant on-premise IIS shop. SOX regulations are there for good reason but they are a huge burden if every deploy and config change is done and documented manually. Our deployments were usually an all-hands-on-deck affair, and scheduling an automated deployment seemed like a pipe dream. It once took infrastructure <em>months</em> to enable SSL on our site! Having moved to a cloud-first shop it all just seems like so much unnecessary <em>effort</em>.</p><h2 id="Prerequisites"><a href="#Prerequisites" class="headerlink" title="Prerequisites"></a>Prerequisites</h2><ul><li>NodeJS &amp; Angular CLI</li><li>AWS CLI Installed &amp; Credentials Configured</li><li>Route53 Domain &amp; Amazon Certificate Manager cert for the domain<br>(Needed for custom domain name and HTTPS)</li><li>Optional: Clone the tutorial repository:<br><a href="https://github.com/ibliskavka/aws-angular-stack-starter">https://github.com/ibliskavka/aws-angular-stack-starter</a></li></ul><h2 id="Deploy-Infrastructure"><a href="#Deploy-Infrastructure" class="headerlink" title="Deploy Infrastructure"></a>Deploy Infrastructure</h2><p><img src="Environment-Diagram.png" alt="Simple Demo Environment"></p><p>In AWS infrastructure-as-code is done with CloudFormation templates. We will deploy the following resources with our template.</p><ul><li>S3 Bucket - for hosting our Angular App</li><li>CloudFront Distribution - CDN for our Angular App. Also handles error redirects for SPA routes and SSL</li><li>Route53 DNS Entries - Everybody loves pretty domain names</li></ul><p>The annotated stack template can be found here:<br><a href="https://github.com/ibliskavka/aws-angular-stack-starter/blob/master/stack/template.yml">https://github.com/ibliskavka/aws-angular-stack-starter/blob/master/stack/template.yml</a></p><p>Once you download the template you can install it with the following CLI command:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">aws cloudformation deploy \</span><br><span class="line">    --template-file template.yml \</span><br><span class="line">    --stack-name aws-ng-demo \</span><br><span class="line">    --parameter-overrides BaseUrl=*** AppUrl=*** AcmCertArn=***</span><br></pre></td></tr></table></figure><p><em>Note: Replace the asterisks with your own values</em></p><p>It took almost 25 minutes to build my stack. Stack completion time varies but in general CloudFront distributions take ~20 minutes to create. All the more reason to script your deploys. You can check the detailed status (or errors) in the AWS web console.</p><p>Upon completion, you will receive a message like:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Waiting for changeset to be created...</span><br><span class="line">Waiting for stack create/update to complete</span><br><span class="line">Successfully created/updated stack - aws-ng-demo</span><br></pre></td></tr></table></figure><p>Open up the web console to see your stack outputs or run the following command:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">aws cloudformation describe-stacks \</span><br><span class="line">    --stack-name aws-ng-demo \</span><br><span class="line">    --query <span class="string">&quot;Stacks[0].Outputs[?OutputKey==`DistributionId` || OutputKey==`AppBucket`]&quot;</span></span><br></pre></td></tr></table></figure><h2 id="Deploy-Code"><a href="#Deploy-Code" class="headerlink" title="Deploy Code"></a>Deploy Code</h2><p>I recommend pulling a sample angular app for this tutorial. I included one in the tutorial repository.</p><p>Add a script in package.json that looks like this:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;deploy&quot;: &quot;ng build --prod --aot</span><br><span class="line">    &amp;&amp; aws s3 sync dist/demo-app s3://&#123;AppBucket&#125;</span><br><span class="line">    &amp;&amp; aws cloudfront create-invalidation --distribution-id &#123;DistributionId&#125; --paths /&quot;</span><br></pre></td></tr></table></figure><p><em>Note: Replace {AppBucket} &amp; {DistributionId} with your stack outputs.</em></p><p>What this does</p><ol><li>Builds the angular app for production</li><li>Syncs the built app to your S3 bucket</li><li>Creates a CloudFront invalidation so that edge locations would get the latest build. (optional)</li></ol><p>Deploy your code with the following command<br><code>npm run deploy</code></p><p>That’s it! Now your app is deployed to AWS!</p><p><img src="Deployed.png" alt="Screenshot of demo-app using SSL on a Route53 domain"></p><h2 id="Cleanup"><a href="#Cleanup" class="headerlink" title="Cleanup"></a>Cleanup</h2><p>Cleaning everything up is even easier than putting it up. There is no reason to pay for environments that are not in use!</p><p>The following commands will delete your bucket contents and then delete the stack.</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">aws s3 <span class="built_in">rm</span> s3://&#123;AppBucket&#125; --recursive</span><br><span class="line">aws cloudformation delete-stack --stack-name aws-ng-demo</span><br></pre></td></tr></table></figure><p><em>Note: Replace {AppBucket} with your stack output</em></p><h2 id="Conclusion"><a href="#Conclusion" class="headerlink" title="Conclusion"></a>Conclusion</h2><p>This may seem like if overkill you only have one toy app. If you are producing a bunch of apps and have multiple environments this becomes an amazing time saver.</p><p>If the policy states that you can’t deploy your own code, your ops guys will love this! Additionally, any infrastructure changes are explicitly documented in your source control.</p>]]></content>
    
    
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;Effort is not work. Carrying a piano up the stairs is the same amount of work as using an elevator, but tremendously more effort.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Your focus should be on delivering quality code, not the intricacies of deployments. It should be as easy as:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Deploy Infrastructure&lt;/li&gt;
&lt;li&gt;Deploy Code&lt;/li&gt;
&lt;li&gt;Repeat as necessary&lt;/li&gt;
&lt;/ol&gt;</summary>
    
    
    
    <category term="DevOps" scheme="https://bliskavka.com/categories/DevOps/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="DevOps" scheme="https://bliskavka.com/tags/DevOps/"/>
    
    <category term="CloudFormation" scheme="https://bliskavka.com/tags/CloudFormation/"/>
    
    <category term="Angular" scheme="https://bliskavka.com/tags/Angular/"/>
    
  </entry>
  
  <entry>
    <title>AWS Solution Architect Cert</title>
    <link href="https://bliskavka.com/2018/02/15/AWS-Solution-Architect-Cert/"/>
    <id>https://bliskavka.com/2018/02/15/AWS-Solution-Architect-Cert/</id>
    <published>2018-02-15T22:49:36.000Z</published>
    <updated>2024-02-25T19:57:57.964Z</updated>
    
    <content type="html"><![CDATA[<p>I am now an <a href="https://www.certmetrics.com/amazon/public/badge.aspx?i=1&t=c&d=2018-02-15&ci=AWS00430147&dm=80">AWS Solution Architect Associate</a>!</p><p>My previous experience has all been in an on-premise SOX environment with burdensome manual testing, documentation &amp; deployment processes. The AWS Cloud really does streamline so many of those processes. Not only can you save a ton of money by leveraging their SaaS products, but the time ($$$) you can save on deployments and documentation is simply staggering.</p><span id="more"></span><p>Everything in AWS comes down to an API call. Logging each user action can be turned on with a few commands. Implementing a process like that in an on-premise environment can quickly take you from months to years!</p><p>On top of that, automating the deployment of both infrastructure and code is a top priority. Think about how long it would take to get a new test or QA environment set up in your on-premise data center that matches production. Think about the number of meetings it would take just to get started. The AWS cloud can do all that within a matter of hours.</p><p>Let’s talk about how moving to the cloud can save you $$$! You can reach me on <a href="https://www.linkedin.com/in/ibliskavka">LinkedIn</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;I am now an &lt;a href=&quot;https://www.certmetrics.com/amazon/public/badge.aspx?i=1&amp;t=c&amp;d=2018-02-15&amp;ci=AWS00430147&amp;dm=80&quot;&gt;AWS Solution Architect Associate&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;My previous experience has all been in an on-premise SOX environment with burdensome manual testing, documentation &amp;amp; deployment processes. The AWS Cloud really does streamline so many of those processes. Not only can you save a ton of money by leveraging their SaaS products, but the time ($$$) you can save on deployments and documentation is simply staggering.&lt;/p&gt;</summary>
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="AWS" scheme="https://bliskavka.com/tags/AWS/"/>
    
    <category term="Certification" scheme="https://bliskavka.com/tags/Certification/"/>
    
  </entry>
  
  <entry>
    <title>Resume Writing Tips</title>
    <link href="https://bliskavka.com/2017/11/08/Resume-Writing-Tips/"/>
    <id>https://bliskavka.com/2017/11/08/Resume-Writing-Tips/</id>
    <published>2017-11-08T14:45:56.000Z</published>
    <updated>2024-02-25T19:57:57.965Z</updated>
    
    <content type="html"><![CDATA[<p>My friend asked me to help her with her resume, so I decided to make a video of the process.</p><p>I offer tips on the content and also plenty of technical tips on making your life easier in Microsoft Word.</p><p><a href="https://youtu.be/m5CFmJv5rec" title="30 minute video of resume writing tips">Check out the 30-minute video on YouTube</a></p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;My friend asked me to help her with her resume, so I decided to make a video of the process.&lt;/p&gt;
&lt;p&gt;I offer tips on the content and also</summary>
        
      
    
    
    
    <category term="Career" scheme="https://bliskavka.com/categories/Career/"/>
    
    
    <category term="Career" scheme="https://bliskavka.com/tags/Career/"/>
    
    <category term="Writing" scheme="https://bliskavka.com/tags/Writing/"/>
    
  </entry>
  
  <entry>
    <title>Screen Saver Gallery</title>
    <link href="https://bliskavka.com/2016/01/03/Screen-Saver-Gallery/"/>
    <id>https://bliskavka.com/2016/01/03/Screen-Saver-Gallery/</id>
    <published>2016-01-03T15:12:06.000Z</published>
    <updated>2024-02-25T19:57:57.965Z</updated>
    
    <content type="html"><![CDATA[<p>I created a screen-saver app for the Universal Windows Platform. You can find it in the <a href="https://www.microsoft.com/en-us/store/apps/screen-saver-gallery/9nblggh5j8tx" title="Screen Saver Gallery Download Link">Windows Store</a></p><p>It is a slideshow that sources high-quality images from the imgur.com API. It’s not quite a screen saver because it does not auto-start - the option is not available for Windows Store Apps</p><p>July 2018 Update: <strong>6,000 Downloads a month!</strong></p>]]></content>
    
    
      
      
        
        
    <summary type="html">&lt;p&gt;I created a screen-saver app for the Universal Windows Platform. You can find it in the &lt;a</summary>
        
      
    
    
    
    <category term="Entrepreneurship" scheme="https://bliskavka.com/categories/Entrepreneurship/"/>
    
    
    <category term="Screen Saver Gallery" scheme="https://bliskavka.com/tags/Screen-Saver-Gallery/"/>
    
  </entry>
  
</feed>
