<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <atom:link href="http://shanehowearth.com/rss/" rel="self" type="application/rss+xml"></atom:link>
    <title>Shane Howearth</title>
    <link>http://shanehowearth.com</link>
    <description>A curiousity collection</description>
    <lastBuildDate>Fri, 24 Apr 2026 13:57:55 GMT</lastBuildDate>
    <image>
      <url>http://shanehowearth.com/public/images/blog-logo.jpg</url>
      <title>Shane Howearth</title>
      <link>http://shanehowearth.com</link>
    </image>
    <item>
      <title>Dead Letter Queues</title>
      <link>http://shanehowearth.com/dead-letter-queues</link>
      <description>&lt;p&gt;A Dead Letter Queue (DLQ) is an architectural pattern for handling messages or events that a system cannot process after multiple attempts.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Why is it named a Dead Letter Queue (DLQ)?&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The physical mail in the snail mail/Postal service would occasionally encounter mail that was undeliverable - usually because the address was incorrect, illegible, or for some other reason.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;What should happen to that mail? It got put into a bin/container where it could be dealt with later.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Why are DLQs used?&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Standard resilience patterns, like retries, are perfect for handling transient failures - the quick network blips or short-term service degradations that resolve themselves. However, when a failure becomes permanent (exceeding your SLA or retry limits), you need a robust fallback strategy. This is where the DLQ comes in: it captures the message for later action while allowing you to surface a clean error to the user.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;In the past, these failures were often just logged. The problem is that logs are easily buried in a flood of other data; if no one is manually auditing them, a failure can go unnoticed indefinitely. Moving these events to a DLQ ensures they aren&amp;rsquo;t just recorded, but stored in a dedicated location for resolution.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;While it’s tempting to make every failure a pageable event, context matters. Nobody wants to be woken up at 2:00 AM because of a user&amp;rsquo;s malformed input. A DLQ allows for a more nuanced approach to on-call rotations:&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Internal Failures: If your own upstream service fails, it may warrant a page.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Third-Party Failures: If an external vendor returns a 500 error or a &amp;ldquo;payment required&amp;rdquo; status, your support team often can’t fix it immediately.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Instead of triggering unnecessary alarms, the best strategy is to route these &amp;ldquo;un-processable&amp;rdquo; messages to the DLQ. This provides a central &amp;ldquo;to-do list&amp;rdquo; that your team can review and manage at the start of the next business day.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Common mistakes&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;A DLQ is great at capturing the fact that events have become unprocessable, but that&amp;rsquo;s of little use if the DLQ is never checked (it&amp;rsquo;s easy to &amp;ldquo;fire and forget&amp;rdquo;, messages being passed to the DLQ but never handled).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Keeping the failed messages forever - there should be a policy to remove the events after some given amount of time.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Overusing the DLQ - The DLQ is for messages that cannot be processed, it&amp;rsquo;s not a replacement for log messages that might be detailing poor use of the service.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">48a8e384-86a6-4786-9d74-7b0a71f3e499</guid>
      <pubDate>Sun, 08 Mar 2026 23:38:46 GMT</pubDate>
    </item>
    <item>
      <title>The rise of &#34;AI&#34; (LLMs)</title>
      <link>http://shanehowearth.com/the-rise-of-ai-llms</link>
      <description>&lt;p&gt;For anyone living under a rock, there has been major advances in AI technology over the past few years.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The initial release of ChatGPT (November 2022), led to wild claims about the value of AI, and the demise of various industries. As time has progressed, a lot of that hype has so far failed to be realised. Having said that, some of the anti-AI hysteria has also proved to be overdone.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;As I write this, know that there is currently a &amp;ldquo;race&amp;rdquo; going on where a lot of money is being spent on finding ways to integrate these LLMs into products that serve us better. This post isn&amp;rsquo;t going to address that aspect (I&amp;rsquo;m not much of a technology forecaster, what&amp;rsquo;s going to happen is going to happen, but how it will look is beyond me).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;What this post will focus on is how I am finding using AI in my day to day activity as a Software Engineer.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Let me further preface this by saying that I have been writing software (professionally) for the better part of the last 15 years, longer if you count before I went to University.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Because of the length of time that I have been in &amp;ldquo;the game&amp;rdquo; I have seen a few things come and go, and I will use that experience to evaluate the AI tools.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I have been using two LLMs over the past couple of months that are free to use, and &amp;ldquo;do the job&amp;rdquo; - they are &lt;a href=&#34;https://gemini.google.com/&#34;&gt;Gemini&lt;/a&gt; and &lt;a href=&#34;https://claude.ai/&#34;&gt;Claude&lt;/a&gt; - I have not been using &lt;a href=&#34;https://chatgpt.com/&#34;&gt;ChatGPT&lt;/a&gt;, even though they have been&amp;rdquo;leading the race&amp;rdquo; and have released a new version (5.0).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The first thing to say, I use AI as a &amp;ldquo;pairing partner&amp;rdquo; and to help me &amp;ldquo;search&amp;rdquo; for answers - the LLMs have a better Natural Language Processing ability that &amp;ldquo;understands&amp;rdquo; my query than a query submitted to Google.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This means that I will get answers that I would get from sites like &lt;a href=&#34;https://stackoverflow.com/&#34;&gt;StackOverflow&lt;/a&gt;, or blog posts, or whatever else Google would find for me. The knowledge isn&amp;rsquo;t &amp;ldquo;knew&amp;rdquo;, it&amp;rsquo;s just (IMO) easier to access and/or find.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Subjectively I can say that I have found Claude to marginally &amp;ldquo;better&amp;rdquo; than Gemini, it has been producing less hallucinated code examples for me. I have had Gemini provide me example code that used incorrect types in function calls.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;To give a better understanding, I am using &lt;a href=&#34;https://github.com/rivo/tview&#34;&gt;TView&lt;/a&gt; in my version of &lt;a href=&#34;https://github.com/shaneHowearth/solitaire&#34;&gt;Solitaire&lt;/a&gt; that runs in a terminal.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;At one point Gemini suggested that the function it was proposing I use to solve an issue required a &lt;code&gt;tview.Key&lt;/code&gt;, which doesn&amp;rsquo;t exist within the library. What was actually required was a &lt;code&gt;tcell.Key&lt;/code&gt; (defined in &lt;a href=&#34;https://github.com/gdamore/tcell/&#34;&gt;TCell&lt;/a&gt;).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;In general I have been able to do things like &amp;ldquo;paste all of my project code ino a Claude prompt&amp;rdquo; and ask Claude &amp;ldquo;Why is this broken&amp;rdquo;, and got a lis of issues to look at.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I have also had Claude help me immensely by telling me how to rearchitect my code (after I asked it to), which produced &lt;a href=&#34;https://github.com/shaneHowearth/solitaire/commit/95693d9db7a8e5ad6b75d48502d45f9ae0175435&#34;&gt;this commit&lt;/a&gt; (Note: I edited the code as suggested by Claude, and I found that asking Claude to present it&amp;rsquo;s suggestions as a diff made my life a LOT easier)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;However there have been a couple of notable issues with both Claude and Gemini that I feel need raising. I had the experience of an issue with an incorrect cell being highlighted when clicked. The LLMs both suggested three possible issues, that proved to be (at least partially) wrong.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Further, I would tell the LLMs that they were wrong, and provide evidence, they would claim to accept that they had erred, but, some moments later&amp;hellip; give me the same &amp;ldquo;solution&amp;rdquo; to the problem again.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Quite infuriating.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I eventually found a fix for the problem I was having, but that was through my own creativity, not due to any help from the LLMs (or Google for that matter).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I have found that I treat the LLMs as &amp;ldquo;pairing partners&amp;rdquo; - I can ask them for their input, and they give me their /opinions/ freely. I still have to use my experience as a developer, and my intuition to help me realise whether to use the advice, or not, but I have the same filter working for me when I interact with humans.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;One thing that I do find the LLMs are superior than me at is rewording comments, and creating names. I had a linter complain that the comment I was using in a &lt;code&gt;//nolint&lt;/code&gt; directive wasn&amp;rsquo;t good enough - but it gave no indication of why or how to fix it.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The following is the transcript from Claude&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;can you improve the comment on this for gocritic&#xA;&#xA;// nolint: mnd // The magic numbers in here are too magic.&#xA;&#xA;Edit&#xA;Here are some better comments that actually explain why you&#39;re disabling the magic number detector:&#xA;&#xA;Option 1: Explain the context&#xA;&#xA;go&#xA;//nolint:mnd // Card game constants (suits, ranks, deck size) are self-documenting&#xA;Option 2: Explain why constants aren&#39;t better&#xA;&#xA;go&#xA;//nolint:mnd // Small numeric literals for UI positioning are clearer inline than as named constants&#xA;Option 3: Domain-specific explanation&#xA;&#xA;go&#xA;//nolint:mnd // Standard card values (13 ranks, 4 suits, 52 cards) are universally understood&#xA;Option 4: Performance/readability trade-off&#xA;&#xA;go&#xA;//nolint:mnd // Simple numeric values used once don&#39;t warrant separate constant declarations&#xA;Option 5: Configuration/layout explanation&#xA;&#xA;go&#xA;//nolint:mnd // Layout dimensions and offsets are more readable as literal values&#xA;The key is to explain why magic numbers are acceptable in this specific context, not just acknowledge that they exist. Choose the option that best matches what your code is actually doing.&#xA;&#xA;What kind of magic numbers are you dealing with? That would help me suggest the most appropriate comment.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;I chose the final suggestion, and the linter was happy. I honestly wouldn&amp;rsquo;t have thought to come up with that.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Hopefully it&amp;rsquo;s clear to you that the LLMs are usable, and helpful, but only to a point. As a user I still need to understand, and sometimes verify, what the LLM is telling me. There&amp;rsquo;s no &amp;ldquo;blind acceptance&amp;rdquo;. It&amp;rsquo;s easy, if you don&amp;rsquo;t pay attention, for the LLMs to create problems.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">b426fe27-a16c-459b-9692-4c271e6dac75</guid>
      <pubDate>Sun, 17 Aug 2025 01:56:49 GMT</pubDate>
    </item>
    <item>
      <title>The wonders of git</title>
      <link>http://shanehowearth.com/the-wonders-of-git</link>
      <description>&lt;p&gt;I&amp;rsquo;ve been wanting to write about git for a very long time. It&amp;rsquo;s a tool used for managing versions of software (but it can be used for other things).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;One of the reasons that I haven&amp;rsquo;t written much about git is that, quite frankly, there&amp;rsquo;s a lot to learn.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I use git from the command line, because it&amp;rsquo;s the most flexible way I know, and I am about to show an example of what I had to do today, which will demonstrate git&amp;rsquo;s strength (I hope).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Before I start, if you&amp;rsquo;re unfamiliar with the terminology used here, I recommend reading the first 3 or 4 chapters of &lt;a href=&#34;https://git-scm.com/&#34;&gt;the Git book&lt;/a&gt; (it&amp;rsquo;s well worth the effort).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Right, some context. Today I was working on creating a &lt;code&gt;topic branch&lt;/code&gt; because I had a change to write for the code at work. As I worked on the change I realised that I really needed to break the branch up into two branches, one with a large amount of change that prepared the way for the actual change that I was working on. Reviewing the two changes togather would be tiresome, and might lead to things being missed.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I created the preparation branch, with a commit inside it, and pushed it to our shared repository.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I then pushed the remaining change in the topic branch to the same repository.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Our CI runs on every branch, triggered by the push, and I duly got an email saying that tests had failed on the preparation branch, and I realised that I had included some of the change meant for the preparation branch in the topic branch. And this is what I did to get myself into a position where the preparation branch had the code it needed, and the topic branch no longer did.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-sh&#34;&gt;git checkout &amp;lt;topic branch&amp;gt;&#xA;git reset HEAD~1&#xA;git add -p&#xA;&amp;lt;add the stuff meant for the preparation branch&amp;gt;&#xA;git commit -m &amp;quot;move me&amp;quot;&#xA;git commit -m &amp;quot;the rest&amp;quot; .&#xA;git log -n2&#xA;&amp;lt;grab the sha1&amp;gt;&#xA;git checkout &amp;lt;preparation branch&amp;gt;&#xA;git cherry-pick &amp;lt;saved sha1&amp;gt;&#xA;git rebase -i HEAD~2&#xA;&amp;lt;squash the two commits&amp;gt;&#xA;git push --force&#xA;git checkout &amp;lt;topic branch&amp;gt;&#xA;git rebase &amp;lt;preparation branch&amp;gt;&#xA;git push --force&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Let&amp;rsquo;s break that down into (hopefully) manageable bites.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;1) &lt;code&gt;git checkout &amp;lt;topic branch&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This tells git which branch it is working on. Work on one branch at a time. Git will apply the commands to this branch, until told otherwise.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;2) &lt;code&gt;git reset HEAD~1&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This &amp;ldquo;undoes&amp;rdquo; the last commit. But it&amp;rsquo;s a &lt;strong&gt;soft&lt;/strong&gt; reset, meaning that the changes in that commit are now unstaged.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;3) &lt;code&gt;git add -p&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;(add the stuff meant for the preparation branch)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Git allows you to add parts of a change, hunk by hunk, and you can edit what is being added using this command.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;4) &lt;code&gt;git commit -m &amp;quot;move me&amp;quot;&lt;/code&gt;;&#xA;&lt;code&gt;git commit -m &amp;quot;the rest&amp;quot; .&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;These two commands commit the changes, the first is a commit comprised of what I had staged in the &lt;code&gt;add -p&lt;/code&gt; step above, the second commits the rest. The &lt;code&gt;-m&lt;/code&gt; tells git to use the &lt;code&gt;message&lt;/code&gt; supplied, instead of going into the editor and mucking about.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;5) &lt;code&gt;git log -n2&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I used this to show me a synopsis of the two commits that I had just created, and allowed me to copy the sha1 hash of the commit that I had named &amp;ldquo;move me&amp;rdquo; which contained the changes that I wanted to move.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;6) &lt;code&gt;git checkout &amp;lt;preparation branch&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Git is now applying the commands to the preparation branch.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;7) &lt;code&gt;git cherry-pick &amp;lt;saved sha1&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This moves the commit from the topic branch to the preparation branch.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;8) &lt;code&gt;git rebase -i HEAD~2&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Moving into &lt;code&gt;interactive&lt;/code&gt; mode, git willapply some &lt;code&gt;rebase&lt;/code&gt; commands, in this case I &lt;code&gt;squashed&lt;/code&gt; the two commits together (the existing commit in the preparation branch, and the commit that I just moved to this branch).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;9) &lt;code&gt;git push --force&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Updating the remote repository with the new state of the preparation branch. The push must be &lt;code&gt;forced&lt;/code&gt; because of the change to the commit made in the previous step.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;10) &lt;code&gt;git checkout &amp;lt;topic branch&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Git is now applying the commands to the topic branch.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;11) &lt;code&gt;git rebase &amp;lt;preparation branch&amp;gt;&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This tells git to move the topic branch such that it is branched from the new tip of the preparation branch.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;12) &lt;code&gt;git push --force&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The previous command had changed the branch in such a way that the remote repository needed to be forced to accept the new understanding of this branch.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;That was a LOT of fun to actually do :)&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">1cadfce8-b502-40c4-ab39-ef9dadae0c8b</guid>
      <pubDate>Wed, 25 Sep 2024 09:16:42 GMT</pubDate>
    </item>
    <item>
      <title>Static site hosting on S3</title>
      <link>http://shanehowearth.com/static-site-hosting-on-s3</link>
      <description>&lt;p&gt;A static site is one where there is no server side computation required, no database lookups, just ask for a resource by name, and get it back.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;You can do client side computations with javascript, but not server side.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;There are three parts to hosting a static site on AWS, the storage of the objects, the policy to access the objects, and the name used to access the objects.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;S3 itself does not support &lt;code&gt;https&lt;/code&gt;, if you want your static site to be accessed using &lt;code&gt;https&lt;/code&gt; then use &lt;a href=&#34;https://us-east-1.console.aws.amazon.com/cloudfront/v4/home&#34;&gt;CloudFront&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;With that out of the way let&amp;rsquo;s create a simple static site that is hosted on an S3 bucket on AWS.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;From the &lt;a href=&#34;https://us-east-1.console.aws.amazon.com/console/home&#34;&gt;AWS Console&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Search for &lt;code&gt;S3&lt;/code&gt; and click on the &lt;code&gt;S3&lt;/code&gt; link.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;/images/2024/04/1714455336_d743461f-4468-4ad2-867e-d216b9ae8264.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Click the &amp;ldquo;Create bucket&amp;rdquo; button&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;/images/2024/04/1714452747_98e8121b-b24d-41d7-9a1d-beaf1460b18c.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;And give the new bucket a name (My example uses &amp;ldquo;random-name-12345678&amp;rdquo;, the rule for bucket names is that they &lt;strong&gt;must&lt;/strong&gt; be unique across an AWS partition, be between 3 and 63 characters long, consist only of lowercase letters, numbers, dots, and hyphens, but dot usage isn&amp;rsquo;t recommended, and begin and end with a letter)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;/images/2024/05/1714524699_6c491616-7c1a-4e54-aea7-5f5267c0d06e.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Select the new bucket from the list of buckets&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;/images/2024/04/1714452749_73a92882-1084-4965-b22a-f70ab931af0d.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Upload your files to the bucket. An example might be an index.html&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;html&amp;gt;&#xA;  &amp;lt;head&amp;gt;&amp;lt;title&amp;gt;Hello AWS&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&#xA;  &amp;lt;body&amp;gt;&#xA;    &amp;lt;h1&amp;gt;Rock and/or Roll!&amp;lt;/h1&amp;gt;&#xA;  &amp;lt;/body&amp;gt;&#xA;&amp;lt;/html&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Click the &amp;ldquo;Upload&amp;rdquo; and &amp;ldquo;Close&amp;rdquo; buttons to upload the files, then close the dialog once complete.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The task now is to make the objects within the bucket available for all to access. For the record, there is no way to have some objects in a bucket private and others public, so do NOT store any objects intended to be private in this bucket, public only.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Select the &amp;ldquo;Permissions&amp;rdquo; tab&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;/images/2024/04/1714452750_a46e3f2c-a5b3-4cb2-b511-ef6be20fe4d1.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Select the &amp;ldquo;edit&amp;rdquo; button in the &amp;ldquo;Block Public Access&amp;rdquo; box, and clear all of the checkboxes, then &amp;ldquo;Save Changes&amp;rdquo;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;img src=&#34;/images/2024/04/1714452749_f29fda1f-e231-45cb-b2eb-2aacc0565f70.png&#34; alt=&#34;&#34;&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;A policy is required to allow the objects to be accessed as well.&#xA;&lt;img src=&#34;/images/2024/04/1714452746_48a50781-e48c-414c-992e-54fc2f3880a5.png&#34; alt=&#34;&#34;&gt; -&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Click the &amp;ldquo;Add New Statement&amp;rdquo; button so that we can add the following policy (replacing the defaulted in policy):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;{&#xA;   &amp;quot;Version&amp;quot;: &amp;quot;2012-10-17&amp;quot;,&#xA;   &amp;quot;Statement&amp;quot;: [&#xA;      {&#xA;         &amp;quot;Sid&amp;quot;: &amp;quot;Only allow writes to my bucket with bucket owner full control&amp;quot;,&#xA;         &amp;quot;Effect&amp;quot;: &amp;quot;Allow&amp;quot;,&#xA;         &amp;quot;Principal&amp;quot;: {&#xA;            &amp;quot;AWS&amp;quot;: [&#xA;               &amp;quot;arn:aws:iam::111122223333:user/ExampleUser&amp;quot;&#xA;            ]&#xA;         },&#xA;         &amp;quot;Action&amp;quot;: [&#xA;            &amp;quot;s3:PutObject&amp;quot;&#xA;         ],&#xA;         &amp;quot;Resource&amp;quot;: &amp;quot;arn:aws:s3:::random-name-12345678/*&amp;quot;,&#xA;         &amp;quot;Condition&amp;quot;: {&#xA;            &amp;quot;StringEquals&amp;quot;: {&#xA;               &amp;quot;s3:x-amz-acl&amp;quot;: &amp;quot;bucket-owner-full-control&amp;quot;&#xA;            }&#xA;         }&#xA;      }&#xA;   ]&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And now you can access the site from the following URL - note that this example has long since been deleted, so you will need to determine the URL for your bucket, it&amp;rsquo;s a simple case of &lt;code&gt;bucket name&lt;/code&gt;.&lt;code&gt;AWS region&lt;/code&gt;.amazonaws.com&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://random-name-12345678.s3.us-east-1.amazonaws.com/index.html&#34;&gt;https://random-name-12345678.s3.us-east-1.amazonaws.com/index.html&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Of course, if you want to have a friendlier name you will need to work with &lt;a href=&#34;https://aws.amazon.com/route53/&#34;&gt;Route53&lt;/a&gt;, but that is the subject of another blog post :-)&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">2b0b8e95-1637-4a06-8cd7-be54a2a45cc4</guid>
      <pubDate>Wed, 01 May 2024 04:21:31 GMT</pubDate>
    </item>
    <item>
      <title>The power of the index</title>
      <link>http://shanehowearth.com/the-power-of-the-index</link>
      <description>&lt;p&gt;A discussion came up on IRC today, someone was asking why a query was being done the way it was, by Postgres. The person had looked at the explain function of Postgres for a given query, and the planner showed how the query was going to be actioned.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The query was such&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;SELECT COUNT(*) FROM one, two&#xA;WHERE one.id = two.id AND d &amp;gt;= 56 AND d &amp;lt;= 69 AND a &amp;gt; 110;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The planner explained the query plan thus&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;Aggregate (cost=1365.77..1365.77 rows=1 width=0) (actual time=5.250..5.250 rows=1 loops=1)&#xA;  -&amp;gt; Nested Loop (cost=0.00..1365.12 rows=250 width=0) (actual time=0.034..5.181 rows=230 loops=1)&#xA;    -&amp;gt; Seq Scan on two (cost=0.00..180.00 rows=391 width=4) (actual time=0.014..3.737 rows=388 loops=1)&#xA;      Filter: (a &amp;gt; 110)&#xA;    -&amp;gt; Index Scan using one_id_idx on one (cost=0.00..3.02 rows=1 width=4) (actual time=0.003..0.003 rows=1 loops=388)&#xA;      Index Cond: (one.id = &amp;quot;outer&amp;quot;.id)&#xA;      Filter: ((d &amp;gt;= 56) AND (d &amp;lt;= 69))&#xA;Total runtime: 5.339 ms&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;So, what&amp;rsquo;s happening?&#xA;The query planner has done one loop on table &lt;code&gt;two&lt;/code&gt; and 388 loops on the index of table &lt;code&gt;one&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The question is, why did the planner choose that as the best way to implement the query?&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This is where understanding an index is so important.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;First I will explain why the query planner chose this route, then I will explain a bit more about indexes.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;In the first place, the query planner had two create subsets of each table, rows from table &lt;code&gt;one&lt;/code&gt; had to have field &lt;code&gt;d&lt;/code&gt; between 56 and 69, and rows from table &lt;code&gt;two&lt;/code&gt; had to have a field &lt;code&gt;a&lt;/code&gt; greater than 110.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;That created two sets with the following number of rows each.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;SELECT COUNT(*) FROM one WHERE d &amp;gt;=56 AND d &amp;lt;=69;&#xA; count&#xA;----------&#xA;   6544&#xA;(1 row)&#xA;&#xA;SELECT COUNT(*) FROM two WHERE a &amp;gt; 119;&#xA; count&#xA;----------&#xA;    388&#xA;(1 row)&#xA;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Next the &lt;code&gt;join&lt;/code&gt; - the part of the query that says&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;WHERE one.id = two.id&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;There are two choices to be made, for the same algorithm. That is, one set must have every one of its items searched for in the second set.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The cost of the query will be&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;O(n)*O(log2(m))&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The reason for the cost - every item in one set = &lt;code&gt;O(n)&lt;/code&gt; (Where &lt;code&gt;n&lt;/code&gt; is the number of items in the first set)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Searching for the item in the other set &lt;code&gt;O(log2(m))&lt;/code&gt; (where &lt;code&gt;m&lt;/code&gt; is the number of items in the second set)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;A search is &lt;code&gt;O(log2(n))&lt;/code&gt; (where &lt;code&gt;n&lt;/code&gt; is the number of rows in the set being searched) because the fastest way that we know how to search is binary search of a sorted list, that is, we look at the midpoint of a sorted list, and if our item is greater than the value held at the midpoint, we then search the top half of the list, and if our item is less than the value held at the midpoint we search the bottom half. And so on until we either find the item, or there is nothing left to search.  Because the number of items being searched each time is reduced by half, we end up with a total number of times that we searched (worst case) of &lt;code&gt;log2(n)&lt;/code&gt;, the logarithm with a base of 2 of &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;An index is a sorted list (and that&amp;rsquo;s all an index is), so searching an index is going to cost &lt;code&gt;O(log(n))&lt;/code&gt; (where &lt;code&gt;n&lt;/code&gt; is the number of rows in the set being searched).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The planner has chosen to iterate over every item in the smaller set &lt;code&gt;two&lt;/code&gt; which has 388 items, and will search the larger set &lt;code&gt;one&lt;/code&gt; which has 6544 items.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The reason why can be seen in the following math.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Search the larger set for the keys in the smaller set&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;log2(6544) = 12.67596&#xA;multiplied by the number of searches&#xA;388 * 13 = 5044 (we cannot do a partial operation) &#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Whereas searching the smaller set for the items in the larger set:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;log2(388) = 8.5999&#xA;multiplied by the number of searches&#xA;6544 * 9 = 58896 (we cannot do a partial operation) &#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;If the planner had iterated over every item in the larger set, searching the smaller set, there would have (worst case) been 58896 operations.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;However, by iterating over every item in the smaller set, there are only (worst case) 5044 operations.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Summary&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;By having an index to search, what might have been a full table scan (&lt;code&gt;O(n)&lt;/code&gt;), becomes a binary search (&lt;code&gt;O(log2(n))&lt;/code&gt;).&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">dae1c776-16af-44cb-ad83-14503d3d560d</guid>
      <pubDate>Mon, 08 Apr 2024 10:03:14 GMT</pubDate>
    </item>
    <item>
      <title>How I design the layout of a Go program&#39;s code on disk</title>
      <link>http://shanehowearth.com/how-i-design-the-layout-of-a-go-program-on-disk</link>
      <description>&lt;p&gt;The layout of a Go application on disk is a question that almost everyone asks when starting out.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;It&amp;rsquo;s a fair question - there&amp;rsquo;s no scaffolding that&amp;rsquo;s widely used, there was no  guidelines for people for the first decade.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;One of the biggest strengths of Go is that you can lay your project out on disk pretty much how you want to.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Unfortunately, though, that strength is its weakness, people lay their projects out on disk pretty much how they want to, leaving people new to the ecosystem scratching their heads.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The official Go blog did finally create a &lt;a href=&#34;https://go.dev/doc/modules/layout&#34;&gt;blog post&lt;/a&gt; that put some ideas out there for people to use.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Here&amp;rsquo;s howI think about the layout.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;In the first place, the project, for example, has a root directory, that will have the &lt;code&gt;README.md&lt;/code&gt; and &lt;code&gt;go.mod&lt;/code&gt; and &lt;code&gt;go.sum&lt;/code&gt; files in it.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;myproject/&#xA;├── go.mod&#xA;├── go.sum&#xA;└── README.md&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The project itself has a &amp;ldquo;core&amp;rdquo; library, which is typically the business logic for the project, so I put that at the root of the project.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The &lt;code&gt;package&lt;/code&gt; that these files are in is going to be &lt;code&gt;myproject&lt;/code&gt; as well.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;myproject/&#xA;├── go.mod&#xA;├── go.sum&#xA;├── myproject.go&#xA;├── my_project_test.go&#xA;└── README.md&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;To build the project into a binary there needs to be a &lt;code&gt;main&lt;/code&gt; package. There is a de facto standard (meaning everyone uses this standard, but it&amp;rsquo;s not an actual standard), and that is to  put the &lt;code&gt;main&lt;/code&gt; package in a directory named &lt;code&gt;cmd&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;myproject/&#xA;├── cmd&#xA;│   └── main.go&#xA;├── go.mod&#xA;├── go.sum&#xA;├── myproject.go&#xA;├── my_project_test.go&#xA;└── README.md&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The next step in my thought process is &amp;ldquo;supporting libraries&amp;rdquo;, that is, libraries that my business logic will need in order to do its tasks.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I put these supporting libraries beneath the core library, so that all developers can see that the library at the top of the tree is supported by the libraries below.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;There&amp;rsquo;s a little bit extra happening here, as a firm believer in the &lt;a href=&#34;https://en.wikipedia.org/wiki/SOLID&#34;&gt;SOLID&lt;/a&gt; design principles - I create an &lt;code&gt;interface&lt;/code&gt; that is owned by the core library that defines the contract that my datastores need to abide by to be used.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;For this example the interface will be in &lt;code&gt;feed.go&lt;/code&gt;, and the &lt;code&gt;feeds&lt;/code&gt; directory holds the code that creates types that satisfy that interface and allow the core library to interact with the upstream data feeds (each in their own directory).&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;myproject/&#xA;├── cmd&#xA;│   └── main.go&#xA;├── feeds&#xA;│   ├── twitter&#xA;│   │   ├── twitter.go&#xA;│   │   └── twitter_test.go&#xA;│   └── reddit&#xA;│       ├── reddit.go&#xA;│       └── reddit_test.go&#xA;├── feed.go&#xA;├── go.mod&#xA;├── go.sum&#xA;├── myproject.go&#xA;├── my_project_test.go&#xA;└── README.md&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;I used to put the &lt;code&gt;feed.go&lt;/code&gt; file, with interface definition, within the &lt;code&gt;feeds&lt;/code&gt; directory, to show that that wasthe interface that the feeds need to satisfy in order to be used by the core library, but having the definition in the core library demonstrates that the core library &lt;strong&gt;owns&lt;/strong&gt; the interface (which it should).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This should be enough to demonstrate the layout of a project, with the only other thing to mention being that if the supporting libraries themselves need supporting libraries, then those secondary support libraries should go below the libraries that they support.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">5f3e61e1-88fe-438b-96c2-b9b48bdda3de</guid>
      <pubDate>Sat, 23 Mar 2024 03:47:07 GMT</pubDate>
    </item>
    <item>
      <title>Kubernetes initContainers vs Jobs</title>
      <link>http://shanehowearth.com/kubernetes-initcontainers-vs-jobs</link>
      <description>&lt;p&gt;There are tasks that need to take place within your cluster on an ad hoc basis. These tasks tend to have short lifetimes, an example might be &amp;ldquo;updating the schema in a database&amp;rdquo;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Kubernetes offers two pathways to achieve this goal, both very similar, but differ in very subtle ways.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://kubernetes.io/docs/concepts/workloads/controllers/job/&#34;&gt;Jobs&lt;/a&gt;, and &lt;a href=&#34;https://kubernetes.io/docs/concepts/workloads/pods/init-containers/&#34;&gt;init containers&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Both of these pathways execute the same tasks, and, as will be seen further down, they both have similar definitions in manifests.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Jobs&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;A Kubernetes Job will execute a task as soon as it is loaded into the cluster.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This is the manifest I have for a container that I have defined elsewhere to have a certain set of commands be applied to it (basically, I have a container with &lt;a href=&#34;https://github.com/pressly/goose&#34;&gt;Goose&lt;/a&gt; and netcat in it, an image of which is sent to my container repository, and this job &amp;lsquo;runs&amp;rsquo; it with the commands).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The key things to note are that it&amp;rsquo;s a &lt;strong&gt;job&lt;/strong&gt;, it has a &lt;strong&gt;name&lt;/strong&gt;, a container image that it will interact with, some &lt;strong&gt;environment variables&lt;/strong&gt; to be set on that container, and a set of &lt;strong&gt;commands&lt;/strong&gt; that will be run &lt;strong&gt;on&lt;/strong&gt; that container.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: batch/v1&#xA;kind: Job&#xA;metadata:&#xA;  name: accounts-schema-init&#xA;  namespace: &amp;quot;$NAMESPACE&amp;quot;&#xA;spec:&#xA;  template:&#xA;    metadata:&#xA;      name: accounts-schema&#xA;    spec:&#xA;      containers:&#xA;      - name: accounts-schema&#xA;        image: &amp;quot;onepage/accounts-schema:$TAG&amp;quot;&#xA;        imagePullPolicy: &amp;quot;$IMAGEPULLPOLICY&amp;quot;&#xA;        env:&#xA;        - name: PGUSER&#xA;          valueFrom:&#xA;            secretKeyRef:&#xA;              name: account-secret&#xA;              key: username&#xA;        - name: PGPASSWORD&#xA;          valueFrom:&#xA;            secretKeyRef:&#xA;              name: account-secret&#xA;              key: password&#xA;        - name: PGDBNAME&#xA;          value: &amp;quot;$DBNAME&amp;quot;&#xA;        - name: PGHOST&#xA;          value: &amp;quot;$HOST&amp;quot;&#xA;        command:&#xA;        - /bin/sh&#xA;        - -c&#xA;        - |&#xA;          while ! nc -z &amp;quot;$PGHOST&amp;quot; &amp;quot;5432&amp;quot;; do&#xA;            echo &amp;quot;Waiting for $PGHOST:5432 to become available...&amp;quot;&#xA;            sleep 5&#xA;          done&#xA;          goose -v -dir=migrations postgres &amp;quot;user=$PGUSER dbname=$PGDBNAME password=$PGPASSWORD host=$PGHOST sslmode=disable&amp;quot; up;&#xA;      restartPolicy: Never&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;Init containers&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;A bit of background first. A &lt;a href=&#34;https://kubernetes.io/docs/concepts/workloads/pods/&#34;&gt;Pod&lt;/a&gt; will have one or more containers in it that will run. However &lt;strong&gt;before&lt;/strong&gt; those containers are loaded and run on that pod an &lt;strong&gt;init container&lt;/strong&gt; can be run on that pod (and will exit before the main container(s) will run).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This means that when the deployment of the pod takes place then the init container will run in each pod before the main container(s) in the pod do, and we can run something very similar to the above job on the init container.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Note the manifest below, the key things are:&#xA;It&amp;rsquo;s a &lt;strong&gt;deployment&lt;/strong&gt;, with a &lt;strong&gt;name&lt;/strong&gt;, a container image that it will work with (for the &lt;strong&gt;init container&lt;/strong&gt;) as well as the main container, and a sidecar container (I know that it muddies things a bit to have it in the manifest, but it demonstrates that the containers in the pod each have different purposes).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The &lt;strong&gt;initContainer&lt;/strong&gt; specification is almost identical to the job above.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1&#xA;kind: Deployment&#xA;metadata:&#xA;  namespace: &amp;quot;$NAMESPACE&amp;quot;&#xA;  name: accountserver&#xA;  labels:&#xA;    app: account&#xA;spec: # this is the specification of the deployment&#xA;  replicas: $REPLICAS&#xA;  selector:&#xA;    matchLabels:&#xA;      app: account&#xA;  template:&#xA;    metadata:&#xA;      annotations: # inject linkerd into this pod.&#xA;        linkerd.io/inject: enabled&#xA;      labels:&#xA;        app: account&#xA;    spec: # this is the specification of the pods.&#xA;      initContainers: # container that will run when this pod is initialised.&#xA;      - name: accounts-schema&#xA;        image: &amp;quot;onepage/accounts-schema:$TAG&amp;quot;&#xA;        imagePullPolicy: &amp;quot;$IMAGEPULLPOLICY&amp;quot;&#xA;        env:&#xA;        - name: PGUSER&#xA;          valueFrom:&#xA;            secretKeyRef:&#xA;              name: account-secret&#xA;              key: username&#xA;        - name: PGPASSWORD&#xA;          valueFrom:&#xA;            secretKeyRef:&#xA;              name: account-secret&#xA;              key: password&#xA;        - name: PGDBNAME&#xA;          value: &amp;quot;$DBNAME&amp;quot;&#xA;        - name: PGHOST&#xA;          value: &amp;quot;$HOST&amp;quot;&#xA;        command:&#xA;        - /bin/sh&#xA;        - -c&#xA;        - |&#xA;          while ! nc -z &amp;quot;$PGHOST&amp;quot; &amp;quot;5432&amp;quot;; do&#xA;            echo &amp;quot;Waiting for $PGHOST:5432 to become available...&amp;quot;&#xA;            sleep 5&#xA;          done&#xA;          goose -v -dir=migrations postgres &amp;quot;user=$PGUSER dbname=$PGDBNAME password=$PGPASSWORD host=$PGHOST sslmode=disable&amp;quot; up;&#xA;      containers:&#xA;      - name: account&#xA;        image: onepage/accounts&#xA;        imagePullPolicy: &amp;quot;$IMAGEPULLPOLICY&amp;quot;&#xA;        ports:&#xA;        - containerPort: $TARGETPORT&#xA;        env:&#xA;        - name: PORT_NUM&#xA;          value: &amp;quot;$TARGETPORT&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;When to use which.&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;If they&amp;rsquo;re doing the same thing, why use one or the other? Or, When should one approach be used over the other?&lt;/p&gt;&#xA;&#xA;&lt;p&gt;In this case, migrating the schema of a database, we would use the &lt;code&gt;initContainers&lt;/code&gt; because it means that the coupling of the schema to a version of the application container is explicit, and, that when a new version of a container is rolled out that the schema will be in a proscribed state before that new container will begin its work.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;However, there are times when out of band updates are required (eg. &amp;ldquo;Oh god, I rolled out a change to the cluster that needs to be undone without rolling out a new deployment&amp;rdquo; - nobody has ever encountered that&amp;hellip; have they :). At that point a &lt;code&gt;Job&lt;/code&gt; is appropriate, pushing it into the cluster has an immediate effect.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">04b58efd-a892-415c-8325-36f7ebd11811</guid>
      <pubDate>Sat, 06 Jan 2024 00:05:19 GMT</pubDate>
    </item>
    <item>
      <title>envsubstr</title>
      <link>http://shanehowearth.com/envsubstr</link>
      <description>&lt;p&gt;I&amp;rsquo;m writing a lot of &lt;code&gt;yaml&lt;/code&gt; files at the moment, mostly manifests for kubernetes. Part of that task involves substituting variables into the yaml, because they change depending on the environment.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;As an example, this manifest that kubernetes will use to create a namespace has a variable in it that allows me to use the name that I think is appropriate for the environment.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: v1&#xA;kind: Namespace&#xA;metadata:&#xA;    name: &amp;quot;$NAMESPACE&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;To set that &lt;code&gt;$NAMESPACE&lt;/code&gt; variable to something useful I use a command called &lt;a href=&#34;https://www.man7.org/linux/man-pages/man1/envsubst.1.html&#34;&gt;envsubstr&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;From the shell I would do something like&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;NAMESPACE=production envsubst &amp;lt; k8s/namespace.yaml | kubectl apply -f -&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Which means that the information that gets passed to kubernetes would look something like&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: v1&#xA;kind: Namespace&#xA;metadata:&#xA;    name: &amp;quot;production&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;If that&amp;rsquo;s all there was to know, this would be a very short blog post, but wait, there&amp;rsquo;s more!&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;envsubst&lt;/code&gt; will substitute all the things it sees that it thinks are variables. That is, if the example yaml looked like this&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: v1&#xA;kind: Namespace&#xA;metadata:&#xA;    name: &amp;quot;$NAMESPACE&amp;quot;&#xA;    labels:&#xA;        name: &amp;quot;$NAME&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And we ran our previous example command&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;NAMESPACE=production envsubst &amp;lt; k8s/namespace.yaml | kubectl apply -f -&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Then &lt;code&gt;envsubst&lt;/code&gt; will substitute both &amp;lsquo;variables&amp;rsquo;, but only one will have a value - creating&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: v1&#xA;kind: Namespace&#xA;metadata:&#xA;    name: &amp;quot;production&amp;quot;&#xA;    labels:&#xA;        name: &amp;quot;&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Which may not be what we want. We might only want the &lt;code&gt;$NAMESPACE&lt;/code&gt; variable to be changed, and any other variables to be left alone (I will show a full example of a manifest that makes use of this at the bottom, but continue with this small example to keep focus on what&amp;rsquo;s happening).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;To achieve this we explicitly tell &lt;code&gt;envsubstr&lt;/code&gt; which variables to change - putting them in a string after the call, and surrounding the name of each with &lt;code&gt;{}&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;NAMESPACE=production envsubst &#39;${NAMESPACE}&#39; &amp;lt; k8s/namespace.yaml | kubectl apply -f -&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;If there are multiple it would look something like&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;NAMESPACE=production envsubst &#39;${NAMESPACE} ${FOO} ${BAR}&#39; &amp;lt; k8s/namespace.yaml | kubectl apply -f -&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And people can make bash expansions that look for prefixes and so on (but that&amp;rsquo;s well above my paygrade ;)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;what gets produced by &lt;code&gt;envsubstr&lt;/code&gt; is:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: v1&#xA;kind: Namespace&#xA;metadata:&#xA;    name: &amp;quot;production&amp;quot;&#xA;    labels:&#xA;        name: &amp;quot;$NAME&amp;quot;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;One more thing, &lt;a href=&#34;https://www.gnu.org/software/make/&#34;&gt;gnu make&lt;/a&gt; has to use a slightly different syntax in the list, it needs a double &lt;code&gt;$$&lt;/code&gt; .&lt;/p&gt;&#xA;&#xA;&lt;p&gt;So, in a &lt;code&gt;Makefile&lt;/code&gt; use&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;NAMESPACE=production envsubst &#39;$${NAMESPACE}&#39; &amp;lt; k8s/namespace.yaml | kubectl apply -f -&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Finally, the example where I &lt;em&gt;want&lt;/em&gt; some substitution to take place, and some variables to be left behind.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The reason is that I am building a command that I want to use the environment variables that are set inside the docker container&amp;rsquo;s shell.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;You can see that I prefix the variables to be set in the &lt;code&gt;env&lt;/code&gt; with &lt;code&gt;PG&lt;/code&gt;, that&amp;rsquo;s purely so that I can differentiate them, there&amp;rsquo;s no other rhyme or reason.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;So what I need to happen is my Makefile sets &lt;strong&gt;some&lt;/strong&gt; of the variables, but I don&amp;rsquo;t want it to set some others to &amp;ldquo;&amp;rdquo; because they will be set by the shell when the command is run (and if I leave &lt;code&gt;envsubst&lt;/code&gt; to its own devices, it deletes the variables, meaning the shell doesn&amp;rsquo;t know they&amp;rsquo;re even there(.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;apiVersion: batch/v1&#xA;kind: Job&#xA;metadata:&#xA;  name: accounts-schema-init&#xA;  namespace: &amp;quot;$NAMESPACE&amp;quot;&#xA;spec:&#xA;  template:&#xA;    metadata:&#xA;      name: accounts-schema&#xA;    spec:&#xA;      containers:&#xA;      - name: accounts-schema&#xA;        image: &amp;quot;onepage/accounts-schema:$TAG&amp;quot;&#xA;        imagePullPolicy: &amp;quot;$IMAGEPULLPOLICY&amp;quot;&#xA;        env:&#xA;        - name: PGUSER&#xA;          valueFrom:&#xA;            secretKeyRef:&#xA;              name: account-secret&#xA;              key: username&#xA;        - name: PGPASSWORD&#xA;          valueFrom:&#xA;            secretKeyRef:&#xA;              name: account-secret&#xA;              key: password&#xA;        - name: PGDBNAME&#xA;          value: &amp;quot;$DBNAME&amp;quot;&#xA;        - name: PGHOST&#xA;          value: &amp;quot;$HOST&amp;quot;&#xA;        command:&#xA;        - /bin/sh&#xA;        - -c&#xA;        - goose -v -dir=migrations postgres &amp;quot;user=$PGUSER dbname=$PGDBNAME password=$PGPASSWORD host=$PGHOST sslmode=disable&amp;quot; up;&#xA;      restartPolicy: Never&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;Summary&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;Hopefully this gives you (almost) everything that you need to use &lt;code&gt;envsubstr&lt;/code&gt; for substituting variables in files, and only the ones you want substituted.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">e052d775-def7-4596-8980-bd875ace56a8</guid>
      <pubDate>Thu, 04 Jan 2024 21:41:49 GMT</pubDate>
    </item>
    <item>
      <title>kubectl wait</title>
      <link>http://shanehowearth.com/kubectl-wait</link>
      <description>&lt;p&gt;After writing a few million scripts over the years,I&amp;rsquo;ve learnt a thing or two (I hope). One of those things is how painful it is to wait for some condition before continuing with the next step in a script.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;ve used &lt;a href=&#34;https://www.man7.org/linux/man-pages/man1/expect.1.html&#34;&gt;expect&lt;/a&gt; scripts, I&amp;rsquo;ve added &lt;a href=&#34;https://man7.org/linux/man-pages/man1/sleep.1.html&#34;&gt;sleep&lt;/a&gt; calls.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I&amp;rsquo;ve tried a few other things too. It&amp;rsquo;s never fun :(&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt; comes with a &lt;code&gt;wait&lt;/code&gt; option.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;You can ask &lt;code&gt;kubectl&lt;/code&gt; to wait for any number of conditions eg:&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Wait for a &lt;code&gt;namespace&lt;/code&gt; named &lt;code&gt;shane&lt;/code&gt; to become &lt;code&gt;Active&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl wait --for jsonpath=&#39;{.status.phase}=Active&#39; --timeout=5s namespace/shane&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Wait for a &lt;code&gt;deployment&lt;/code&gt; to become &lt;code&gt;Available&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl wait deployment -n cnpg-system cnpg-controller-manager --for condition=Available=True --timeout=210s&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Note that the condition can be specified using the &lt;code&gt;jsonpath&lt;/code&gt;, or, in some cases, by name.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The &lt;code&gt;timeout&lt;/code&gt; is a maximum time to wait for the condition to become true.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Finding the condition can be difficult. Typically it&amp;rsquo;s a matter of digging through &lt;code&gt;.yaml&lt;/code&gt; files looking for selectors, or, getting whatever you want running, then inspecting it. eg&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl get  pods -n shane&#xA;NAME                            READY   STATUS    RESTARTS   AGE&#xA;accountserver-c8897d469-js9qs   1/1     Running   0          17m&#xA;accountserver-c8897d469-mr7jn   1/1     Running   0          17m&#xA;accountserver-c8897d469-xdzpn   1/1     Running   0          17m&#xA;apiserver-57474fd944-7zxmb      1/1     Running   0          18m&#xA;postgres-1                      1/1     Running   0          22m&#xA;postgres-2                      1/1     Running   0          21m&#xA;postgres-3                      1/1     Running   0          19m&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Followed by getting all the metadata on the object, in &lt;code&gt;json&lt;/code&gt; format&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt; $ kubectl get  pods postgres-1 -n shane -o json&#xA;{&#xA;    &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,&#xA;    &amp;quot;kind&amp;quot;: &amp;quot;Pod&amp;quot;,&#xA;    &amp;quot;metadata&amp;quot;: {&#xA;        &amp;quot;annotations&amp;quot;: {&#xA;            &amp;quot;cnpg.io/nodeSerial&amp;quot;: &amp;quot;1&amp;quot;,&#xA;            &amp;quot;cnpg.io/operatorVersion&amp;quot;: &amp;quot;1.21.0&amp;quot;,&#xA;            &amp;quot;cnpg.io/podEnvHash&amp;quot;: &amp;quot;fcffbc754&amp;quot;,&#xA;            &amp;quot;cnpg.io/podSpec&amp;quot;: &amp;quot;{\&amp;quot;volumes\&amp;quot;:[{\&amp;quot;name\&amp;quot;:\&amp;quot;pgdata\&amp;quot;,\&amp;quot;persistentVolumeClaim\&amp;quot;:{\&amp;quot;claimName\&amp;quot;:\&amp;quot;postgres-1\&amp;quot;}},{\&amp;quot;name\&amp;quot;:\&amp;quot;scratch-data\&amp;quot;,\&amp;quot;emptyDir\&amp;quot;:{}},{\&amp;quot;name\&amp;quot;:\&amp;quot;shm\&amp;quot;,\&amp;quot;emptyDir\&amp;quot;:{\&amp;quot;medium\&amp;quot;:\&amp;quot;Memory\&amp;quot;}},{\&amp;quot;name\&amp;quot;:\&amp;quot;app-secret\&amp;quot;,\&amp;quot;secret\&amp;quot;:{\&amp;quot;secretName\&amp;quot;:\&amp;quot;account-secret\&amp;quot;}}],\&amp;quot;initContainers\&amp;quot;:[{\&amp;quot;name\&amp;quot;:\&amp;quot;bootstrap-controller\&amp;quot;,\&amp;quot;image\&amp;quot;:\&amp;quot;ghcr.io/cloudnative-pg/cloudnative-pg:1.21.0\&amp;quot;,\&amp;quot;command\&amp;quot;:[\&amp;quot;/manager\&amp;quot;,\&amp;quot;bootstrap\&amp;quot;,\&amp;quot;/controller/manager\&amp;quot;,\&amp;quot;--log-level=info\&amp;quot;],\&amp;quot;resources\&amp;quot;:{},\&amp;quot;volumeMounts\&amp;quot;:[{\&amp;quot;name\&amp;quot;:\&amp;quot;pgdata\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/var/lib/postgresql/data\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;scratch-data\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/run\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;scratch-data\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/controller\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;shm\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/dev/shm\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;app-secret\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/etc/app-secret\&amp;quot;}],\&amp;quot;securityContext\&amp;quot;:{\&amp;quot;capabilities\&amp;quot;:{\&amp;quot;drop\&amp;quot;:[\&amp;quot;ALL\&amp;quot;]},\&amp;quot;privileged\&amp;quot;:false,\&amp;quot;runAsNonRoot\&amp;quot;:true,\&amp;quot;readOnlyRootFilesystem\&amp;quot;:true,\&amp;quot;allowPrivilegeEscalation\&amp;quot;:false,\&amp;quot;seccompProfile\&amp;quot;:{\&amp;quot;type\&amp;quot;:\&amp;quot;RuntimeDefault\&amp;quot;}}}],\&amp;quot;containers\&amp;quot;:[{\&amp;quot;name\&amp;quot;:\&amp;quot;postgres\&amp;quot;,\&amp;quot;image\&amp;quot;:\&amp;quot;ghcr.io/cloudnative-pg/postgresql:16.0\&amp;quot;,\&amp;quot;command\&amp;quot;:[\&amp;quot;/controller/manager\&amp;quot;,\&amp;quot;instance\&amp;quot;,\&amp;quot;run\&amp;quot;,\&amp;quot;--log-level=info\&amp;quot;],\&amp;quot;ports\&amp;quot;:[{\&amp;quot;name\&amp;quot;:\&amp;quot;postgresql\&amp;quot;,\&amp;quot;containerPort\&amp;quot;:5432,\&amp;quot;protocol\&amp;quot;:\&amp;quot;TCP\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;metrics\&amp;quot;,\&amp;quot;containerPort\&amp;quot;:9187,\&amp;quot;protocol\&amp;quot;:\&amp;quot;TCP\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;status\&amp;quot;,\&amp;quot;containerPort\&amp;quot;:8000,\&amp;quot;protocol\&amp;quot;:\&amp;quot;TCP\&amp;quot;}],\&amp;quot;env\&amp;quot;:[{\&amp;quot;name\&amp;quot;:\&amp;quot;PGDATA\&amp;quot;,\&amp;quot;value\&amp;quot;:\&amp;quot;/var/lib/postgresql/data/pgdata\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;POD_NAME\&amp;quot;,\&amp;quot;value\&amp;quot;:\&amp;quot;postgres-1\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;NAMESPACE\&amp;quot;,\&amp;quot;value\&amp;quot;:\&amp;quot;shane\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;CLUSTER_NAME\&amp;quot;,\&amp;quot;value\&amp;quot;:\&amp;quot;postgres\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;PGPORT\&amp;quot;,\&amp;quot;value\&amp;quot;:\&amp;quot;5432\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;PGHOST\&amp;quot;,\&amp;quot;value\&amp;quot;:\&amp;quot;/controller/run\&amp;quot;}],\&amp;quot;resources\&amp;quot;:{},\&amp;quot;volumeMounts\&amp;quot;:[{\&amp;quot;name\&amp;quot;:\&amp;quot;pgdata\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/var/lib/postgresql/data\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;scratch-data\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/run\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;scratch-data\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/controller\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;shm\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/dev/shm\&amp;quot;},{\&amp;quot;name\&amp;quot;:\&amp;quot;app-secret\&amp;quot;,\&amp;quot;mountPath\&amp;quot;:\&amp;quot;/etc/app-secret\&amp;quot;}],\&amp;quot;livenessProbe\&amp;quot;:{\&amp;quot;httpGet\&amp;quot;:{\&amp;quot;path\&amp;quot;:\&amp;quot;/healthz\&amp;quot;,\&amp;quot;port\&amp;quot;:8000},\&amp;quot;timeoutSeconds\&amp;quot;:5,\&amp;quot;periodSeconds\&amp;quot;:10},\&amp;quot;readinessProbe\&amp;quot;:{\&amp;quot;httpGet\&amp;quot;:{\&amp;quot;path\&amp;quot;:\&amp;quot;/readyz\&amp;quot;,\&amp;quot;port\&amp;quot;:8000},\&amp;quot;timeoutSeconds\&amp;quot;:5,\&amp;quot;periodSeconds\&amp;quot;:10},\&amp;quot;startupProbe\&amp;quot;:{\&amp;quot;httpGet\&amp;quot;:{\&amp;quot;path\&amp;quot;:\&amp;quot;/healthz\&amp;quot;,\&amp;quot;port\&amp;quot;:8000},\&amp;quot;timeoutSeconds\&amp;quot;:5,\&amp;quot;periodSeconds\&amp;quot;:10,\&amp;quot;failureThreshold\&amp;quot;:360},\&amp;quot;securityContext\&amp;quot;:{\&amp;quot;capabilities\&amp;quot;:{\&amp;quot;drop\&amp;quot;:[\&amp;quot;ALL\&amp;quot;]},\&amp;quot;privileged\&amp;quot;:false,\&amp;quot;runAsNonRoot\&amp;quot;:true,\&amp;quot;readOnlyRootFilesystem\&amp;quot;:true,\&amp;quot;allowPrivilegeEscalation\&amp;quot;:false,\&amp;quot;seccompProfile\&amp;quot;:{\&amp;quot;type\&amp;quot;:\&amp;quot;RuntimeDefault\&amp;quot;}}}],\&amp;quot;terminationGracePeriodSeconds\&amp;quot;:1800,\&amp;quot;serviceAccountName\&amp;quot;:\&amp;quot;postgres\&amp;quot;,\&amp;quot;securityContext\&amp;quot;:{\&amp;quot;runAsUser\&amp;quot;:26,\&amp;quot;runAsGroup\&amp;quot;:26,\&amp;quot;runAsNonRoot\&amp;quot;:true,\&amp;quot;fsGroup\&amp;quot;:26,\&amp;quot;seccompProfile\&amp;quot;:{\&amp;quot;type\&amp;quot;:\&amp;quot;RuntimeDefault\&amp;quot;}},\&amp;quot;hostname\&amp;quot;:\&amp;quot;postgres-1\&amp;quot;,\&amp;quot;affinity\&amp;quot;:{\&amp;quot;podAntiAffinity\&amp;quot;:{\&amp;quot;preferredDuringSchedulingIgnoredDuringExecution\&amp;quot;:[{\&amp;quot;weight\&amp;quot;:100,\&amp;quot;podAffinityTerm\&amp;quot;:{\&amp;quot;labelSelector\&amp;quot;:{\&amp;quot;matchExpressions\&amp;quot;:[{\&amp;quot;key\&amp;quot;:\&amp;quot;cnpg.io/cluster\&amp;quot;,\&amp;quot;operator\&amp;quot;:\&amp;quot;In\&amp;quot;,\&amp;quot;values\&amp;quot;:[\&amp;quot;postgres\&amp;quot;]}]},\&amp;quot;topologyKey\&amp;quot;:\&amp;quot;kubernetes.io/hostname\&amp;quot;}}]}}}&amp;quot;&#xA;        },&#xA;        &amp;quot;creationTimestamp&amp;quot;: &amp;quot;2023-12-18T07:40:02Z&amp;quot;,&#xA;        &amp;quot;labels&amp;quot;: {&#xA;            &amp;quot;cnpg.io/cluster&amp;quot;: &amp;quot;postgres&amp;quot;,&#xA;            &amp;quot;cnpg.io/instanceName&amp;quot;: &amp;quot;postgres-1&amp;quot;,&#xA;            &amp;quot;cnpg.io/instanceRole&amp;quot;: &amp;quot;primary&amp;quot;,&#xA;            &amp;quot;cnpg.io/podRole&amp;quot;: &amp;quot;instance&amp;quot;,&#xA;            &amp;quot;role&amp;quot;: &amp;quot;primary&amp;quot;&#xA;        },&#xA;        &amp;quot;name&amp;quot;: &amp;quot;postgres-1&amp;quot;,&#xA;        &amp;quot;namespace&amp;quot;: &amp;quot;shane&amp;quot;,&#xA;        &amp;quot;ownerReferences&amp;quot;: [&#xA;            {&#xA;                &amp;quot;apiVersion&amp;quot;: &amp;quot;postgresql.cnpg.io/v1&amp;quot;,&#xA;                &amp;quot;blockOwnerDeletion&amp;quot;: true,&#xA;                &amp;quot;controller&amp;quot;: true,&#xA;                &amp;quot;kind&amp;quot;: &amp;quot;Cluster&amp;quot;,&#xA;                &amp;quot;name&amp;quot;: &amp;quot;postgres&amp;quot;,&#xA;                &amp;quot;uid&amp;quot;: &amp;quot;261148e6-26ac-4d58-8c1e-b248f188aae1&amp;quot;&#xA;            }&#xA;        ],&#xA;        &amp;quot;resourceVersion&amp;quot;: &amp;quot;9936&amp;quot;,&#xA;        &amp;quot;uid&amp;quot;: &amp;quot;1a003c2f-ee83-4d25-af0e-cf192495a235&amp;quot;&#xA;    },&#xA;    &amp;quot;spec&amp;quot;: {&#xA;        &amp;quot;affinity&amp;quot;: {&#xA;            &amp;quot;podAntiAffinity&amp;quot;: {&#xA;                &amp;quot;preferredDuringSchedulingIgnoredDuringExecution&amp;quot;: [&#xA;                    {&#xA;                        &amp;quot;podAffinityTerm&amp;quot;: {&#xA;                            &amp;quot;labelSelector&amp;quot;: {&#xA;                                &amp;quot;matchExpressions&amp;quot;: [&#xA;                                    {&#xA;                                        &amp;quot;key&amp;quot;: &amp;quot;cnpg.io/cluster&amp;quot;,&#xA;                                        &amp;quot;operator&amp;quot;: &amp;quot;In&amp;quot;,&#xA;                                        &amp;quot;values&amp;quot;: [&#xA;                                            &amp;quot;postgres&amp;quot;&#xA;                                        ]&#xA;                                    }&#xA;                                ]&#xA;                            },&#xA;                            &amp;quot;topologyKey&amp;quot;: &amp;quot;kubernetes.io/hostname&amp;quot;&#xA;                        },&#xA;                        &amp;quot;weight&amp;quot;: 100&#xA;                    }&#xA;                ]&#xA;            }&#xA;        },&#xA;        &amp;quot;containers&amp;quot;: [&#xA;            {&#xA;                &amp;quot;command&amp;quot;: [&#xA;                    &amp;quot;/controller/manager&amp;quot;,&#xA;                    &amp;quot;instance&amp;quot;,&#xA;                    &amp;quot;run&amp;quot;,&#xA;                    &amp;quot;--log-level=info&amp;quot;&#xA;                ],&#xA;                &amp;quot;env&amp;quot;: [&#xA;                    {&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;PGDATA&amp;quot;,&#xA;                        &amp;quot;value&amp;quot;: &amp;quot;/var/lib/postgresql/data/pgdata&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;POD_NAME&amp;quot;,&#xA;                        &amp;quot;value&amp;quot;: &amp;quot;postgres-1&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;NAMESPACE&amp;quot;,&#xA;                        &amp;quot;value&amp;quot;: &amp;quot;shane&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;CLUSTER_NAME&amp;quot;,&#xA;                        &amp;quot;value&amp;quot;: &amp;quot;postgres&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;PGPORT&amp;quot;,&#xA;                        &amp;quot;value&amp;quot;: &amp;quot;5432&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;PGHOST&amp;quot;,&#xA;                        &amp;quot;value&amp;quot;: &amp;quot;/controller/run&amp;quot;&#xA;                    }&#xA;                ],&#xA;                &amp;quot;image&amp;quot;: &amp;quot;ghcr.io/cloudnative-pg/postgresql:16.0&amp;quot;,&#xA;                &amp;quot;imagePullPolicy&amp;quot;: &amp;quot;IfNotPresent&amp;quot;,&#xA;                &amp;quot;livenessProbe&amp;quot;: {&#xA;                    &amp;quot;failureThreshold&amp;quot;: 3,&#xA;                    &amp;quot;httpGet&amp;quot;: {&#xA;                        &amp;quot;path&amp;quot;: &amp;quot;/healthz&amp;quot;,&#xA;                        &amp;quot;port&amp;quot;: 8000,&#xA;                        &amp;quot;scheme&amp;quot;: &amp;quot;HTTP&amp;quot;&#xA;                    },&#xA;                    &amp;quot;periodSeconds&amp;quot;: 10,&#xA;                    &amp;quot;successThreshold&amp;quot;: 1,&#xA;                    &amp;quot;timeoutSeconds&amp;quot;: 5&#xA;                },&#xA;                &amp;quot;name&amp;quot;: &amp;quot;postgres&amp;quot;,&#xA;                &amp;quot;ports&amp;quot;: [&#xA;                    {&#xA;                        &amp;quot;containerPort&amp;quot;: 5432,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;postgresql&amp;quot;,&#xA;                        &amp;quot;protocol&amp;quot;: &amp;quot;TCP&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;containerPort&amp;quot;: 9187,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;metrics&amp;quot;,&#xA;                        &amp;quot;protocol&amp;quot;: &amp;quot;TCP&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;containerPort&amp;quot;: 8000,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;status&amp;quot;,&#xA;                        &amp;quot;protocol&amp;quot;: &amp;quot;TCP&amp;quot;&#xA;                    }&#xA;                ],&#xA;                &amp;quot;readinessProbe&amp;quot;: {&#xA;                    &amp;quot;failureThreshold&amp;quot;: 3,&#xA;                    &amp;quot;httpGet&amp;quot;: {&#xA;                        &amp;quot;path&amp;quot;: &amp;quot;/readyz&amp;quot;,&#xA;                        &amp;quot;port&amp;quot;: 8000,&#xA;                        &amp;quot;scheme&amp;quot;: &amp;quot;HTTP&amp;quot;&#xA;                    },&#xA;                    &amp;quot;periodSeconds&amp;quot;: 10,&#xA;                    &amp;quot;successThreshold&amp;quot;: 1,&#xA;                    &amp;quot;timeoutSeconds&amp;quot;: 5&#xA;                },&#xA;                &amp;quot;resources&amp;quot;: {},&#xA;                &amp;quot;securityContext&amp;quot;: {&#xA;                    &amp;quot;allowPrivilegeEscalation&amp;quot;: false,&#xA;                    &amp;quot;capabilities&amp;quot;: {&#xA;                        &amp;quot;drop&amp;quot;: [&#xA;                            &amp;quot;ALL&amp;quot;&#xA;                        ]&#xA;                    },&#xA;                    &amp;quot;privileged&amp;quot;: false,&#xA;                    &amp;quot;readOnlyRootFilesystem&amp;quot;: true,&#xA;                    &amp;quot;runAsNonRoot&amp;quot;: true,&#xA;                    &amp;quot;seccompProfile&amp;quot;: {&#xA;                        &amp;quot;type&amp;quot;: &amp;quot;RuntimeDefault&amp;quot;&#xA;                    }&#xA;                },&#xA;                &amp;quot;startupProbe&amp;quot;: {&#xA;                    &amp;quot;failureThreshold&amp;quot;: 360,&#xA;                    &amp;quot;httpGet&amp;quot;: {&#xA;                        &amp;quot;path&amp;quot;: &amp;quot;/healthz&amp;quot;,&#xA;                        &amp;quot;port&amp;quot;: 8000,&#xA;                        &amp;quot;scheme&amp;quot;: &amp;quot;HTTP&amp;quot;&#xA;                    },&#xA;                    &amp;quot;periodSeconds&amp;quot;: 10,&#xA;                    &amp;quot;successThreshold&amp;quot;: 1,&#xA;                    &amp;quot;timeoutSeconds&amp;quot;: 5&#xA;                },&#xA;                &amp;quot;terminationMessagePath&amp;quot;: &amp;quot;/dev/termination-log&amp;quot;,&#xA;                &amp;quot;terminationMessagePolicy&amp;quot;: &amp;quot;File&amp;quot;,&#xA;                &amp;quot;volumeMounts&amp;quot;: [&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/var/lib/postgresql/data&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;pgdata&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/run&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;scratch-data&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/controller&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;scratch-data&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/dev/shm&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;shm&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/etc/app-secret&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;app-secret&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/var/run/secrets/kubernetes.io/serviceaccount&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;kube-api-access-s9n8l&amp;quot;,&#xA;                        &amp;quot;readOnly&amp;quot;: true&#xA;                    }&#xA;                ]&#xA;            }&#xA;        ],&#xA;        &amp;quot;dnsPolicy&amp;quot;: &amp;quot;ClusterFirst&amp;quot;,&#xA;        &amp;quot;enableServiceLinks&amp;quot;: true,&#xA;        &amp;quot;hostname&amp;quot;: &amp;quot;postgres-1&amp;quot;,&#xA;        &amp;quot;initContainers&amp;quot;: [&#xA;            {&#xA;                &amp;quot;command&amp;quot;: [&#xA;                    &amp;quot;/manager&amp;quot;,&#xA;                    &amp;quot;bootstrap&amp;quot;,&#xA;                    &amp;quot;/controller/manager&amp;quot;,&#xA;                    &amp;quot;--log-level=info&amp;quot;&#xA;                ],&#xA;                &amp;quot;image&amp;quot;: &amp;quot;ghcr.io/cloudnative-pg/cloudnative-pg:1.21.0&amp;quot;,&#xA;                &amp;quot;imagePullPolicy&amp;quot;: &amp;quot;IfNotPresent&amp;quot;,&#xA;                &amp;quot;name&amp;quot;: &amp;quot;bootstrap-controller&amp;quot;,&#xA;                &amp;quot;resources&amp;quot;: {},&#xA;                &amp;quot;securityContext&amp;quot;: {&#xA;                    &amp;quot;allowPrivilegeEscalation&amp;quot;: false,&#xA;                    &amp;quot;capabilities&amp;quot;: {&#xA;                        &amp;quot;drop&amp;quot;: [&#xA;                            &amp;quot;ALL&amp;quot;&#xA;                        ]&#xA;                    },&#xA;                    &amp;quot;privileged&amp;quot;: false,&#xA;                    &amp;quot;readOnlyRootFilesystem&amp;quot;: true,&#xA;                    &amp;quot;runAsNonRoot&amp;quot;: true,&#xA;                    &amp;quot;seccompProfile&amp;quot;: {&#xA;                        &amp;quot;type&amp;quot;: &amp;quot;RuntimeDefault&amp;quot;&#xA;                    }&#xA;                },&#xA;                &amp;quot;terminationMessagePath&amp;quot;: &amp;quot;/dev/termination-log&amp;quot;,&#xA;                &amp;quot;terminationMessagePolicy&amp;quot;: &amp;quot;File&amp;quot;,&#xA;                &amp;quot;volumeMounts&amp;quot;: [&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/var/lib/postgresql/data&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;pgdata&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/run&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;scratch-data&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/controller&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;scratch-data&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/dev/shm&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;shm&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/etc/app-secret&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;app-secret&amp;quot;&#xA;                    },&#xA;                    {&#xA;                        &amp;quot;mountPath&amp;quot;: &amp;quot;/var/run/secrets/kubernetes.io/serviceaccount&amp;quot;,&#xA;                        &amp;quot;name&amp;quot;: &amp;quot;kube-api-access-s9n8l&amp;quot;,&#xA;                        &amp;quot;readOnly&amp;quot;: true&#xA;                    }&#xA;                ]&#xA;            }&#xA;        ],&#xA;        &amp;quot;nodeName&amp;quot;: &amp;quot;minikube&amp;quot;,&#xA;        &amp;quot;preemptionPolicy&amp;quot;: &amp;quot;PreemptLowerPriority&amp;quot;,&#xA;        &amp;quot;priority&amp;quot;: 0,&#xA;        &amp;quot;restartPolicy&amp;quot;: &amp;quot;Always&amp;quot;,&#xA;        &amp;quot;schedulerName&amp;quot;: &amp;quot;default-scheduler&amp;quot;,&#xA;        &amp;quot;securityContext&amp;quot;: {&#xA;            &amp;quot;fsGroup&amp;quot;: 26,&#xA;            &amp;quot;runAsGroup&amp;quot;: 26,&#xA;            &amp;quot;runAsNonRoot&amp;quot;: true,&#xA;            &amp;quot;runAsUser&amp;quot;: 26,&#xA;            &amp;quot;seccompProfile&amp;quot;: {&#xA;                &amp;quot;type&amp;quot;: &amp;quot;RuntimeDefault&amp;quot;&#xA;            }&#xA;        },&#xA;        &amp;quot;serviceAccount&amp;quot;: &amp;quot;postgres&amp;quot;,&#xA;        &amp;quot;serviceAccountName&amp;quot;: &amp;quot;postgres&amp;quot;,&#xA;        &amp;quot;terminationGracePeriodSeconds&amp;quot;: 1800,&#xA;        &amp;quot;tolerations&amp;quot;: [&#xA;            {&#xA;                &amp;quot;effect&amp;quot;: &amp;quot;NoExecute&amp;quot;,&#xA;                &amp;quot;key&amp;quot;: &amp;quot;node.kubernetes.io/not-ready&amp;quot;,&#xA;                &amp;quot;operator&amp;quot;: &amp;quot;Exists&amp;quot;,&#xA;                &amp;quot;tolerationSeconds&amp;quot;: 300&#xA;            },&#xA;            {&#xA;                &amp;quot;effect&amp;quot;: &amp;quot;NoExecute&amp;quot;,&#xA;                &amp;quot;key&amp;quot;: &amp;quot;node.kubernetes.io/unreachable&amp;quot;,&#xA;                &amp;quot;operator&amp;quot;: &amp;quot;Exists&amp;quot;,&#xA;                &amp;quot;tolerationSeconds&amp;quot;: 300&#xA;            }&#xA;        ],&#xA;        &amp;quot;volumes&amp;quot;: [&#xA;            {&#xA;                &amp;quot;name&amp;quot;: &amp;quot;pgdata&amp;quot;,&#xA;                &amp;quot;persistentVolumeClaim&amp;quot;: {&#xA;                    &amp;quot;claimName&amp;quot;: &amp;quot;postgres-1&amp;quot;&#xA;                }&#xA;            },&#xA;            {&#xA;                &amp;quot;emptyDir&amp;quot;: {},&#xA;                &amp;quot;name&amp;quot;: &amp;quot;scratch-data&amp;quot;&#xA;            },&#xA;            {&#xA;                &amp;quot;emptyDir&amp;quot;: {&#xA;                    &amp;quot;medium&amp;quot;: &amp;quot;Memory&amp;quot;&#xA;                },&#xA;                &amp;quot;name&amp;quot;: &amp;quot;shm&amp;quot;&#xA;            },&#xA;            {&#xA;                &amp;quot;name&amp;quot;: &amp;quot;app-secret&amp;quot;,&#xA;                &amp;quot;secret&amp;quot;: {&#xA;                    &amp;quot;defaultMode&amp;quot;: 420,&#xA;                    &amp;quot;secretName&amp;quot;: &amp;quot;account-secret&amp;quot;&#xA;                }&#xA;            },&#xA;            {&#xA;                &amp;quot;name&amp;quot;: &amp;quot;kube-api-access-s9n8l&amp;quot;,&#xA;                &amp;quot;projected&amp;quot;: {&#xA;                    &amp;quot;defaultMode&amp;quot;: 420,&#xA;                    &amp;quot;sources&amp;quot;: [&#xA;                        {&#xA;                            &amp;quot;serviceAccountToken&amp;quot;: {&#xA;                                &amp;quot;expirationSeconds&amp;quot;: 3607,&#xA;                                &amp;quot;path&amp;quot;: &amp;quot;token&amp;quot;&#xA;                            }&#xA;                        },&#xA;                        {&#xA;                            &amp;quot;configMap&amp;quot;: {&#xA;                                &amp;quot;items&amp;quot;: [&#xA;                                    {&#xA;                                        &amp;quot;key&amp;quot;: &amp;quot;ca.crt&amp;quot;,&#xA;                                        &amp;quot;path&amp;quot;: &amp;quot;ca.crt&amp;quot;&#xA;                                    }&#xA;                                ],&#xA;                                &amp;quot;name&amp;quot;: &amp;quot;kube-root-ca.crt&amp;quot;&#xA;                            }&#xA;                        },&#xA;                        {&#xA;                            &amp;quot;downwardAPI&amp;quot;: {&#xA;                                &amp;quot;items&amp;quot;: [&#xA;                                    {&#xA;                                        &amp;quot;fieldRef&amp;quot;: {&#xA;                                            &amp;quot;apiVersion&amp;quot;: &amp;quot;v1&amp;quot;,&#xA;                                            &amp;quot;fieldPath&amp;quot;: &amp;quot;metadata.namespace&amp;quot;&#xA;                                        },&#xA;                                        &amp;quot;path&amp;quot;: &amp;quot;namespace&amp;quot;&#xA;                                    }&#xA;                                ]&#xA;                            }&#xA;                        }&#xA;                    ]&#xA;                }&#xA;            }&#xA;        ]&#xA;    },&#xA;    &amp;quot;status&amp;quot;: {&#xA;        &amp;quot;conditions&amp;quot;: [&#xA;            {&#xA;                &amp;quot;lastProbeTime&amp;quot;: null,&#xA;                &amp;quot;lastTransitionTime&amp;quot;: &amp;quot;2023-12-18T07:40:08Z&amp;quot;,&#xA;                &amp;quot;status&amp;quot;: &amp;quot;True&amp;quot;,&#xA;                &amp;quot;type&amp;quot;: &amp;quot;Initialized&amp;quot;&#xA;            },&#xA;            {&#xA;                &amp;quot;lastProbeTime&amp;quot;: null,&#xA;                &amp;quot;lastTransitionTime&amp;quot;: &amp;quot;2023-12-18T07:40:22Z&amp;quot;,&#xA;                &amp;quot;status&amp;quot;: &amp;quot;True&amp;quot;,&#xA;                &amp;quot;type&amp;quot;: &amp;quot;Ready&amp;quot;&#xA;            },&#xA;            {&#xA;                &amp;quot;lastProbeTime&amp;quot;: null,&#xA;                &amp;quot;lastTransitionTime&amp;quot;: &amp;quot;2023-12-18T07:40:22Z&amp;quot;,&#xA;                &amp;quot;status&amp;quot;: &amp;quot;True&amp;quot;,&#xA;                &amp;quot;type&amp;quot;: &amp;quot;ContainersReady&amp;quot;&#xA;            },&#xA;            {&#xA;                &amp;quot;lastProbeTime&amp;quot;: null,&#xA;                &amp;quot;lastTransitionTime&amp;quot;: &amp;quot;2023-12-18T07:40:02Z&amp;quot;,&#xA;                &amp;quot;status&amp;quot;: &amp;quot;True&amp;quot;,&#xA;                &amp;quot;type&amp;quot;: &amp;quot;PodScheduled&amp;quot;&#xA;            }&#xA;        ],&#xA;        &amp;quot;containerStatuses&amp;quot;: [&#xA;            {&#xA;                &amp;quot;containerID&amp;quot;: &amp;quot;docker://56603914ff01f3b781174deac8e7d25fae9e5bd764139eea547715d0c1690351&amp;quot;,&#xA;                &amp;quot;image&amp;quot;: &amp;quot;ghcr.io/cloudnative-pg/postgresql:16.0&amp;quot;,&#xA;                &amp;quot;imageID&amp;quot;: &amp;quot;docker-pullable://ghcr.io/cloudnative-pg/postgresql@sha256:5baf99e0fb30a051d8e108eeecb558af65a7119e068a280641873a2b98e35457&amp;quot;,&#xA;                &amp;quot;lastState&amp;quot;: {},&#xA;                &amp;quot;name&amp;quot;: &amp;quot;postgres&amp;quot;,&#xA;                &amp;quot;ready&amp;quot;: true,&#xA;                &amp;quot;restartCount&amp;quot;: 0,&#xA;                &amp;quot;started&amp;quot;: true,&#xA;                &amp;quot;state&amp;quot;: {&#xA;                    &amp;quot;running&amp;quot;: {&#xA;                        &amp;quot;startedAt&amp;quot;: &amp;quot;2023-12-18T07:40:10Z&amp;quot;&#xA;                    }&#xA;                }&#xA;            }&#xA;        ],&#xA;        &amp;quot;hostIP&amp;quot;: &amp;quot;192.168.49.2&amp;quot;,&#xA;        &amp;quot;initContainerStatuses&amp;quot;: [&#xA;            {&#xA;                &amp;quot;containerID&amp;quot;: &amp;quot;docker://70792f2a6c3f5f40424d2a1bb9818d201a768d4c8f63935e80be1bbe700838ff&amp;quot;,&#xA;                &amp;quot;image&amp;quot;: &amp;quot;ghcr.io/cloudnative-pg/cloudnative-pg:1.21.0&amp;quot;,&#xA;                &amp;quot;imageID&amp;quot;: &amp;quot;docker-pullable://ghcr.io/cloudnative-pg/cloudnative-pg@sha256:44f6132d5335528417427ea12634c5e6c96371e25e00dc898c3b6d091a279c21&amp;quot;,&#xA;                &amp;quot;lastState&amp;quot;: {},&#xA;                &amp;quot;name&amp;quot;: &amp;quot;bootstrap-controller&amp;quot;,&#xA;                &amp;quot;ready&amp;quot;: true,&#xA;                &amp;quot;restartCount&amp;quot;: 0,&#xA;                &amp;quot;started&amp;quot;: false,&#xA;                &amp;quot;state&amp;quot;: {&#xA;                    &amp;quot;terminated&amp;quot;: {&#xA;                        &amp;quot;containerID&amp;quot;: &amp;quot;docker://70792f2a6c3f5f40424d2a1bb9818d201a768d4c8f63935e80be1bbe700838ff&amp;quot;,&#xA;                        &amp;quot;exitCode&amp;quot;: 0,&#xA;                        &amp;quot;finishedAt&amp;quot;: &amp;quot;2023-12-18T07:40:06Z&amp;quot;,&#xA;                        &amp;quot;reason&amp;quot;: &amp;quot;Completed&amp;quot;,&#xA;                        &amp;quot;startedAt&amp;quot;: &amp;quot;2023-12-18T07:40:06Z&amp;quot;&#xA;                    }&#xA;                }&#xA;            }&#xA;        ],&#xA;        &amp;quot;phase&amp;quot;: &amp;quot;Running&amp;quot;,&#xA;        &amp;quot;podIP&amp;quot;: &amp;quot;10.244.0.18&amp;quot;,&#xA;        &amp;quot;podIPs&amp;quot;: [&#xA;            {&#xA;                &amp;quot;ip&amp;quot;: &amp;quot;10.244.0.18&amp;quot;&#xA;            }&#xA;        ],&#xA;        &amp;quot;qosClass&amp;quot;: &amp;quot;BestEffort&amp;quot;,&#xA;        &amp;quot;startTime&amp;quot;: &amp;quot;2023-12-18T07:40:02Z&amp;quot;&#xA;    }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;We can use almost anything in that, let&amp;rsquo;s say when the pod is assigned an IP. We can see the path is &lt;code&gt;status.podIPs&lt;/code&gt; and it&amp;rsquo;s an array, with the field inside each &lt;code&gt;podIP&lt;/code&gt; being &lt;code&gt;ip&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl wait --for jsonpath=&#39;{.status.podIPs[0].ip}&#39; --timeout=5s pod/postgres-1 -n shane&#xA;pod/postgres-1 condition met&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Simple :)&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Summary&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;Just a quick note for using what&amp;rsquo;s a &lt;strong&gt;very&lt;/strong&gt; helpful feature of &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;wait&lt;/code&gt;, which is now firmly in my toolkit.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">13836228-428f-4b95-85b5-e94f84462b79</guid>
      <pubDate>Mon, 18 Dec 2023 08:10:18 GMT</pubDate>
    </item>
    <item>
      <title>Troubleshooting your Kubernetes Cluster</title>
      <link>http://shanehowearth.com/troubleshooting-your-kubernetes-cluster</link>
      <description>&lt;p&gt;When faced with a cluster that&amp;rsquo;s not doing what you expect it do, what should you do to find out why it&amp;rsquo;s not behaving?&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Assuming that you have (tried to) configured your cluster to be something in the order of &amp;ldquo;Ingress listens for traffic on port 80, meant for domain name example.com&amp;rdquo; then passes that traffic to a service that passes the traffic to a pod which is your container, then the first job is to check that the ingress is receiving the traffic intended for the domain.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Start by looking at all the pods&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl get pods --all-namespaces&#xA;NAMESPACE       NAME                                        READY   STATUS      RESTARTS        AGE&#xA;ingress-nginx   ingress-nginx-admission-create-ghh8z        0/1     Completed   0               23h&#xA;ingress-nginx   ingress-nginx-admission-patch-pxf2q         0/1     Completed   0               23h&#xA;ingress-nginx   ingress-nginx-controller-7c6974c4d8-2x5vq   1/1     Running     1 (5h39m ago)   23h&#xA;kube-system     coredns-5dd5756b68-rn4td                    1/1     Running     1 (19h ago)     30h&#xA;kube-system     etcd-minikube                               1/1     Running     1 (19h ago)     30h&#xA;kube-system     kube-apiserver-minikube                     1/1     Running     1 (5h39m ago)   30h&#xA;kube-system     kube-controller-manager-minikube            1/1     Running     1 (19h ago)     30h&#xA;kube-system     kube-proxy-kv2xd                            1/1     Running     1 (19h ago)     30h&#xA;kube-system     kube-scheduler-minikube                     1/1     Running     1 (19h ago)     30h&#xA;kube-system     storage-provisioner                         1/1     Running     3 (5h38m ago)   30h&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Then check the status of the pods in your &lt;code&gt;namespace&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl get pods -n shane&#xA;NAME                             READY   STATUS              RESTARTS     AGE&#xA;accountserver-56b469dbbd-9q492   0/1     ErrImageNeverPull   0            12h&#xA;apiserver-6687c679f-tzgsw        1/1     Running             1 (9h ago)   14h&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;We can see in that snippet that there&amp;rsquo;s a problem with the &lt;code&gt;accountserver&lt;/code&gt; image, but the &lt;code&gt;apiserver&lt;/code&gt; is claiming to be happy.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Assuming that we&amp;rsquo;re checking the &lt;code&gt;apiserver&lt;/code&gt; we continue -&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Check that the ingress is receiving traffic (requires two shells, one to send the command, one to log what&amp;rsquo;s happening)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The logging shell:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl logs -n ingress-nginx ingress-nginx-controller-7c6974c4d8-2x5vq -f&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The command shell:&#xA;First I want the address that the ingress has&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl get ingress -n shane&#xA;NAME    CLASS    HOSTS       ADDRESS        PORTS   AGE&#xA;shane   &amp;lt;none&amp;gt;   example.com   192.168.58.2   80      3h15m&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Test if that IP is receiving traffic&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;wget 192.168.58.2&#xA;--2023-12-04 15:17:53--  http://192.168.58.2/&#xA;Connecting to 192.168.58.2:80... connected.&#xA;HTTP request sent, awaiting response... 404 Not Found&#xA;2023-12-04 15:17:53 ERROR 404: Not Found.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Well, &lt;code&gt;wget&lt;/code&gt; reports connecting ok, but then it got a &lt;code&gt;404&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;So, let&amp;rsquo;s try with the domain name (For the record, I set the IP of the domain I want to pretend I have in /etc/hosts)&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt; $ wget example.com&#xA;--2023-12-04 15:27:03--  http://example.com/&#xA;Resolving example.com (example.com)... 192.168.58.2&#xA;Connecting to example.com (example.com)|192.168.58.2|:80... connected.&#xA;HTTP request sent, awaiting response... 502 Bad Gateway&#xA;2023-12-04 15:27:03 ERROR 502: Bad Gateway.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;This tells us that the &lt;code&gt;ingress&lt;/code&gt; is listening, and will only respond to requests on &lt;code&gt;example.com&lt;/code&gt; which is fine. But it&amp;rsquo;s returning a &lt;code&gt;502&lt;/code&gt; error, let&amp;rsquo;s see what the logs say (the other shell)&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;2023/12/04 04:27:03 [error] 577#577: *342388 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.58.1, server: example.com, request: &amp;quot;GET / HTTP/1.1&amp;quot;, upstream: &amp;quot;http://10.244.0.11:8080/&amp;quot;, host: &amp;quot;example.com&amp;quot;&#xA;2023/12/04 04:27:03 [error] 577#577: *342388 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.58.1, server: example.com, request: &amp;quot;GET / HTTP/1.1&amp;quot;, upstream: &amp;quot;http://10.244.0.11:8080/&amp;quot;, host: &amp;quot;example.com&amp;quot;&#xA;2023/12/04 04:27:03 [error] 577#577: *342388 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.58.1, server: example.com, request: &amp;quot;GET / HTTP/1.1&amp;quot;, upstream: &amp;quot;http://10.244.0.11:8080/&amp;quot;, host: &amp;quot;example.com&amp;quot;&#xA;192.168.58.1 - - [04/Dec/2023:04:27:03 +0000] &amp;quot;GET / HTTP/1.1&amp;quot; 502 150 &amp;quot;-&amp;quot; &amp;quot;Wget/1.21.2&amp;quot; 124 0.001 [example-api-service-8080] [] 10.244.0.11:8080, 10.244.0.11:8080, 10.244.0.11:8080 0, 0, 0 0.001, 0.000, 0.000 502, 502, 502 3cee74a2a4e97297f6ef379689bda677&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The &lt;code&gt;ingress&lt;/code&gt; is telling us that it the connection attempt upstream was refused.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;We now turn our attention to the pod that the ingress is trying to connect to.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;What have the logs been for &lt;code&gt;apiserver&lt;/code&gt;?&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ kubectl logs apiserver-86c595ccfc-lpd2x -n shane&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Ooo, empty, that&amp;rsquo;s curious.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;From here it&amp;rsquo;s time to look at the configuration in kubernetes as well as in the container. It&amp;rsquo;s clear that kubernetes has directed traffic from the &lt;code&gt;ingress&lt;/code&gt; to the &lt;code&gt;accountserver&lt;/code&gt; service, but it&amp;rsquo;s not been accepted.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Upon inspection I found that the port configured in the &lt;code&gt;service&lt;/code&gt; definition did not match the port configured in the container - so I have made a permanent fix, using an environment variable passed from the service to the container to define the port, meaning that the two will (from hence forth) have the same value for port number.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">03208574-b31b-4c5a-96d8-4d4a9d3504f2</guid>
      <pubDate>Mon, 04 Dec 2023 20:03:22 GMT</pubDate>
    </item>
    <item>
      <title>Observable systems</title>
      <link>http://shanehowearth.com/observable-systems</link>
      <description>&lt;p&gt;This post is mainly about instrumenting tracing in distributed systems with &lt;a href=&#34;https://opentelemetry.io/&#34;&gt;OpenTelemetry&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Before we get to that we need to understand what constitutes an observable system. Observable systems have three &amp;lsquo;pillars&amp;rsquo; of observability:&lt;/p&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Logs&lt;/li&gt;&#xA;&lt;li&gt;Traces&lt;/li&gt;&#xA;&lt;li&gt;Metrics&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;p&gt;OpenTelemetry itself hasn&amp;rsquo;t got logging implemented, but it does have tracing and metric gathering capabilities (for Go).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;On to the interesting stuff :)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I have an &lt;code&gt;API&lt;/code&gt; container that has a RESTful interface for clients on the internet to call. The API container has a number of routes/endpoints that it handles by calling other containers via gRPC.&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;HTTP&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;I use &lt;a href=&#34;https://github.com/go-chi/chi&#34;&gt;Chi&lt;/a&gt; as my mux, and I use the &lt;a href=&#34;https://pkg.go.dev/go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux#Middleware&#34;&gt;Gorilla OpenTelemetry Middleware&lt;/a&gt;. Using the &lt;a href=&#34;https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/github.com/gorilla/mux/otelmux/example/v0.46.1/instrumentation/github.com/gorilla/mux/otelmux/example/server.go&#34;&gt;Server.go&lt;/a&gt; as an example I found it incredibly simple to instrument the HTTP server.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;First, a trace initialisation:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package main&#xA;&#xA;// required imports:&#xA;// &amp;quot;go.opentelemetry.io/otel&amp;quot;&#xA;//  stdout &amp;quot;go.opentelemetry.io/otel/exporters/stdout/stdouttrace&amp;quot;&#xA;// sdktrace &amp;quot;go.opentelemetry.io/otel/sdk/trace&amp;quot;&#xA;&#xA;var tracer = otel.Tracer(&amp;quot;api-server&amp;quot;)&#xA;&#xA;func initTracer() (*sdktrace.TracerProvider, error) {&#xA;        exporter, err := stdout.New(stdout.WithPrettyPrint())&#xA;        if err != nil {&#xA;                return nil, err&#xA;        }&#xA;&#xA;        tp := sdktrace.NewTracerProvider(&#xA;                sdktrace.WithSampler(sdktrace.AlwaysSample()),&#xA;                sdktrace.WithBatcher(exporter),&#xA;        )&#xA;        otel.SetTracerProvider(tp)&#xA;        otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))&#xA;&#xA;        return tp, nil&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Then tell our mux to use the middleware:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package main&#xA;&#xA;// required imports:&#xA;// &amp;quot;github.com/go-chi/chi/v5&amp;quot;&#xA;// &amp;quot;github.com/go-chi/chi/v5/middleware&amp;quot;&#xA;// &amp;quot;go.opentelemetry.io/contrib/instrumentation/github.com/gorilla/mux/otelmux&amp;quot;&#xA;// &amp;quot;time&amp;quot; (not necessary for OpenTelemetry, but used for another middleware).&#xA;&#xA;func createRouter() *chi.Mux {&#xA;        router := chi.NewRouter()&#xA;&#xA;        router.Use(&#xA;                otelmux.Middleware(&amp;quot;my-server&amp;quot;),&#xA;                // The rest are standard chi middleware (unnecessary for telemetry, but left in for example of other middleware).&#xA;                middleware.RequestID,&#xA;                middleware.RealIP,&#xA;                middleware.Logger,&#xA;                middleware.Recoverer,&#xA;                //nolint:gomnd&#xA;                middleware.Timeout(60*time.Second),&#xA;        )&#xA;&#xA;        // All routes are in routes.go&#xA;        OnepageRoutes(router)&#xA;&#xA;        return router&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And a simple main - the only OpenTelemetry related code is the call to the &lt;code&gt;initTracer&lt;/code&gt; function:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package main&#xA;&#xA;// required imports:&#xA;// &amp;quot;log&amp;quot;&#xA;// &amp;quot;context&amp;quot;&#xA;// &amp;quot;log/slog&amp;quot;&#xA;// &amp;quot;net/http&amp;quot;&#xA;// &amp;quot;time&amp;quot;&#xA;&#xA;func main() {&#xA;        tp, err := initTracer()&#xA;        if err != nil {&#xA;                log.Fatal(err)&#xA;        }&#xA;&#xA;        defer func() {&#xA;                if err := tp.Shutdown(context.Background()); err != nil {&#xA;                        log.Printf(&amp;quot;Error shutting down tracer provider: %v&amp;quot;, err)&#xA;                }&#xA;        }()&#xA;&#xA;        router := createRouter()&#xA;&#xA;        server := &amp;amp;http.Server{&#xA;                Addr: &amp;quot;:80&amp;quot;,&#xA;                ReadHeaderTimeout: 3 * time.Second,&#xA;                Handler:           router,&#xA;        }&#xA;&#xA;        if err := server.ListenAndServe(); err != nil {&#xA;                slog.Warn(&amp;quot;cannot listen and serve&amp;quot;, &amp;quot;error&amp;quot;, err.Error())&#xA;        }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The routes are untouched by the OpenTelemetry code, the next part is to ensure that the handlers are injecting traces, and spans, so that the call by a user can be &amp;lsquo;traced&amp;rsquo; across the systems.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I keep a tracer.go that ensures that the tracer is accessible across the package:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package api&#xA;&#xA;import &amp;quot;go.opentelemetry.io/otel&amp;quot;&#xA;&#xA;var tracer = otel.Tracer(&amp;quot;mux-server&amp;quot;)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And, an example of handler (all that is required for OpenTelemetry is the first two lines):&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package api&#xA;&#xA;...&#xA;&#xA;func GetSomething(response http.ResponseWriter, req *http.Request) {&#xA;        _, span := tracer.Start(context.Background(), &amp;quot;GetSomething&amp;quot;)&#xA;        defer span.End()&#xA;        ...&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s all that&amp;rsquo;s required for HTTP telemetry (tracing).&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;gRPC Server&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;gRPC initialisation on the server is a little more involved, allegedly there is an easier (less code) way to do this, but I couldn&amp;rsquo;t grasp how to enumerate which middleware I wanted to include.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;These examples are largely based on &lt;a href=&#34;https://github.com/grpc-ecosystem/go-grpc-middleware/tree/main/examples&#34;&gt;Go gRPC examples&lt;/a&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;server:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package main&#xA;&#xA;import (&#xA;        &amp;quot;bytes&amp;quot;&#xA;        &amp;quot;context&amp;quot;&#xA;        &amp;quot;fmt&amp;quot;&#xA;        &amp;quot;log&amp;quot;&#xA;        &amp;quot;log/slog&amp;quot;&#xA;        &amp;quot;net&amp;quot;&#xA;        &amp;quot;os&amp;quot;&#xA;        &amp;quot;runtime/debug&amp;quot;&#xA;&#xA;        &amp;quot;shanehowearth.com/stuff&amp;quot;&#xA;        grpcProto &amp;quot;shanehowearth.com/stuff/grpc/proto/v1&amp;quot;&#xA;&#xA;        grpcprom &amp;quot;github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus&amp;quot;&#xA;        &amp;quot;github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery&amp;quot;&#xA;        &amp;quot;github.com/prometheus/client_golang/prometheus&amp;quot;&#xA;        &amp;quot;github.com/prometheus/client_golang/prometheus/promauto&amp;quot;&#xA;        &amp;quot;go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc&amp;quot;&#xA;        &amp;quot;go.opentelemetry.io/otel&amp;quot;&#xA;        stdout &amp;quot;go.opentelemetry.io/otel/exporters/stdout/stdouttrace&amp;quot;&#xA;        &amp;quot;go.opentelemetry.io/otel/propagation&amp;quot;&#xA;        sdktrace &amp;quot;go.opentelemetry.io/otel/sdk/trace&amp;quot;&#xA;        &amp;quot;go.opentelemetry.io/otel/trace&amp;quot;&#xA;        &amp;quot;google.golang.org/grpc&amp;quot;&#xA;        &amp;quot;google.golang.org/grpc/codes&amp;quot;&#xA;        &amp;quot;google.golang.org/grpc/reflection&amp;quot;&#xA;        &amp;quot;google.golang.org/grpc/status&amp;quot;&#xA;)&#xA;&#xA;type server struct {&#xA;        grpcProto.UnimplementedAccountServiceServer&#xA;}&#xA;&#xA;// package global&#xA;var stuffServer *stuff.Server&#xA;&#xA;func main() {&#xA;        stuffServer = stuff.NewAccountServer()&#xA;&#xA;        // gRPC service&#xA;        portNumber, ok := os.LookupEnv(&amp;quot;PORT&amp;quot;)&#xA;        if !ok {&#xA;                log.Fatalf(&amp;quot;PORT not set in environment, cannot continue&amp;quot;)&#xA;        }&#xA;&#xA;        lis, err := net.Listen(&amp;quot;tcp&amp;quot;, &amp;quot;0.0.0.0:&amp;quot;+portNumber)&#xA;        if err != nil {&#xA;                log.Fatalf(&amp;quot;Failed to listen: %v&amp;quot;, err)&#xA;        }&#xA;&#xA;        // Setup metrics.&#xA;        srvMetrics := grpcprom.NewServerMetrics(&#xA;                grpcprom.WithServerHandlingTimeHistogram(&#xA;                        grpcprom.WithHistogramBuckets([]float64{0.001, 0.01, 0.1, 0.3, 0.6, 1, 3, 6, 9, 20, 30, 60, 90, 120}),&#xA;                ),&#xA;        )&#xA;&#xA;        reg := prometheus.NewRegistry()&#xA;        reg.MustRegister(srvMetrics)&#xA;&#xA;        exemplarFromContext := func(ctx context.Context) prometheus.Labels {&#xA;                if span := trace.SpanContextFromContext(ctx); span.IsSampled() {&#xA;                        return prometheus.Labels{&amp;quot;traceID&amp;quot;: span.TraceID().String()}&#xA;                }&#xA;&#xA;                return nil&#xA;        }&#xA;&#xA;        // Set up OTLP tracing (stdout for debug).&#xA;        exporter, err := stdout.New(stdout.WithPrettyPrint())&#xA;        if err != nil {&#xA;                slog.Error(&amp;quot;exporter setup&amp;quot;, &amp;quot;error&amp;quot;, err.Error())&#xA;                os.Exit(1)&#xA;        }&#xA;&#xA;        tp := sdktrace.NewTracerProvider(&#xA;                sdktrace.WithSampler(sdktrace.AlwaysSample()),&#xA;                sdktrace.WithBatcher(exporter),&#xA;        )&#xA;        otel.SetTracerProvider(tp)&#xA;&#xA;        otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))&#xA;&#xA;        defer func() { _ = exporter.Shutdown(context.Background()) }()&#xA;&#xA;        // Setup metric for panic recoveries.&#xA;        panicsTotal := promauto.With(reg).NewCounter(prometheus.CounterOpts{&#xA;                Name: &amp;quot;grpc_req_panics_recovered_total&amp;quot;,&#xA;                Help: &amp;quot;Total number of gRPC requests recovered from internal panic.&amp;quot;,&#xA;        })&#xA;&#xA;        grpcPanicRecoveryHandler := func(p any) error {&#xA;                panicsTotal.Inc()&#xA;                slog.Error(&amp;quot;msg&amp;quot;, &amp;quot;recovered from panic&amp;quot;, &amp;quot;panic&amp;quot;, p, &amp;quot;stack&amp;quot;, debug.Stack())&#xA;&#xA;                return status.Errorf(codes.Internal, &amp;quot;%s&amp;quot;, p)&#xA;        }&#xA;&#xA;        grpcServer := grpc.NewServer(&#xA;                grpc.StatsHandler(otelgrpc.NewServerHandler()),&#xA;                grpc.ChainUnaryInterceptor(&#xA;                        // Order matters e.g. tracing interceptor have to create&#xA;                        // span first for the later exemplars to work.&#xA;                        otelgrpc.UnaryServerInterceptor(),&#xA;                        srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),&#xA;                        recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),&#xA;                ),&#xA;                grpc.ChainStreamInterceptor(&#xA;                        otelgrpc.StreamServerInterceptor(),&#xA;                        srvMetrics.StreamServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),&#xA;                        recovery.StreamServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),&#xA;                ),&#xA;        )&#xA;        grpcProto.RegisterAccountServiceServer(grpcServer, &amp;amp;server{})&#xA;        reflection.Register(grpcServer)&#xA;&#xA;        if err := grpcServer.Serve(lis); err != nil {&#xA;                panic(fmt.Sprintf(&amp;quot;failed to serve: %v&amp;quot;, err))&#xA;        }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;again, I have a tracer.go that exposes the tracer to the whole package:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package main&#xA;&#xA;import  &amp;quot;go.opentelemetry.io/otel&amp;quot;&#xA;&#xA;var tracer = otel.Tracer(&amp;quot;accounts-grpc-server&amp;quot;)&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And the functions exposed by the gRPC server look like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;func (s *server) GetStuff(ctx context.Context) (*grpcProto.Stuff, error) {&#xA;        ctx, childSpan := tracer.Start(ctx, &amp;quot;Get Stuff&amp;quot;)&#xA;        defer childSpan.End()&#xA;        &#xA;        ...&#xA;&#xA;        return &amp;amp;grpcProto.Stuff{HtmlForm: &amp;quot;weee&amp;quot;}, nil&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h2&gt;gRPC Client&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;The only thing happening in the client connection is the inclusion of the &lt;code&gt;otel&lt;/code&gt; middleware&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;package stuff&#xA;&#xA;import (&#xA;        &amp;quot;context&amp;quot;&#xA;        &amp;quot;log&amp;quot;&#xA;        &amp;quot;log/slog&amp;quot;&#xA;        &amp;quot;time&amp;quot;&#xA;&#xA;        &amp;quot;go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc&amp;quot;&#xA;        &amp;quot;google.golang.org/grpc&amp;quot;&#xA;        &amp;quot;google.golang.org/grpc/credentials/insecure&amp;quot;&#xA;        proto &amp;quot;shanehowearth.com/stuff/grpc/proto/v1&amp;quot;&#xA;)&#xA;&#xA;type Client struct {&#xA;        Address string&#xA;}&#xA;&#xA;func (ac *Client) newConnection() (proto.StuffServiceClient, *grpc.ClientConn) {&#xA;        conn, err := grpc.Dial(&#xA;                ac.Address,&#xA;                grpc.WithTransportCredentials(insecure.NewCredentials()),&#xA;                grpc.WithStatsHandler(otelgrpc.NewClientHandler()),&#xA;        )&#xA;        if err != nil {&#xA;                log.Fatalf(&amp;quot;did not connect: %v&amp;quot;, err)&#xA;        }&#xA;&#xA;        return proto.NewStuffServiceClient(conn), conn&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And that&amp;rsquo;s it. I don&amp;rsquo;t include any traces in the client caller function, but there&amp;rsquo;s no reason that it cannot be done.&lt;/p&gt;&#xA;&#xA;&lt;h2&gt;Summary&lt;/h2&gt;&#xA;&#xA;&lt;p&gt;Turning a distributed system of microservices written in Go into an observable one with OpenTelemetry is a fairly simple affair, and these examples should mean you can do it too :)&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">d0b498c7-7161-4f96-89db-f81c2d1792d4</guid>
      <pubDate>Sat, 02 Dec 2023 04:34:07 GMT</pubDate>
    </item>
    <item>
      <title>Private repositories and Go</title>
      <link>http://shanehowearth.com/private-repositories-and-go</link>
      <description>&lt;p&gt;EDIT: I&amp;rsquo;ve discovered this morning that, in fact, the issue still exists. I suspect a bug in the VCS code for Go, and have raised an &lt;a href=&#34;https://github.com/golang/go/issues/64389&#34;&gt;issue&lt;/a&gt; (Although the issue &lt;del&gt;bot&lt;/del&gt; gnome has misread what was being said and claimed it was a discussion not an issue)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Almost every write up I have seen for working with private repositories and Go have taken the point of view that the repository is hosted on one of the major public Git repository holders (&lt;a href=&#34;https://github.com/&#34;&gt;Github&lt;/a&gt;, or &lt;a href=&#34;https://gitlab.com/gitlab-org/gitlab&#34;&gt;Gitlab&lt;/a&gt;). My problem is that I am hosting repositories on my own machines. I can use git without those websites ;).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I was having a lot of trouble with Go interacting with those self-hosted repositories however, which I thought was odd because Go uses Git under the hood for things like &lt;code&gt;go get&lt;/code&gt; and &lt;code&gt;go mod download&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;After a lot of hunting around I discovered that, in fact, Go has been created to deal with several possible Version Control Systems (VCS). You can see how it interacts with them in the internal &lt;a href=&#34;https://github.com/golang/go/tree/0c7e5d3b8db6668d6bdb16410d59c0ae4133400a/src/cmd/go/internal/vcs&#34;&gt;VCS&lt;/a&gt; package.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The general purpose nature of the VCS usage in Go means that some assumptions have been made that make &lt;code&gt;go get&lt;/code&gt; and &lt;code&gt;go mod download&lt;/code&gt; not quite work the same as (say) &lt;code&gt;git clone&lt;/code&gt;, and that&amp;rsquo;s why I was having a bit of trouble :).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This StackOverflow question about &lt;a href=&#34;https://stackoverflow.com/questions/73491769/github-private-repository-ssh-go-get-go-mod-tidy-fail-while-git-clone-is-w&#34;&gt;Private Repository with SSH&lt;/a&gt; was invaluable for diagnosing the problems and suggestions for solutions, including what I have finally got working.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;There are three things that are &lt;strong&gt;needed&lt;/strong&gt; in order for you to use the code hosted non-publicly.&lt;/p&gt;&#xA;&#xA;&lt;h3&gt;Gitconfig&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The very first problem for me was that my repositories are accessible via &lt;code&gt;ssh&lt;/code&gt; only, not &lt;code&gt;https&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Both &lt;code&gt;git&lt;/code&gt; and &lt;code&gt;go get&lt;/code&gt; can handle this just fine, by adding a couple of lines to the local &lt;code&gt;~/.gitconfig&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;[url &amp;quot;git@github.com:&amp;quot;]&#xA;        insteadOf = https://github.com/&#xA;[url &amp;quot;git@shanehoweath.com:&amp;quot;]&#xA;        insteadOf = https://shanehowearth.com/&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;GOPRIVATE&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;The Go download tools will try to find the repository/package using the module proxy, but my modules are private, and I don&amp;rsquo;t want them published/acknowledged to even exist on the public proxy.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Go uses an environment variable &lt;a href=&#34;https://goproxy.io/docs/GOPRIVATE-env.html&#34;&gt;GOPRIVATE&lt;/a&gt; to determine if the public proxy should be consulted.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I use &lt;code&gt;GOPRIVATE&lt;/code&gt; when I run &lt;code&gt;go mod download&lt;/code&gt; (including in Dockerfiles)&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;GOPRIVATE=shanehowearth.com go mod download&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;h3&gt;Repository naming.&lt;/h3&gt;&#xA;&#xA;&lt;p&gt;This was the most difficult issue to diagnose. None of the tools were telling me what the problem was, and it&amp;rsquo;s a function of the fact that Go&amp;rsquo;s &lt;code&gt;vcs&lt;/code&gt; system is trying to cater to multiple different types of version control systems (git, mercurial, etc).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;code&gt;Git&lt;/code&gt; will happily clone, pull, push, to any directory on any host that it&amp;rsquo;s directed to. Go will too, &lt;strong&gt;except&lt;/strong&gt; when it&amp;rsquo;s a private repository over ssh, then the name of the repository/directory becomes important.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Firstly, the name of the host &lt;strong&gt;must&lt;/strong&gt; have a &amp;lsquo;.&amp;rsquo; (period) in the name. so &lt;code&gt;localhost/path/to/repo&lt;/code&gt; will not work. The stackoverflow link above recommends to use the &lt;code&gt;ip&lt;/code&gt; if the host doesn&amp;rsquo;t have a period in the name (eg. use &lt;code&gt;192.168.1.72&lt;/code&gt; instead of &lt;code&gt;zeus&lt;/code&gt;).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Secondly, and this is quite painful, the name of the repository &lt;strong&gt;must&lt;/strong&gt; have a &lt;code&gt;.git&lt;/code&gt; suffix.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;So &lt;code&gt;shanehowearth.com/path/to/repo&lt;/code&gt; must be renamed to &lt;code&gt;shanehowearth.com/path/to/repo.git&lt;/code&gt; in order for it to work.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Note that this also means any references to the repo in the code must also have the &lt;code&gt;.git&lt;/code&gt; suffix too, so I have package imports like &lt;code&gt;shanehowearth.com/path/to/repo.git/package/name&lt;/code&gt; in my code, which I find ugly, but it is what it is.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This includes the &lt;code&gt;go.mod&lt;/code&gt; file, which has to have &lt;code&gt;.git&lt;/code&gt; in the package name:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;module shanehowearth/path/to/module.git&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;I am exasperated by this issue, it took me a couple of days to discover that the three things were combining to make it really difficult to host my own repo and use it via ssh with go, but I can now.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Hopefully this blog post saves someone else the pain I went through, and, of course, a massive thanks to StackOverflow user &lt;a href=&#34;https://stackoverflow.com/users/17478325/nshuytsa&#34;&gt;nshuytsa&lt;/a&gt; for publishing their solution as well.&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">98fc8527-957a-409b-a7c3-5b314d780a7c</guid>
      <pubDate>Sat, 25 Nov 2023 22:16:39 GMT</pubDate>
    </item>
    <item>
      <title>Combining templates with embed in Go</title>
      <link>http://shanehowearth.com/combining-templates-with-embed-in-go</link>
      <description>&lt;p&gt;Go templates are a great way to build dynamic text or html with Go programs. They are very much like &lt;a href=&#34;https://jinja.palletsprojects.com/en/3.1.x/&#34;&gt;Jinja templates&lt;/a&gt;, but they come with a serious shortcoming, they are loaded at runtime, rather than compile time.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This is a problem because a developer has very little control on where the templates will be located on a filesystem when the application is in use (whether in a users machine, or on a cloud).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Go 1.16 added the &lt;a href=&#34;https://pkg.go.dev/embed&#34;&gt;embed&lt;/a&gt; package that meant that files needed by a binary could be embedded in the binary at compile time. This means that a developer now has absolute control on where a template is at compile time, and at runtime.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This is an example of templates being used with embed.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;First, the filesystem for this example is thus:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ tree&#xA;.&#xA;├── accounts.go&#xA;├── cmd&#xA;│   └── main.go&#xA;├── coreTemplates&#xA;│   └── base.tmpl&#xA;├── Dockerfile&#xA;├── go.mod&#xA;├── go.sum&#xA;├── Makefile&#xA;├── README.md&#xA;└──templates&#xA;    └── signup.tmpl&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The &lt;code&gt;accounts.go&lt;/code&gt; file needs to make use of the &lt;code&gt;base.tmpl&lt;/code&gt; and &lt;code&gt;signup.tmpl&lt;/code&gt; templates, so that is all the &lt;code&gt;go&lt;/code&gt; code that needs to be seen.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Note that there is a form in &lt;code&gt;signup.tmpl&lt;/code&gt; that requires a CSRF token in it (see the template file pasted at the bottom).&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt; $ cat accounts.go&#xA;package accounts&#xA;&#xA;import (&#xA;        &amp;quot;embed&amp;quot;&#xA;        &amp;quot;io&amp;quot;&#xA;        &amp;quot;log/slog&amp;quot;&#xA;        &amp;quot;text/template&amp;quot;&#xA;&#xA;        &amp;quot;github.com/gorilla/csrf&amp;quot;&#xA;)&#xA;&#xA;var (&#xA;        //go:embed coreTemplates/*.tmpl&#xA;        baseTemplateFS embed.FS&#xA;&#xA;        //go:embed templates/*.tmpl&#xA;        signupTemplateFS embed.FS&#xA;)&#xA;&#xA;type Server struct{}&#xA;&#xA;func NewAccountServer() *Server {&#xA;        return &amp;amp;Server{}&#xA;}&#xA;&#xA;func (s *Server) ShowRegisterAccountForm(responseWriter io.Writer, csrfToken string) {&#xA;        baseTemplate := template.Must(template.New(&amp;quot;base&amp;quot;).ParseFS(baseTemplateFS, &amp;quot;coreTemplates/base.tmpl&amp;quot;))&#xA;&#xA;        signupTemplate := template.Must(baseTemplate.ParseFS(signupTemplateFS, &amp;quot;templates/signup.tmpl&amp;quot;))&#xA;&#xA;        if err := signupTemplate.ExecuteTemplate(responseWriter, &amp;quot;base.tmpl&amp;quot;, map[string]interface{}{&#xA;                csrf.TemplateTag: csrfToken,&#xA;        }); err != nil {&#xA;                slog.Error(&amp;quot;unable to execute template&amp;quot;, &amp;quot;error&amp;quot;, err.Error())&#xA;        }&#xA;}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The templates are loaded into the &lt;code&gt;embed.FS&lt;/code&gt; filesystem, when the file is compiled. I&amp;rsquo;ve used two &lt;code&gt;embed.FS&lt;/code&gt; one for &lt;code&gt;coreTemplates&lt;/code&gt; which is a git &lt;code&gt;submodule&lt;/code&gt; containing template files that are the basis for system wide web pages, containing common headers, footers, menus, etc.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The second &lt;code&gt;embed.FS&lt;/code&gt; contains the templates relevant to this service/domain (for the Domain Driven Design aficionados).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Within the &lt;code&gt;ShowRegisterAccountForm&lt;/code&gt; function the &lt;code&gt;base.tmpl&lt;/code&gt; is parsed (the path and name of the template being parsed from the &lt;code&gt;embed.FS&lt;/code&gt; needs to be supplied).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Similarly the &lt;code&gt;signup.tmpl&lt;/code&gt; is parsed, but with the &lt;code&gt;baseTemplate&lt;/code&gt;, because we want the &lt;code&gt;signup.tmpl&lt;/code&gt; to have some of the &lt;code&gt;base.tmpl&lt;/code&gt; attributes.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Finally, the templates are executed with the &lt;code&gt;csrf token&lt;/code&gt; injected into the &lt;code&gt;signup.tmpl&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The &lt;code&gt;base.tmpl&lt;/code&gt; looks like this:&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ cat coreTemplates/base.tmpl&#xA;&amp;lt;html&amp;gt;&#xA;  &amp;lt;head&amp;gt;{{block &amp;quot;head&amp;quot; .}} Working Title {{end}}&amp;lt;/head&amp;gt;&#xA;  &amp;lt;body&amp;gt;{{block &amp;quot;body&amp;quot; .}} Lorem ipsum dolor sit amet, consectetur adipiscing&#xA;  elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut&#xA;  enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut&#xA;  aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in&#xA;  voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint&#xA;  occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit&#xA;  anim id est laborum.{{end}}&amp;lt;/body&amp;gt;&#xA;  &amp;lt;footer&amp;gt;{{block &amp;quot;footer&amp;quot; .}}TODO{{end}}&amp;lt;/footer&amp;gt;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;And the &lt;code&gt;signup.tmpl&lt;/code&gt; looks like this.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;$ cat templates/signup.tmpl&#xA;{{define &amp;quot;head&amp;quot;}}&amp;lt;title&amp;gt;Join Something&amp;lt;/title&amp;gt;{{end}}&#xA;{{define &amp;quot;body&amp;quot;}}&#xA;&amp;lt;form method=&amp;quot;post&amp;quot; action=&amp;quot;/account&amp;quot;&amp;gt;&#xA;  {{ .csrfField  }}&#xA;  &amp;lt;label for=&amp;quot;fname&amp;quot;&amp;gt;First name:&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;fname&amp;quot; name=&amp;quot;fname&amp;quot; value=&amp;quot;Lady&amp;quot;&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;label for=&amp;quot;lname&amp;quot;&amp;gt;Last name:&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;lname&amp;quot; name=&amp;quot;lname&amp;quot; value=&amp;quot;Penelope&amp;quot;&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;label for=&amp;quot;lname&amp;quot;&amp;gt;Company:&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;company&amp;quot; name=&amp;quot;company&amp;quot; value=&amp;quot;International Rescue&amp;quot;&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;label for=&amp;quot;lname&amp;quot;&amp;gt;Email:&amp;lt;/label&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;input type=&amp;quot;text&amp;quot; id=&amp;quot;email&amp;quot; name=&amp;quot;email&amp;quot; value=&amp;quot;Lady.Penelope@example.com&amp;quot;&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;&#xA;  &amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Submit&amp;quot;&amp;gt;&#xA;&amp;lt;/form&amp;gt;&#xA;{{end}}&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">75e66c3a-ce83-4316-b3a9-e31aa681dd02</guid>
      <pubDate>Wed, 22 Nov 2023 22:42:12 GMT</pubDate>
    </item>
    <item>
      <title>Benign Data Races in Go</title>
      <link>http://shanehowearth.com/benign-data-races-in-go</link>
      <description>&lt;p&gt;Every so often, in one forum, or another, the subject of benign data races comes up. People want to know if a data race absolutely must have synchronisation guards applied (mutexes, semaphores, etc).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;For those not familiar with the concept, a &lt;a href=&#34;https://en.wikipedia.org/wiki/Race_condition#Data_race&#34;&gt;&lt;code&gt;data race&lt;/code&gt;&lt;/a&gt; is when two or more goroutines/threads/processes/etc have access to a mutable resource, and at least one of them mutates that resource. A &lt;code&gt;benign data race&lt;/code&gt; is when such a data race is claimed to have no ill effect on the application.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Benign Data races have been the subject of discussion in a number of technologies for some time, and are contentious.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://docs.oracle.com/cd/E19205-01/820-0619/gecqt/index.html&#34;&gt;Java documentation&lt;/a&gt; talks about benign data races not only being possible, but also advantageous, because of improved performance (no need to lock/unlock synchronisation tools). Note, from the documentation&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;In addition to benign data-races, a large class of applications allow data-races because they rely on lock-free and wait-free algorithms which are difficult to design correctly. &#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;(My blog post isn&amp;rsquo;t going to delve into lock free programming, beyond Benign data races)&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://hboehm.info/&#34;&gt;Hans-J. Boehm&lt;/a&gt; wrote a paper entitled &lt;a href=&#34;https://www.usenix.org/legacy/event/hotpar11/tech/final_files/Boehm.pdf&#34;&gt;How to miscompile programs with &amp;ldquo;benign&amp;rdquo; data races&lt;/a&gt; which concluded that&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;C or C++ data races are always unsafe in portable code&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;and&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code&gt;Although “benign” races in Java are not clearly incorrect, they nonetheless put the programmer into an uncertain and dangerous territory that can be avoided by avoiding data races.&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;Boehm&amp;rsquo;s findings largely rest on the fact that C and C++ compilers may be compiling for targets other than x86, which do not, necessarily, have &lt;code&gt;atomic&lt;/code&gt; writes.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Further Boehm&amp;rsquo;s paper points to a number of usecases where data races can occur because of adjacency, that is, the compiler is treating two variables as one because they will lie so close together in memory.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;For these reasons, it&amp;rsquo;s important to be aware of more than just the &amp;ldquo;correctness&amp;rdquo; of the code that the given language is written in, but, also the code that is emitted for a given CPU architecture, and that CPU&amp;rsquo;s atomicity when performing writes (it&amp;rsquo;s writes that cause the problem, not reads).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;But we have a &amp;ldquo;benign data race&amp;rdquo; example, in computing, that is used all day, every day - we even have a name for it &amp;ldquo;Eventual consistency&amp;rdquo; - the state of the data may or may not be reflected accurately by the reader, but, we account for that by saying that, given some time, the reader will accurately reflect the actual state of the data.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Eventual consistency exists because of the &lt;a href=&#34;https://en.wikipedia.org/wiki/CAP_theorem&#34;&gt;CAP theorem&lt;/a&gt;. Once data is partitioned, that is, more than one copy exists, or multiple compute instances (threads, etc) access that data, then the end result will be consistent, or available, but never both.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;For this reason, we have to accept that benign data races are both possible, and, genuinely safe for some applications.  The principle is the same, the data is being shared by multiple threads/processes, and a mutation of that data can occur at any time. The readers will eventually be showing that new state, and as long as that is recognised, then that is an acceptable outcome.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;For non-technical readers, eventual consistency has existed in the real world forever. If I say that I am going to pay a bill, and it takes some time for that payment to show up, even though the bill has been paid, it will show, for some time, as unpaid (the cheque is in the mail! :-)&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">dd5ac9b9-412e-4877-ae5d-1f32b920bd39</guid>
      <pubDate>Mon, 20 Nov 2023 23:12:53 GMT</pubDate>
    </item>
    <item>
      <title>Redux - state management (with React and Typescript)</title>
      <link>http://shanehowearth.com/redux-state-management-with-react-and-typescript</link>
      <description>&lt;p&gt;I am building a tool that requires a great deal of state management on the client side. After all of 30 seconds of research I decided to use &lt;a href=&#34;https://redux.js.org/&#34;&gt;Redux&lt;/a&gt; for state management. (For the record, after I started down this path someone mentioned &lt;a href=&#34;https://github.com/pmndrs/zustand/tree/main&#34;&gt;Zustand&lt;/a&gt; as an alternative, which they claimed had an easier interface. I haven&amp;rsquo;t investigated it at all, but someone might find it useful to know of alternaitves).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;I largely followed the &lt;a href=&#34;https://redux.js.org/tutorials/typescript-quick-start&#34;&gt;Redux Typescript tutorial&lt;/a&gt;, but there were a couple of things that I ended up asking for help with, because it wasn&amp;rsquo;t clear (to me) in the tutorial.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;The basic concepts that are required to be understood is that &lt;code&gt;redux&lt;/code&gt; has a &lt;code&gt;store&lt;/code&gt; that components can Create, Read, Update, Delete (&lt;code&gt;CRUD&lt;/code&gt;) items from.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;In earlier releases a developer needed to understand that the &lt;code&gt;store&lt;/code&gt; was immutable, so changes were made by creating a copy, making changes to the copy, then saving the new version of the &lt;code&gt;store&lt;/code&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;&lt;a href=&#34;https://redux-toolkit.js.org/&#34;&gt;Redux-toolkit&lt;/a&gt; has abstracted that away, developers only need to tell &lt;code&gt;redux&lt;/code&gt; what changes are being made, and the &lt;a href=&#34;https://immerjs.github.io/immer/&#34;&gt;immer&lt;/a&gt; library is used under the hood to keep with the immutable nature of the &lt;code&gt;redux&lt;/code&gt; &lt;code&gt;store&lt;/code&gt;. (This is helpful to know, however, when debugging, because you cannot just &lt;code&gt;console.log(state)&lt;/code&gt;).&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Down to business, er, I mean, let&amp;rsquo;s look at how things are done in the code, with explanations.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;First of all, access to the &lt;code&gt;state&lt;/code&gt; is via &lt;a href=&#34;https://redux-toolkit.js.org/usage/immer-reducers&#34;&gt;reducers&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;First, I put this code in &lt;code&gt;features/location/locationSlice.ts&lt;/code&gt;&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-TSX&#34;&gt;import { createSlice  } from &#39;@reduxjs/toolkit&#39;;&#xA;&#xA;// This import allows us to dump the state to console log&#xA;// for debugging&#xA;import { current } from &amp;quot;immer&amp;quot;;&#xA;&#xA;// This is the shape of the data being stored in redux.&#xA;// In this instance there is only one object, indexed by an id.&#xA;// But there is no reason that there couldn&#39;t be more objects.&#xA;// eg. If you wanted one for UI, and one for some&#xA;// other set of data.&#xA;interface EntityState {&#xA;&#xA;      x: number;&#xA;      y: number;&#xA;  };&#xA;}&#xA;&#xA;// Initialise the state for redux.&#xA;const initialState: EntityState = {};&#xA;&#xA;// This is where the work is done.&#xA;// We name the object (this becomes important when reading from&#xA;// the state in components).&#xA;// We define the reducers, note that the reducers can be defined&#xA;// elsewhere, and called here in the reducers section.&#xA;export const positionSlice = createSlice({&#xA;    name: &#39;position&#39;,&#xA;    initialState,&#xA;  reducers: {&#xA;    // This is a modifier reducer, modifying the current state.&#xA;    moveItem: (state, action) =&amp;gt; {&#xA;      // decompose the action payload.&#xA;      const { id, x, y  } = action.payload;&#xA;&#xA;      // Update the nested object.&#xA;      if (state[id]) {&#xA;          state[id].x = x;&#xA;          state[id].y = y;&#xA;      }&#xA;    },&#xA;&#xA;    // This is a Creation reducer. It&#39;s job is to create new&#xA;    // instances of objects being stored in the state.&#xA;    newItem: (state, action) =&amp;gt; {&#xA;      // This is a debug statement, purely for demonstration.&#xA;      console.log(current(state));&#xA;&#xA;      // Decompose the action payload.&#xA;      const { id, x, y, width, height, elementType  } = action.payload;&#xA;&#xA;      // Create the new nested object.&#xA;      // Note that I am not doing any checking if an object with&#xA;      // the same name already exists.&#xA;      // I am using uuids for the name and am confident that&#xA;      // there will be no collisions, but defensive code would&#xA;      // be a good idea here.&#xA;      state[id] = {&#xA;        x: x,&#xA;        y: y,&#xA;      }&#xA;    },&#xA;  },&#xA;});&#xA;&#xA;// Expose the actions to the world.&#xA;export const { newItem, moveItem  } = positionSlice.actions;&#xA;&#xA;export default positionSlice.reducer;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;I have a &lt;code&gt;store&lt;/code&gt;, defined in &lt;code&gt;store.ts&lt;/code&gt;, it&amp;rsquo;s not very complex&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-TSX&#34;&gt;import { configureStore } from &#39;@reduxjs/toolkit&#39;&#xA;&#xA;// When we build the store we use the previously created&#xA;// reducer.&#xA;import positionReducer from &#39;./features/position/positionSlice&#39;;&#xA;&#xA;const store = configureStore({&#xA;  reducer: {&#xA;    position: positionReducer,&#xA;  },&#xA;})&#xA;&#xA;// Make the RootState and Dispatch types easier to access&#xA;// in components.&#xA;export type RootState = ReturnType&amp;lt;typeof store.getState&amp;gt;&#xA;&#xA;export type AppDispatch = typeof store.dispatch&#xA;&#xA;export default store&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;edit: I forgot to add the following &lt;code&gt;index.tsx&lt;/code&gt; (or &lt;code&gt;App.tsx&lt;/code&gt; - this is purely to make the store available to all of the components.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-TSX&#34;&gt;import React from &#39;react&#39;;&#xA;import ReactDOM from &#39;react-dom/client&#39;;&#xA;import &#39;./index.css&#39;;&#xA;import App from &#39;./App&#39;;&#xA;import store from &#39;./store&#39;&#xA;import { Provider } from &#39;react-redux&#39;&#xA;&#xA;const root = ReactDOM.createRoot(&#xA;  document.getElementById(&#39;root&#39;) as HTMLElement&#xA;);&#xA;&#xA;root.render(&#xA;  &amp;lt;React.StrictMode&amp;gt;&#xA;    &amp;lt;Provider store={store}&amp;gt;&#xA;      &amp;lt;App /&amp;gt;&#xA;    &amp;lt;/Provider&amp;gt;&#xA;  &amp;lt;/React.StrictMode&amp;gt;&#xA;);&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;That&amp;rsquo;s all that&amp;rsquo;s required for &lt;code&gt;redux&lt;/code&gt;. The next parts are &lt;strong&gt;using&lt;/strong&gt; the &lt;code&gt;redux&lt;/code&gt; &lt;code&gt;store&lt;/code&gt; in components.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;This shows the creation reducer being called.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-TSX&#34;&gt;import React, { useState } from &amp;quot;react&amp;quot;;&#xA;&#xA;// import the creation reducer&#xA;import { newItem } from &amp;quot;../features/position/positionSlice&amp;quot;;&#xA;&#xA;// Import useDispatch - this is used to call the reducer.&#xA;import { useDispatch  } from &#39;react-redux&#39;&#xA;&#xA;const { v4: uuidv4  } = require(&#39;uuid&#39;);&#xA;&#xA;const Editor = () =&amp;gt; {&#xA;  // make the function a little bit nicer to call.&#xA;  const dispatch = useDispatch();&#xA;&#xA;  // add an element&#xA;  const handleClick = (name: string, height: number, width: number) =&amp;gt; {&#xA;    // Set the ID for the new item.&#xA;    const ID = uuidv4();&#xA;&#xA;    // Add new item to global state.&#xA;    dispatch(newItem({&#xA;      id: ID,&#xA;      x: 0,&#xA;      y: 0,&#xA;    }));&#xA;  };&#xA;&#xA;  const handleItemClick = () =&amp;gt; { };&#xA;&#xA;  return &amp;lt;div className=&amp;quot;example&amp;quot;&amp;gt;&#xA;    &amp;lt;span className=&amp;quot;symbol&amp;quot; id=&amp;quot;projectStarter&amp;quot; onClick={() =&amp;gt; handleClick(&amp;quot;project&amp;quot;, 120, 120)}&amp;gt;&#xA;        [Product/System]&#xA;      &amp;lt;/span&amp;gt;&#xA;&amp;lt;/div&amp;gt;&#xA;};&#xA;&#xA;export default Example;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The modifier access is exactly the same, but the read access is a little more tricky, so that&amp;rsquo;s what i will show here.&lt;/p&gt;&#xA;&#xA;&lt;pre&gt;&lt;code class=&#34;language-TSX&#34;&gt;import React from &amp;quot;react&amp;quot;;&#xA;&#xA;// Import the tools needed to access the redux state.&#xA;import { useSelector } from &#39;react-redux&#39;;&#xA;&#xA;// This was our way of making it easier to get the type of the state.&#xA;import { RootState } from &amp;quot;../store&amp;quot;;&#xA;&#xA;type ArrowProps = {&#xA;    startPoint: ElementItem;&#xA;    endPoint: ElementItem;&#xA;};&#xA;&#xA;const Arrow = ({ startPoint, endPoint  }: ArrowProps) =&amp;gt; {&#xA;  // Get start and end points from global state.&#xA;  // It&#39;s important to note the use of the name from the first file to access the nested object inside the state.&#xA;  // That is, the state itself is a complex object, and our data has to be accessed as a nested object.&#xA;  const start = useSelector((state: RootState) =&amp;gt; state.position[startPoint.id]);&#xA;  const end = useSelector((state: RootState) =&amp;gt; state.position[endPoint.id])&#xA;...&#xA;};&#xA;&#xA;export default Arrow;&#xA;&lt;/code&gt;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;I have heavily commented the code because I feel that the explanation of what&amp;rsquo;s happening is best read in place, where something is being used.&lt;/p&gt;&#xA;&#xA;&lt;p&gt;It should be clear, though, that &lt;code&gt;redux&lt;/code&gt; is very simple to use, when you know what you are doing ;)&lt;/p&gt;&#xA;</description>
      <dc:creator>Shane</dc:creator>
      <guid isPermaLink="false">12670f98-108c-4b28-8388-d59026145dae</guid>
      <pubDate>Fri, 10 Nov 2023 00:17:08 GMT</pubDate>
    </item>
  </channel>
</rss>