<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.3">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2023-07-10T15:02:07+00:00</updated><id>/feed.xml</id><title type="html">cookies and containers</title><subtitle>Or how I learned to love the cloud.</subtitle><entry><title type="html">Google Foobar Challenge</title><link href="/2023/03/12/google-foobar.html" rel="alternate" type="text/html" title="Google Foobar Challenge" /><published>2023-03-12T00:00:00+00:00</published><updated>2023-03-12T00:00:00+00:00</updated><id>/2023/03/12/google-foobar</id><content type="html" xml:base="/2023/03/12/google-foobar.html">&lt;p&gt;A few weeks ago I was googling random things (more specifically how to rename a remote branch as my naming had impacted a colleagues’ local environment), when I got a pop-up.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/foobar1.png&quot; alt=&quot;Google search with secret pop-up&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A few years ago Google created this secret challenge as a hiring tool, I just didn’t know it was still around! The URL is &lt;a href=&quot;https://foobar.withgoogle.com/&quot;&gt;https://foobar.withgoogle.com/&lt;/a&gt;, but without an invite you can’t access the content. It no longer seems to be used for hiring, but it’s still a fun challenge, especially if you enjoy sites like &lt;a href=&quot;https://adventofcodecom/&quot;&gt;adventofcode&lt;/a&gt; or &lt;a href=&quot;https://projecteuler.net/&quot;&gt;Project Euler&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;how-does-it-work&quot;&gt;How does it work?&lt;/h2&gt;

&lt;p&gt;After getting an invite pop-up (or a code from a friend), you get the option to request a challenge. There are five levels to complete and each level has a number of challenges.&lt;/p&gt;

&lt;p&gt;Your overall goal is to defeat the evil Commander Lambda who is trying to destroy Bunny Planet. Each challenge helps you infiltrate the organization the Commander leads and rise in the ranks until you’ve defeated a doomsday device and saved all the bunnies. Nobody said the backstories for code challenges need to make sense.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/foobar2.png&quot; alt=&quot;foobar help&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-setup&quot;&gt;The setup&lt;/h2&gt;

&lt;p&gt;Each level has up to three challenges, each of which comes with a time limit. There’s plenty of time for each challenge. Most of the ones I had allowed for multiple days to solve it.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/foobar5.png&quot; alt=&quot;foobar levels&quot; /&gt;
Once you request a challenge you get an extra folder with a text file that introduces the challenge problem.&lt;/p&gt;

&lt;p&gt;You’re restricted to using Python or Java. There are two stubs for each challenge, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Solution.java&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;solution.py&lt;/code&gt;, which you can edit, run against a number of test cases and finally submit.&lt;/p&gt;

&lt;h2 id=&quot;the-challenges&quot;&gt;The challenges&lt;/h2&gt;

&lt;p&gt;Even though the challenges work based on some backstory in the overall “help destroy the evil corp”, there are underlying algorithmic concepts.&lt;/p&gt;

&lt;p&gt;Knowing what these are and how they work is very helpful for solving the challenges. Some I came across needed dynamic programming, graphs (shortest path, negative cycles, &lt;a href=&quot;https://en.wikipedia.org/wiki/Depth-first_search&quot;&gt;DFS&lt;/a&gt;), &lt;a href=&quot;https://en.wikipedia.org/wiki/Cellular_automaton&quot;&gt;cellular automata&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Sometime during the process Foobar does ask for your details, but you don’t have to submit them, or can choose to do so later.
&lt;img src=&quot;/assets/images/foobar3.png&quot; alt=&quot;foobar recruit&quot; /&gt;
Keep in mind that (according to other blogs) the challenge hasn’t been used for hiring since 2020.&lt;/p&gt;

&lt;h2 id=&quot;success&quot;&gt;Success!&lt;/h2&gt;

&lt;p&gt;Finishing all challenges gives you an encrypted message.
&lt;img src=&quot;/assets/images/foobar4.png&quot; alt=&quot;foobar finished&quot; /&gt;
Decrypting that returns&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;b&quot;{'success' : 'great', 'colleague' : 'esteemed', 'efforts' : 'incredible', 'achievement' : 'unlocked', 'rabbits' : 'safe', 'foo' : 'win!'}&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s definitely a fun one, and I learned a few new concepts in the process!&lt;/p&gt;

&lt;p&gt;Unfortunately it’s quite easy to come across people posting solutions when researching the topics, so if you don’t want to see any solutions during your first attempt keep an eye out for spoilers on StackOverflow.&lt;/p&gt;</content><author><name></name></author><category term="code" /><category term="challenge" /><category term="google" /><summary type="html">A few weeks ago I was googling random things (more specifically how to rename a remote branch as my naming had impacted a colleagues’ local environment), when I got a pop-up.</summary></entry><entry><title type="html">Useful kubectl commands</title><link href="/2023/01/14/useful-kubectl.html" rel="alternate" type="text/html" title="Useful kubectl commands" /><published>2023-01-14T00:00:00+00:00</published><updated>2023-01-14T00:00:00+00:00</updated><id>/2023/01/14/useful-kubectl</id><content type="html" xml:base="/2023/01/14/useful-kubectl.html">&lt;p&gt;Sometimes I find myself going through old messages, command line history or slack search to find that one random command for debugging or fixing something. This is the very random collection of commands like that, as well as some that I just think are neat and should be better known.&lt;/p&gt;

&lt;h2 id=&quot;finding-all-resources-in-a-namespace&quot;&gt;Finding all resources in a namespace&lt;/h2&gt;

&lt;p&gt;Namespace stuck in terminating? Sometimes not all the resources get cleaned up properly when deleting a namespace and you might end up with namespace that just won’t go away.
This command get EVERYTHING in that namespace (contrary to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kubectl get all&lt;/code&gt; which is limited to a subset of Kubernetes resources).&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;NAMESPACE=default # &amp;lt;- stuck namespace
kubectl api-resources --verbs=list --namespaced -o name  | \
  xargs -n 1 kubectl get --show-kind --ignore-not-found -n $NAMESPACE -oname | \
  xargs -n 1 kubectl delete -n $NAMESPACE
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You can also add a dry-run flag to the delete or leave it out to just see the resources that are still there.&lt;/p&gt;

&lt;h2 id=&quot;delete-resources-stuck-in-terminating&quot;&gt;Delete resources stuck in terminating&lt;/h2&gt;

&lt;p&gt;Sometimes other resources can get stuck in terminating too (especially when you have custom controllers that might be offline or not functioning as expected), when
something attaches a finalizers to a resource but doesn’t finish cleanup.&lt;/p&gt;

&lt;p&gt;Patching the object by removing the finalizer will delete it.
NOTE: This will not finish any steps that were supposed to be executed by whatever placed the finalizer, so run this only when you know what you’re doing.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl get &amp;lt;resource e.g. pods&amp;gt; -l &amp;lt;common label&amp;gt; -n &amp;lt;ns&amp;gt; -oname | \
  xargs kubectl patch  --type json --patch='[ { &quot;op&quot;: &quot;remove&quot;, &quot;path&quot;: &quot;/metadata/finalizers&quot; } ]' -n &amp;lt;ns&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;get-old-logs&quot;&gt;Get old logs&lt;/h2&gt;

&lt;p&gt;Sometimes a container goes away too quickly to capture its logs, luckily you can get the previous container logs from a pod that has restarted as long as it is still around.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl logs &amp;lt;podname&amp;gt; --previous
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://jamesdefabia.github.io/docs/user-guide/kubectl/kubectl_logs/&quot;&gt;K8s docs&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;ephemeral-debug-pods&quot;&gt;Ephemeral debug pods&lt;/h2&gt;

&lt;p&gt;I was so glad when this feature was introduced. No more random debug pods, no more changing your minimal image to use debugging tools in dev.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl debug -it &amp;lt;pod&amp;gt; --image=busybox:1.28 --target=&amp;lt;pod&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href=&quot;https://kubernetes.io/docs/tasks/debug/debug-application/debug-running-pod/#ephemeral-container&quot;&gt;K8s docs&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;create-a-kubernetes-event-manually&quot;&gt;Create a Kubernetes event manually&lt;/h2&gt;

&lt;p&gt;Either by applying the manifest&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f - &amp;lt;&amp;lt;EOF
apiVersion: v1
kind: Event
metadata:
  name: myevent
  namespace: default
type: Normal
message: 'some message'
involvedObject:
  kind: someObject
EOF
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;or by passing the JSON:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jq --arg now &quot;$(date -u +&quot;%Y-%m-%dT%H:%M:%SZ&quot;)&quot; -n '{apiVersion: &quot;v1&quot;, kind: &quot;Event&quot;, metadata: { namespace: &quot;default&quot;, generateName: &quot;event-&quot; }, involvedObject: {apiVersion: &quot;v1&quot;, kind: &quot;Pod&quot;, name: &quot;pod&quot;, namespace: &quot;default&quot;, uid: &quot;6d496a1a-bbc5-a5bb-12a3-133eq7ca0383&quot;, fieldPath: &quot;spec.containers{container}&quot;}, message: &quot;Hello world!&quot;, firstTimestamp: $now, lastTimestamp: $now, type: &quot;Normal&quot;, reason: &quot;JustBecause&quot;, source: {component: &quot;event-client&quot;} }' | kubectl create -f -
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;which creates an event that can then be seen or used to for example trigger an operator’s control loop manually.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ kubectl get event
LAST SEEN   TYPE     REASON         OBJECT                            MESSAGE
4s          Normal   JustBecause    pod/pod                           Hello world!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name></name></author><category term="kubernetes" /><category term="hacks" /><summary type="html">Sometimes I find myself going through old messages, command line history or slack search to find that one random command for debugging or fixing something. This is the very random collection of commands like that, as well as some that I just think are neat and should be better known.</summary></entry><entry><title type="html">Testing: Mocking the Kubernetes API in Go</title><link href="/2022/09/14/mocking-kubernetes.html" rel="alternate" type="text/html" title="Testing: Mocking the Kubernetes API in Go" /><published>2022-09-14T00:00:00+00:00</published><updated>2022-09-14T00:00:00+00:00</updated><id>/2022/09/14/mocking-kubernetes</id><content type="html" xml:base="/2022/09/14/mocking-kubernetes.html">&lt;p&gt;Whether you’re writing an operator just interacting with the Kubernetes API for other reasons, you’ll want to write unit tests for your code. Luckily the Kubernetes Go-client comes with it’s &lt;a href=&quot;https://pkg.go.dev/k8s.io/client-go/kubernetes/fake&quot;&gt;own mocking library&lt;/a&gt; to fake any reaction your API server might have.&lt;/p&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting started&lt;/h2&gt;

&lt;p&gt;For a simple mock like creating a deployment we can start by creating a fake ClientSet and swapping out the dependency.&lt;/p&gt;

&lt;p&gt;For a function that creates a deployment, for example&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func createDeployment(ctx context.Context, client kubernetes.Interface, name string, namespace string) error { ... }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;we can create a simple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main_test.go&lt;/code&gt; (or whatever your package is called) that looks like this:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;package main

import (
    &quot;context&quot;
    &quot;testing&quot;
    &quot;time&quot;

    &quot;github.com/stretchr/testify/require&quot;
    appsv1 &quot;k8s.io/api/apps/v1&quot;
    corev1 &quot;k8s.io/api/core/v1&quot;
    metav1 &quot;k8s.io/apimachinery/pkg/apis/meta/v1&quot;
    &quot;k8s.io/client-go/kubernetes/fake&quot;
)

func TestCreateDeployment(t *testing.T) {
    tests := []struct {
        name string
        err  error

        client *fake.Clientset
    }{
        {
            name:   &quot;success - created deployment&quot;,
            err:    nil,
            client: fake.NewSimpleClientset(),
        },
    }
    for _, test := range tests {
        t.Run(test.name, func(t *testing.T) {
            ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
            defer cancel()

            err := createDeployment(ctx, test.client, &quot;test&quot;, &quot;default&quot;)
            if test.err == nil {
                require.NoError(t, err)
            } else {
                require.EqualError(t, err, test.err.Error())
            }
        })
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We’ve replaced the client with the fake client, so now calling the CreateDeployment function has a simple happy path. But what if we want to ensure our code works as intended if the deployment doesn’t get created?&lt;/p&gt;

&lt;h2 id=&quot;adding-a-failure&quot;&gt;Adding a Failure&lt;/h2&gt;

&lt;p&gt;We can add a test case for an error:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        {
            name:   &quot;failure - creation errors&quot;,
            err:    fmt.Errof(&quot;error&quot;),
            client: fake.NewSimpleClientset(),
        },
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;but that doesn’t actually tell the fake client to return anything else than before. What we need is something called a reactor, which (as the name implies) can add reactions to requests that are sent to the fake API server.&lt;/p&gt;

&lt;p&gt;So we add a field to our tests struct, for adding a reactor function:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    reactor func(t *testing.T, client *fake.Clientset) func(action k8sTesting.Action) (bool, runtime.Object, error)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and change the failure case to:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;        {
            name:    &quot;failure - creation errors&quot;,
            err:     fmt.Errorf(&quot;fake error&quot;),
            client:  fake.NewSimpleClientset(),
            reactor: createDeploymentFail,
        },
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Now we can create a reaction function:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;func createDeploymentFail(t *testing.T, client *fake.Clientset) func(action k8sTesting.Action) (bool, runtime.Object, error) {
    return func(action k8sTesting.Action) (bool, runtime.Object, error) {
        return true, nil, fmt.Errorf(&quot;fake error&quot;)
    }
}

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and don’t forget to add the reactor to the client in the test run:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;test.client.PrependReactor(&quot;create&quot;, &quot;deployments&quot;, test.reactor(t, test.client))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This lets us control the return values of any request, but it can easily make you want to add functions for all the “common” behaviour you’d expect from the Kubernetes API. Like creating a deployment would mean there’s now a replicaset and pods and they’re up and running, right?&lt;/p&gt;

&lt;p&gt;But since the point of this is not to emulate or rebuild Kubernetes functionality it comes down to figuring out what resources your own code is relying on and is reacting to. It’s also interesting to see if the reaction to your cluster misbehaving are as expected.&lt;/p&gt;

&lt;p&gt;You can also add resources to the fake client immediately during initialization, e.g.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;client: fake.NewSimpleClientset(
                &amp;amp;appsv1.Deployment{
                    ObjectMeta: metav1.ObjectMeta{
                        Name:      &quot;test&quot;,
                        Namespace: &quot;default&quot;,
                        Labels: map[string]string{
                            &quot;app&quot;: &quot;test&quot;,
                        },
                    },
                    Spec: appsv1.DeploymentSpec{
                        Replicas: Pointer(int32(2)),
                        Selector: &amp;amp;metav1.LabelSelector{
                            MatchLabels: map[string]string{
                                &quot;app&quot;: &quot;test&quot;,
                            },
                        },
                        Template: corev1.PodTemplateSpec{
                            ObjectMeta: metav1.ObjectMeta{
                                Labels: map[string]string{
                                    &quot;app&quot;: &quot;test&quot;,
                                },
                            },
                        },
                    },
                },
            ),
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This can make it harder too read after adding a few resources, so focussing on only the absolute minimal examples or even reading a manifest file into an object might be preferable.&lt;/p&gt;

&lt;p&gt;Creating a deployment is just a very simple example of what can be tested using the mocking feature of the go-client library, but it already requires some knowledge of Kubernetes internals like which resources exist and what they look like, what actions can and need to be mocked. Some simple tests for success and error responses are a good way to get started and see that unit testing your Go code that touches Kubernetes is not such a complicated task after all.&lt;/p&gt;</content><author><name></name></author><category term="kubernetes" /><category term="testing" /><category term="golang" /><summary type="html">Whether you’re writing an operator just interacting with the Kubernetes API for other reasons, you’ll want to write unit tests for your code. Luckily the Kubernetes Go-client comes with it’s own mocking library to fake any reaction your API server might have.</summary></entry><entry><title type="html">Trying out Scroll</title><link href="/2022/09/03/scroll.html" rel="alternate" type="text/html" title="Trying out Scroll" /><published>2022-09-03T00:00:00+00:00</published><updated>2022-09-03T00:00:00+00:00</updated><id>/2022/09/03/scroll</id><content type="html" xml:base="/2022/09/03/scroll.html">&lt;p&gt;After running this blog with Jekyll and github-pages for over two years, I ran into &lt;a href=&quot;https://scroll.pub/&quot;&gt;Scroll&lt;/a&gt; and fell in love with the newspaper-y style and minimalist setup, so I wanted to try it out.&lt;/p&gt;

&lt;h1 id=&quot;getting-started&quot;&gt;Getting started&lt;/h1&gt;
&lt;p&gt;Scroll is a easy to install as&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm install -g scroll-cli
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/breck7/scroll
cd scroll
npm install -g .
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and then running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scroll init&lt;/code&gt; inside the blog directory. After that I converted all posts to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.scroll&lt;/code&gt; files and pushed them to a separate &lt;a href=&quot;https://github.com/charlottemach/charlottemach.github.io/tree/scroll&quot;&gt;branch&lt;/a&gt;.&lt;/p&gt;

&lt;h1 id=&quot;changes&quot;&gt;Changes&lt;/h1&gt;

&lt;p&gt;The structure changes a bit, with all files in a flat structure (aside from images, those can stay in a subfolder).
For the previous markdown in my posts I just added the &lt;a href=&quot;https://github.com/breck7/scroll/blob/main/public/blog/markups.scroll&quot;&gt;markups.scroll&lt;/a&gt; to avoid too much rewriting.
There are a few features that would go away with the switch, namely analytics and tagged pages. Though tagging seems to be replaceable with categories and I might have found a way to add the javascript to each page.&lt;/p&gt;

&lt;h1 id=&quot;running-locally&quot;&gt;Running locally&lt;/h1&gt;
&lt;p&gt;For local rendering, it’s now &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scroll watch&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll -s&lt;/code&gt;, though you can also run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;scroll build&lt;/code&gt; and open the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.html&lt;/code&gt; files in your browser directly.&lt;/p&gt;

&lt;h1 id=&quot;github-pages&quot;&gt;Github Pages&lt;/h1&gt;

&lt;h2 id=&quot;comparison&quot;&gt;Comparison&lt;/h2&gt;

&lt;p&gt;Comparing the Jekyll design 
&lt;img src=&quot;/assets/images/screenshot_old.png&quot; alt=&quot;Jekyll Design&quot; title=&quot;Website designed in Jekyll&quot; /&gt;
to Scroll
&lt;img src=&quot;/assets/images/screenshot_new.png&quot; alt=&quot;Jekyll Design&quot; title=&quot;Website designed in Scroll&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It gives the site a different feel, but while I do really like the look of it I’m sticking with the Jekyll version for now, mostly for the Markdown and out of the box analytics support.&lt;/p&gt;</content><author><name></name></author><category term="scroll" /><category term="tools" /><category term="blog" /><summary type="html">After running this blog with Jekyll and github-pages for over two years, I ran into Scroll and fell in love with the newspaper-y style and minimalist setup, so I wanted to try it out.</summary></entry><entry><title type="html">Tracing with Jaeger on EKS with Istio</title><link href="/2022/04/13/jaeger-eks.html" rel="alternate" type="text/html" title="Tracing with Jaeger on EKS with Istio" /><published>2022-04-13T00:00:00+00:00</published><updated>2022-04-13T00:00:00+00:00</updated><id>/2022/04/13/jaeger-eks</id><content type="html" xml:base="/2022/04/13/jaeger-eks.html">&lt;p&gt;Understanding and debugging microservices communication can be a challenge in itself. Luckily
there are a bunch of tools to make it easier. In addition to the observability you get from 
running visualization tooling like Kiali with your service mesh, tracing provides valuable
information for troubleshooting. That’s where &lt;a href=&quot;https://github.com/jaegertracing/jaeger&quot;&gt;Jaeger&lt;/a&gt; comes in.&lt;/p&gt;

&lt;h2 id=&quot;installing-jaeger&quot;&gt;Installing Jaeger&lt;/h2&gt;

&lt;p&gt;Jaeger provides an &lt;a href=&quot;https://github.com/jaegertracing/jaeger-operator#getting-started&quot;&gt;Operator&lt;/a&gt;, and a Helm
chart to get started. Using the Helm chart, installation is simple:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ helm repo add jaegertracing https://jaegertracing.github.io/helm-charts
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;to use custom settings add your own &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;values.yaml&lt;/code&gt; file (e.g. an
existing ElasticSearch cluster as a datastore in this case)&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;storage:
  type: elasticsearch
  elasticsearch:
    host: elasticsearch.elk.svc.cluster.local  # using a local elasticsearch
    port: 9200
    # user: &amp;lt;USER&amp;gt;
    # password: &amp;lt;PASSWORD&amp;gt;
provisionDataStore:
  cassandra: false                             # disabling the default cassandra
  elasticsearch: false                         # disabling creating a ES cluster
collector:
  service:
    zipkin:                                    
      port: 9411                               # opening the port for istio to push
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Install jaeger with&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ helm install jaeger jaegertracing/jaeger --values values.yaml --namespace istio-system
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Resulting in something like this&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;NAME: jaeger
LAST DEPLOYED: Mon Apr 11 18:36:34 2022
NAMESPACE: istio-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
###################################################################
### IMPORTANT: Ensure that storage is explicitly configured     ###
### Default storage options are subject to change.              ###
###                                                             ###
### IMPORTANT: The use of &amp;lt;component&amp;gt;.env: {...} is deprecated. ###
### Please use &amp;lt;component&amp;gt;.extraEnv: [] instead.                ###
###################################################################

You can log into the Jaeger Query UI here:

  export POD_NAME=$(kubectl get pods --namespace istio-system -l &quot;app.kubernetes.io/instance=jaeger,app.kubernetes.io/component=query&quot; -o jsonpath=&quot;{.items[0].metadata.name}&quot;)
  echo http://127.0.0.1:8080/
  kubectl port-forward --namespace istio-system $POD_NAME 8080:16686
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;giving you the commands you need to locally check out the UI.&lt;/p&gt;

&lt;h3 id=&quot;adapting-istio&quot;&gt;Adapting Istio&lt;/h3&gt;

&lt;p&gt;To see actual traces, you’ll need to edit Istio to forward data to Jaeger.
Edit the istio ConfigMap in istio-system to point tracer.zipkin.address to&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jaeger-collector.istio-system.svc.cluster.local:9411
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;You need to restart the istiod pod for the change to take effect.&lt;/p&gt;

&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;

&lt;p&gt;The traffic data goes from each istio-proxy sidecar to agents (running as DaemonSets on each host), to 
the collector, which validates, indexes and transforms the traces and either stores them or forwards them to Kafka.
The default setup “samples” 1% of transactions/traces.&lt;/p&gt;

&lt;p&gt;The github page has a great &lt;a href=&quot;https://github.com/jaegertracing/documentation/blob/7f1f07f182302cde97ee33db4f48958b831e7dda/static/img/architecture-v1.png&quot;&gt;architecture diagram here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;usage&quot;&gt;Usage&lt;/h3&gt;

&lt;p&gt;After sending a few hundred requests to your application (or &lt;a href=&quot;https://istio.io/latest/docs/examples/bookinfo/&quot;&gt;bookinfo&lt;/a&gt; as a standin),
you can see the traces in the UI.
&lt;img src=&quot;/assets/images/jaeger1.png&quot; alt=&quot;Traces screenshot&quot; title=&quot;Traces&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The System Architecture view won’t show anything, unless you’re using the all-in-one setup.
For seeing the entire distributed architecture you can use Kiali or add &lt;a href=&quot;https://github.com/jaegertracing/jaeger-analytics&quot;&gt;Jaeger analytics&lt;/a&gt; to fill in the blank page.&lt;/p&gt;</content><author><name></name></author><category term="kubernetes" /><category term="istio" /><category term="jaeger" /><category term="tracing" /><category term="aws" /><summary type="html">Understanding and debugging microservices communication can be a challenge in itself. Luckily there are a bunch of tools to make it easier. In addition to the observability you get from running visualization tooling like Kiali with your service mesh, tracing provides valuable information for troubleshooting. That’s where Jaeger comes in.</summary></entry><entry><title type="html">Getting started with Chaos Engineering using Litmus</title><link href="/2022/01/06/getting-started-litmus.html" rel="alternate" type="text/html" title="Getting started with Chaos Engineering using Litmus" /><published>2022-01-06T00:00:00+00:00</published><updated>2022-01-06T00:00:00+00:00</updated><id>/2022/01/06/getting-started-litmus</id><content type="html" xml:base="/2022/01/06/getting-started-litmus.html">&lt;p&gt;The idea of using Chaos Engineering to improve systems is becoming more and more popular with the Kubernetes crowd, no doubt due to &lt;a href=&quot;https://github.com/Netflix/SimianArmy&quot;&gt;Netflix’s Simian Army&lt;/a&gt; and the experiences that followed.
Simulating outages or different errors to test resilience, understanding your system better and battle-test your applications sounds like a good idea, here’s one way of getting started.&lt;/p&gt;

&lt;h2 id=&quot;understand-the-basics&quot;&gt;Understand the basics&lt;/h2&gt;

&lt;p&gt;Chaos Engineering is NOT about breaking things randomly and seeing what happens. It’s about making an assumption about your system, trying to understand what would happen if a certain error or outage happens and testing that assumption in a controlled environment. For real-life systems that involves a large amount of preparation, communication and learning from whatever results a chaos experiment may have.&lt;/p&gt;

&lt;p&gt;If you’re just getting started and want to play around and learn, &lt;a href=&quot;https://docs.litmuschaos.io/&quot;&gt;Litmus&lt;/a&gt; is a good tool to try on Kubernetes.&lt;/p&gt;

&lt;h2 id=&quot;install&quot;&gt;Install&lt;/h2&gt;

&lt;p&gt;Litmus describes itself as an end-to-end chaos engineering platform for cloud native infra and applications. It’s control plane comes as a Kubernetes deployment consisting of a frontend, a server and a MongoDB backend, all together called the Chaos Center.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;kubectl apply -f https://litmuschaos.github.io/litmus/2.4.0/litmus-2.4.0.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The above command is all it takes to install it on your cluster (see the &lt;a href=&quot;https://docs.litmuschaos.io/docs/getting-started/installation/&quot;&gt;docs&lt;/a&gt; for the latest version).&lt;/p&gt;

&lt;p&gt;Once the pods are up and running, you can access the frontend via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;IP&amp;gt;:&amp;lt;Nodeport&amp;gt;&lt;/code&gt; which you can see in the service that gets created (in my case it’s on a local cluster, so localhost:30711).&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;NAMESPACE     NAME                            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                       AGE
litmus        litmusportal-frontend-service   NodePort    10.98.60.168     &amp;lt;none&amp;gt;        9091:30711/TCP                                                66s
litmus        litmusportal-server-service     NodePort    10.109.185.162   &amp;lt;none&amp;gt;        9002:32132/TCP,8000:30885/TCP,9003:31270/TCP,3030:31009/TCP   65s
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After logging in with the default credentials of user: admin and pw: litmus, you’re prompted to change the password.
In the Chaos Center you can create workflows, add different hubs (like experiment libraries), agents  or monitor experiment runs and results.&lt;/p&gt;

&lt;p&gt;(The 9002 port on the server lets you access the internal &lt;a href=&quot;https://github.com/graphql/graphql-playground&quot;&gt;GraphQL playground&lt;/a&gt;.)&lt;/p&gt;

&lt;h2 id=&quot;concepts&quot;&gt;Concepts&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://dev.to/litmus-chaos/how-litmus-orchestrates-chaos-3nnd&quot;&gt;This post&lt;/a&gt; goes into more detail, but basically Litmus runs as an &lt;a href=&quot;https://kubernetes.io/docs/concepts/extend-kubernetes/operator/&quot;&gt;operator&lt;/a&gt;, using different CustomResources to define states and custom controllers to create Chaos Runners which execute the experiments as jobs.&lt;/p&gt;

&lt;h5 id=&quot;crs&quot;&gt;CRs&lt;/h5&gt;

&lt;ul&gt;
  &lt;li&gt;Chaos Experiment: the actual test you want to run&lt;/li&gt;
  &lt;li&gt;Chaos Engine: the mapping of the test to the thing you want to test (infra/app)&lt;/li&gt;
  &lt;li&gt;Chaos Result: giving you the current state and outcome of a test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A Chaos Workflow can be used to add additional steps around an experiment, for example to add preparation, cleanup or just to execute multiple operations.&lt;/p&gt;

&lt;h2 id=&quot;running-a-test&quot;&gt;Running a test&lt;/h2&gt;

&lt;p&gt;With Litmus up and running you now have different options&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.litmuschaos.io/docs/getting-started/run-your-first-workflow&quot;&gt;Run a sample workflow using the UI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://hub.litmuschaos.io/&quot;&gt;Run a workflow from the ChaosHub&lt;/a&gt;: The Hub contains different generic experiments like introducing network latency or node restarts, as well as application-specific experiments for a some cloud providers, Kafka, Cassandra.&lt;/li&gt;
  &lt;li&gt;Write your own (using &lt;a href=&quot;https://github.com/litmuschaos/litmus-python&quot;&gt;Python&lt;/a&gt; or &lt;a href=&quot;https://github.com/litmuschaos/litmus-go&quot;&gt;Golang&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more resources, check out &lt;a href=&quot;https://github.com/dastergon/awesome-chaos-engineering&quot;&gt;awesome chaos engineering&lt;/a&gt; on GitHub.&lt;/p&gt;</content><author><name></name></author><category term="litmus" /><category term="chaos" /><category term="kubernetes" /><summary type="html">The idea of using Chaos Engineering to improve systems is becoming more and more popular with the Kubernetes crowd, no doubt due to Netflix’s Simian Army and the experiences that followed. Simulating outages or different errors to test resilience, understanding your system better and battle-test your applications sounds like a good idea, here’s one way of getting started.</summary></entry><entry><title type="html">Using AWS Lambda to write from S3 to DynamoDB</title><link href="/2021/12/16/lambda-s3-dynamo.html" rel="alternate" type="text/html" title="Using AWS Lambda to write from S3 to DynamoDB" /><published>2021-12-16T00:00:00+00:00</published><updated>2021-12-16T00:00:00+00:00</updated><id>/2021/12/16/lambda-s3-dynamo</id><content type="html" xml:base="/2021/12/16/lambda-s3-dynamo.html">&lt;p&gt;Creating a lambda function triggered by an S3 event can be done in different ways. Using a blueprint, the console or AWS CDK.&lt;/p&gt;

&lt;p&gt;We’ll take a look at creating a Python Lambda function created from Typescript CDK code.&lt;/p&gt;

&lt;h3 id=&quot;cdk-resources&quot;&gt;CDK Resources&lt;/h3&gt;

&lt;p&gt;We create a separate CDK Stack for an S3 bucket&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bucket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;s3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;S3Bucket&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;bucketName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;somebucketnamethatsnottaken&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;autoDeleteObjects&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;removalPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cdk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;RemovalPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DESTROY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;encryption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;s3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;BucketEncryption&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;S3_MANAGED&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grantRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AccountRootPrincipal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;a DynamoDB table&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dynamoTable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;DynamoDb&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;partitionKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;AttributeType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;STRING&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;tableName&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;sometable&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;removalPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cdk&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;RemovalPolicy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;DESTROY&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and finally a Lambda function.&lt;/p&gt;
&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lambdaFunction&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;Function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Lambda&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Runtime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;PYTHON_3_9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lambda&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;AssetCode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;./lambda&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;lambda_function.lambda_handler&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;vpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;vpc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// this grants the Lambda function rights to read from the bucket&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grantRead&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lambdaFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// this grants the Lambda function right to write to DynamoDB&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;dynamoTable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;grantReadWriteData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lambdaFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// this adds a trigger for any time an object is put into the bucket&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;s3PutEventSource&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lambdaEventSources&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;S3EventSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;events&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;s3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;EventType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;OBJECT_CREATED_PUT&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;lambdaFunction&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;addEventSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s3PutEventSource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The typescript files will usually be in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib&lt;/code&gt; folder (easy to get starting using the &lt;a href=&quot;https://docs.aws.amazon.com/cdk/latest/guide/getting_started.html#getting_started_bootstrap&quot;&gt;AWS docs&lt;/a&gt;) and are added to the app in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin&lt;/code&gt;. The above Lambda function code is expected to be found in a file &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lambda/lambda_function.py&lt;/code&gt; in the same directory as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bin&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lib&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&quot;python-code&quot;&gt;Python code&lt;/h3&gt;

&lt;p&gt;This function consists of two simple pieces, reading the CSV file from S3 and writing its contents to the DynamoDB table.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Imports CSV from S3 to DynamoDB&quot;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;uuid&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;boto3&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'sometable'&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;s3&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boto3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'s3'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;db_table&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;boto3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;resource&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'dynamodb'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TABLE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;write_to_dynamodb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Assigns random id to csv line and writes to table&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;dict&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uuid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;uuid4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;db_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;put_item&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Item&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;items&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;lambda_handler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Gets CSV and splits into lines&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Records'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'s3'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'bucket'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'name'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Records'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'s3'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'object'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;][&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'key'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tmp_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'/tmp/'&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;s3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;meta&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;download_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bucket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tmp_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;with&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tmp_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'r'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;header&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rstrip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;';'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\n&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;rstrip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;';'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;write_to_dynamodb&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;header&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;arr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'statusCode'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;deployment&quot;&gt;Deployment&lt;/h3&gt;

&lt;p&gt;Now all you have to do is create your stack using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cdk deploy&lt;/code&gt; and testing the function by adding a CCSV file to your S3 bucket.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-csv&quot;&gt;User; Id; First Name; Age
spidey;123;Peter;22
mi6;007;James;60
McTestface;7;testy;4
&lt;/code&gt;&lt;/pre&gt;</content><author><name></name></author><category term="python" /><category term="lambda" /><category term="s3" /><category term="dynamodb" /><category term="cdk" /><summary type="html">Creating a lambda function triggered by an S3 event can be done in different ways. Using a blueprint, the console or AWS CDK.</summary></entry><entry><title type="html">Containerizing a simple Flask App</title><link href="/2021/11/29/simple-flask-docker-app.html" rel="alternate" type="text/html" title="Containerizing a simple Flask App" /><published>2021-11-29T00:00:00+00:00</published><updated>2021-11-29T00:00:00+00:00</updated><id>/2021/11/29/simple-flask-docker-app</id><content type="html" xml:base="/2021/11/29/simple-flask-docker-app.html">&lt;p&gt;It never hurts to have a basic example for a quick WebApp to deploy onto a container service. Here’s one for a Python app, using
Flask, Docker and Gunicorn.&lt;/p&gt;

&lt;h3 id=&quot;folder-structure&quot;&gt;Folder structure&lt;/h3&gt;

&lt;p&gt;To get started we create the following structure (it’s ok to leave the files empty, we’ll fill them during the next few instructions):&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;.
├── Dockerfile                     # Instruction for building the docker image
├── app
│   ├── app.py                     # Actual python source
│   ├── templates                   
│   │   └── factorial-form.html    # Template for HTML used in the WebApp
│   └── tests
│       ├── __init__.py
│       └── test_app.py            # Simple tests for the webapp
└── requirements.txt               # Required packages
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After this we can get started with the Python code.&lt;/p&gt;

&lt;h3 id=&quot;writing-the-flask-app&quot;&gt;Writing the Flask app&lt;/h3&gt;

&lt;p&gt;If you’ve never used Flask before, checkout the &lt;a href=&quot;https://flask.palletsprojects.com/en/2.0.x/&quot;&gt;docs&lt;/a&gt;. It’s a micro web framework, making it super easy to build a WebApp.&lt;/p&gt;

&lt;p&gt;Our &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt; will look like this:&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Main flask module.&quot;&quot;&quot;&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;math&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;flask&lt;/span&gt; &lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Flask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Flask&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;factorial_form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Renders main input page.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;render_template&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'factorial-form.html'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;route&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;methods&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'POST'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;form_post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;Calculates and returns factorial of input.&quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;inp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;request&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Factorial'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;fac&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;math&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;factorial&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;f&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The factorial of &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;inp&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; is &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fac&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;.&quot;&lt;/span&gt;


&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;__name__&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'__main__'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The first function creates the main page from the template file&lt;/p&gt;
&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;html&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;lang=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;en&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTF-8&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;title&amp;gt;&lt;/span&gt;FactorialApp&lt;span class=&quot;nt&quot;&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Calculate factorials&lt;span class=&quot;nt&quot;&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;Enter a number:&lt;span class=&quot;nt&quot;&gt;&amp;lt;h4&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;form&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;number&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Factorial&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;min=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;0&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;required&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;input&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;submit&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Calculate&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;which is basically just a form taking a single number as an input.
The second function of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;app.py&lt;/code&gt; receives the input from the POST request, calculates the &lt;a href=&quot;https://en.wikipedia.org/wiki/Factorial&quot;&gt;factorial&lt;/a&gt;, the product of
all positive integers less than or equal to the input. E.g. the factorial of 5 would be 5&lt;em&gt;4&lt;/em&gt;3&lt;em&gt;2&lt;/em&gt;1=120.
The calculated number is then returned as part of a string, to create the output page.&lt;/p&gt;

&lt;p&gt;To test this code you can simply run&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;flask run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and then you can see the App running on &lt;a href=&quot;http://127.0.0.1:5000/&quot;&gt;http://127.0.0.1:5000/&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;We use &lt;a href=&quot;https://docs.pytest.org/en/6.2.x/index.html&quot;&gt;pytest&lt;/a&gt; for testing, and add a few simple unit tests.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;app&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;json&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nn&quot;&gt;pytest&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pytest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fixture&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;():&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;test_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_home_page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    GIVEN an app configured for testing
    WHEN the '/' page is requested (GET)
    THEN check response is valid and correct
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Enter a number:&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_home_page_post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    GIVEN an app configured for testing
    WHEN the '/' page is is posted to without num (POST)
    THEN check that an error is returned
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;status_code&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;400&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The factorial of&quot;&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;test_home_page_post_number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;&quot;&quot;
    GIVEN an app configured for testing
    WHEN the '/' page is is posted to with a number (POST)
    THEN check that its factorial is returned
    &quot;&quot;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;'Content-Type'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;'application/x-www-form-urlencoded'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;post&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
                &lt;span class=&quot;s&quot;&gt;'/'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Factorial=5&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;assert&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;sa&quot;&gt;b&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;The factorial of 5 is 120.&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;First we need a fixture to create the client to run the tests against (think of it like an instance of your app).
Then we try to see if the webpage is up and contains some of the HTML of the template.&lt;/p&gt;

&lt;p&gt;The second test simply checks if a false POST receives the error we want (no input, no result).&lt;/p&gt;

&lt;p&gt;The third test checks if the calculation returns the expected result for a specific input.&lt;/p&gt;

&lt;p&gt;To run the tests&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pytest -v
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;and you should receive an output like&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;============================================ test session starts ============================================
platform darwin -- Python 3.9.9, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 -- /usr/local/opt/python@3.9/bin/python3.9
cachedir: .pytest_cache
rootdir: /Path/To/App
plugins: dash-2.0.0
collected 3 items

tests/test_app.py::test_home_page PASSED                                                              [ 33%]
tests/test_app.py::test_home_page_post PASSED                                                         [ 66%]
tests/test_app.py::test_home_page_post_number PASSED                                                  [100%]

============================================= 3 passed in 0.04s =============================================
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;dockerize&quot;&gt;Dockerize&lt;/h3&gt;

&lt;p&gt;After finishing our app, we can get started on creating the Dockerfile.&lt;/p&gt;
&lt;div class=&quot;language-Dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# base image to build&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; python:3.7 as builder&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;mkdir&lt;/span&gt; /install
&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /install&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; requirements.txt /requirements.txt&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;RUN &lt;/span&gt;pip &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; /requirements.txt &lt;span class=&quot;nt&quot;&gt;--target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/install

&lt;span class=&quot;c&quot;&gt;# smaller image to run&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; python:3.7-alpine&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; --from=builder /install /usr/local&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; app /app&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WORKDIR&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; /app&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ENV&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; PYTHONPATH=&quot;${PYTHONPATH}:/usr/local&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;EXPOSE&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; 5000&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;CMD&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [&quot;gunicorn&quot;, &quot;-w 4&quot;, &quot;-b 0.0.0.0:5000&quot;, &quot;app:app&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The first image create is the builder image, where we later copy our necessary python packages from.
This is a multi-stage Dockerfile, so you’ll end up with a smaller image in the end.
We’re using gunicorn as a production server for Python.&lt;/p&gt;

&lt;p&gt;Then you simply build and run it&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker build -t factorialWebApp:v0.1 .
docker run -it -p 5000:5000 factorialWebApp:v0.1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And your WebApp in now running on &lt;a href=&quot;http://127.0.0.1:5000&quot;&gt;http://127.0.0.1:5000&lt;/a&gt;!
&lt;img src=&quot;/assets/images/flaskapp-safari.png&quot; alt=&quot;Flask WebApp Screenshot&quot; title=&quot;WebApp&quot; /&gt;&lt;/p&gt;</content><author><name></name></author><category term="python" /><category term="flask" /><category term="docker" /><summary type="html">It never hurts to have a basic example for a quick WebApp to deploy onto a container service. Here’s one for a Python app, using Flask, Docker and Gunicorn.</summary></entry><entry><title type="html">Adding a secondary fluentd to Openshift Logging as a syslog receiver</title><link href="/2021/11/17/syslog-fluentd.html" rel="alternate" type="text/html" title="Adding a secondary fluentd to Openshift Logging as a syslog receiver" /><published>2021-11-17T00:00:00+00:00</published><updated>2021-11-17T00:00:00+00:00</updated><id>/2021/11/17/syslog-fluentd</id><content type="html" xml:base="/2021/11/17/syslog-fluentd.html">&lt;p&gt;OpenShift comes with loads of handy operators managing various Kubernetes resources for you, some open source, some not.
&lt;a href=&quot;https://github.com/openshift/cluster-logging-operator&quot;&gt;OpenShift Logging&lt;/a&gt; is the wrapper for an EFK stack running on your cluster.
Like with other operators you don’t create your own daemonsets or deployments, instead you configure it with a CustomResource called
&lt;a href=&quot;https://docs.openshift.com/container-platform/4.9/logging/cluster-logging.html&quot;&gt;ClusterLogging&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So if you’re thinking of using this Fluentd for anything other than logs from this cluster, you’re pretty much out of luck.&lt;/p&gt;

&lt;p&gt;Note: You can set everything to Unmanaged and change the configs, but that’s not sustainable as you’re basically disabling any operator actions. Also editing what’s supposed to be an indented file represented as one long string is no fun.&lt;/p&gt;

&lt;p&gt;You can however piggyback on the resources deployed by the operator by adding a secondary Fluentd in the same namespace with some modifications.
We’ll create a Fluentd that acts as a syslog receiver, though you could in theory add any input you like, see the list of &lt;a href=&quot;https://www.fluentd.org/plugins&quot;&gt;Fluentd input plugins&lt;/a&gt;.
And we’ll re-use the ElasticSearch and Kibana pods to visualize those logs.&lt;/p&gt;

&lt;h3 id=&quot;secondary-fluentd&quot;&gt;Secondary Fluentd&lt;/h3&gt;

&lt;p&gt;Assuming the logging operator is installed and one instance of the EFK pods is up and running:&lt;/p&gt;

&lt;p&gt;Let’s start with a ConfigMap, where we add the Fluentd config file receiving logs on (the pods) port 5140, and sending everything as is to the local ElasticSearch.
The exact paths might vary, but can be taken from the primary Fluentd daemonset.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
  namespace: openshift-logging
data:
  fluent.conf: |-
    #syslog plugin
    &amp;lt;source&amp;gt;
      @type udp
      port 5140
      bind 0.0.0.0
      &amp;lt;parse&amp;gt;
        @type none
      &amp;lt;/parse&amp;gt;
      tag system
    &amp;lt;/source&amp;gt;
    &amp;lt;match **&amp;gt;
      @type copy
      &amp;lt;store&amp;gt;
        @type elasticsearch
        host elasticsearch.openshift-logging.svc
        port 9200
        logstash_format true
        scheme https
        ssl_version TLSv1_2
        client_key '/var/run/ocp-collector/secrets/fluentd/tls.key'
        client_cert '/var/run/ocp-collector/secrets/fluentd/tls.crt'
        ca_file '/var/run/ocp-collector/secrets/fluentd/ca-bundle.crt'
      &amp;lt;/store&amp;gt;
    &amp;lt;/match&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;There is a syslog input plugin as well, but that might need specific settings depending on the syslog message format.
After the config, we can create our deployment (a daemonset might be more useful for you).&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: fluentd-syslog
  name: fluentd-syslog
  namespace: openshift-logging
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fluentd-syslog
  template:
    metadata:
      labels:
        app: fluentd-syslog
    spec:
      containers:
      - image: quay.io/fluentd_elasticsearch/fluentd:v3.3.0
        imagePullPolicy: IfNotPresent
        name: fluentd
        env:
        - name: FLUENTD_ARGS
          value: --no-supervisor
        ports:
        - containerPort: 24224
          name: forward
          protocol: TCP
        - containerPort: 5140
          name: syslog
          protocol: TCP
        volumeMounts:
        - name: varlog
          mountPath: /var/log
          readOnly: false
        - name: config
          mountPath: /etc/fluent/config.d
        - mountPath: /var/run/ocp-collector/secrets/fluentd
          name: fluentd
          readOnly: true
        - mountPath: /etc/pki/ca-trust/extracted/pem/
          name: fluentd-trusted-ca-bundle
          readOnly: true
      volumes:
      - name: varlog
        emptydir: {}
      - configMap:
          defaultMode: 420
          name: fluentd-config
        name: config
      - name: fluentd
        secret:
          defaultMode: 420
          secretName: fluentd
      - configMap:
          defaultMode: 420
          items:
          - key: ca-bundle.crt
            path: tls-ca-bundle.pem
          name: fluentd-trusted-ca-bundle
        name: fluentd-trusted-ca-bundle
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The only differences between this an a generic deployment are the additional mounted volumes for the certificates as well as an image that already contains the ElasticSearch plugin.&lt;/p&gt;

&lt;p&gt;You can build your own image as well, see the README of the original &lt;a href=&quot;https://hub.docker.com/r/fluent/fluentd/&quot;&gt;Fluentd image&lt;/a&gt; for instructions.&lt;/p&gt;

&lt;p&gt;After creating the deployment we only need to expose the port from inside the pod to the outside world. If you have a loadbalancer or ingress, use that. Otherwise a Nodeport works just as well.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;---
apiVersion: v1
kind: Service
metadata:
  name: fluentd-syslog
  namespace: openshift-logging
  labels:
    app: fluentd-syslog
spec:
  type: NodePort
  ports:
  - port: 5140
    targetPort: 5140
    nodePort: 30514
    protocol: UDP
  selector:
    app: fluentd-syslog
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;This service exposed the deployment on the nodes port 30514.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;Now you can test the deployment by sending a message yourself, for example&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nc -w0 -u &amp;lt;Node IP&amp;gt; 30514 &amp;lt;&amp;lt;&amp;lt; &quot;FAKE SYSLOG MESSAGE&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;After a few seconds the log should show up inside your Kibana.&lt;/p&gt;</content><author><name></name></author><category term="OpenShift" /><category term="fluentd" /><category term="logging" /><category term="EFK" /><category term="syslog" /><summary type="html">OpenShift comes with loads of handy operators managing various Kubernetes resources for you, some open source, some not. OpenShift Logging is the wrapper for an EFK stack running on your cluster. Like with other operators you don’t create your own daemonsets or deployments, instead you configure it with a CustomResource called ClusterLogging.</summary></entry><entry><title type="html">Running PowerDNS in docker-compose</title><link href="/2021/09/08/powerdns.html" rel="alternate" type="text/html" title="Running PowerDNS in docker-compose" /><published>2021-09-08T00:00:00+00:00</published><updated>2021-09-08T00:00:00+00:00</updated><id>/2021/09/08/powerdns</id><content type="html" xml:base="/2021/09/08/powerdns.html">&lt;p&gt;PowerDNS is easily installed and run on most servers, whether you’re using it as an authoritative server or a recursor. To better understand the difference, check out &lt;a href=&quot;https://umbrella.cisco.com/blog/what-is-the-difference-between-authoritative-and-recursive-dns-nameservers&quot;&gt;this blog&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Basically your authoritative server has the actual DNS info you’re looking for or providing (usually an IP), and your recursive server is the one finding the path to the right server and caching any previously checked information.&lt;/p&gt;

&lt;h3 id=&quot;checking-out-powerdns&quot;&gt;Checking out PowerDNS&lt;/h3&gt;

&lt;p&gt;While most other nameservers combine recursive and authoritative functions, PowerDNS or pdns comes in two flavours (pdns-auth and pdns-recursor). Trying it out by installing the binaries is easy enough with the &lt;a href=&quot;https://doc.powerdns.com/authoritative/installation.html&quot;&gt;installation docs&lt;/a&gt;. The default comes with a bind backend, but in this example we’re using sqlite3 instead.&lt;/p&gt;

&lt;p&gt;So for the containerized version, all we need is a server with:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;docker&lt;/li&gt;
  &lt;li&gt;docker-compose&lt;/li&gt;
  &lt;li&gt;port 53 (and potentially 8081/8082 if you want to contact pdns via API) open&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We start with the docker-compose.yaml file:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# based on https://github.com/PowerDNS/pdns
---
version: '2.0'
services:
  recursor:
    image: powerdns/pdns-recursor-45:4.5.5 # the latest pdns-recursor from dockerhub
    # the forward-zones are used to make sure the recursor asks the local authoritative server 
    # about our own domains
    command: --forward-zones=&amp;lt;domain&amp;gt;=172.25.0.2:5300 --local-address=0.0.0.0 --local-port=53 
    environment:
      - PDNS_RECURSOR_API_KEY
    ports:
      - &quot;53:53&quot;
      - &quot;53:53/udp&quot;
      - &quot;8082:8082&quot; # HTTP API for the recursor

  auth:
    image: powerdns/pdns-auth-45:4.5.1 # latest pdns-authoritative nameserver image
    command: --local-address=0.0.0.0 --local-port=5300
    user: root
    environment:
      - PDNS_AUTH_API_KEY
    ports:
      - &quot;5300:5300&quot; # this is the port we redirect queries to above
      - &quot;5300:5300/udp&quot;
      - &quot;8081:8081&quot; # HTTP API of the server
    volumes:
      - /var/lib/powerdns/pdns.sqlite3:/var/lib/powerdns/pdns.sqlite3
    networks:
      default:
        ipv4_address: 172.25.0.2

networks:
  default:
    driver: bridge
    ipam:
      config:
        # defining the network range and IP for the authoritative server is only necessary in 
        # specific cases (e.g. to avoid breaking networking on some servers, most do fine without)
        - subnet: 172.25.0.0/16
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;initializing-the-database&quot;&gt;Initializing the database&lt;/h3&gt;

&lt;p&gt;Before running pdns, the sqlite database needs to be created.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;mkdir /var/lib/powerdns
sqlite3 -init schema.sql /var/lib/powerdns/pdns.sqlite3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Where schema.sql is taken from &lt;a href=&quot;https://doc.powerdns.com/authoritative/backends/generic-sqlite3.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;environment-variables-for-api-access&quot;&gt;Environment variables for API access&lt;/h3&gt;

&lt;p&gt;To enable the API access to pdns, make sure you have the following environment variables set:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;export PDNS_RECURSOR_API_KEY=changeme
export PDNS_AUTH_API_KEY=changeme
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Starting the containers is as simple as running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose up -d&lt;/code&gt; in the folder where the above YAML file is.&lt;/p&gt;

&lt;h3 id=&quot;testing&quot;&gt;Testing&lt;/h3&gt;

&lt;p&gt;Check if your containers are successfully running with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose ps -a&lt;/code&gt;.
If they’re up, you can test the service using&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -v -H 'X-API-Key: changeme' http://&amp;lt;serverIP&amp;gt;:8081/api/v1/servers/localhost/ | jq
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To add zones to the authoritative server you can use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pdnsutil&lt;/code&gt; or the &lt;a href=&quot;https://doc.powerdns.com/authoritative/http-api/zone.html&quot;&gt;API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To check if you’ve successfully added a zone or record, you can check with either&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;nslookup &amp;lt;domain&amp;gt; &amp;lt;serverIP&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;dig &amp;lt;domain&amp;gt; @serverIP
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Adding a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--port 5300&lt;/code&gt; to the dig command, let’s you test the authoritative server directly.&lt;/p&gt;</content><author><name></name></author><category term="DNS" /><category term="docker-compose" /><category term="pdns" /><category term="networking" /><summary type="html">PowerDNS is easily installed and run on most servers, whether you’re using it as an authoritative server or a recursor. To better understand the difference, check out this blog.</summary></entry></feed>