<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Daniel Michaels | Developer | Entrepreneur | Veteran</title><link>https://danielms.site/</link><description>Recent content on Daniel Michaels | Developer | Entrepreneur | Veteran</description><generator>Hugo</generator><language>en</language><lastBuildDate>Wed, 13 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://danielms.site/index.xml" rel="self" type="application/rss+xml"/><item><title>HTTPX is the new Requests with Async</title><link>https://danielms.site/blog/httpx-is-the-new-requests-with-async/</link><pubDate>Thu, 25 Jun 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/httpx-is-the-new-requests-with-async/</guid><description>&lt;h1 id="long-live-requests-welcome-httpx"&gt;Long live requests welcome HTTPX&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTPX is a fully featured HTTP client for Python 3, which provides sync and async APIs, and support for both HTTP/1.1 and HTTP/2.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Far and away, requests stands as &lt;em&gt;the&lt;/em&gt; most notable third party package. It&amp;rsquo;s almost considered part of what makes up python these days.
Unfortunately, little forward progress has been made in recent times to incorporate python&amp;rsquo;s async capabilities.&lt;/p&gt;
&lt;p&gt;This is where HTTPX is stepping up to the plate. And it aims to be compatible with &lt;em&gt;most&lt;/em&gt; of &lt;a href="https://www.python-httpx.org/compatibility/"&gt;requests API&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="why-use-httpx-over-requests"&gt;Why use HTTPX over Requests?&lt;/h2&gt;
&lt;p&gt;In addtion to async, HTTPX also supports:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP/2&lt;/li&gt;
&lt;li&gt;Timeouts by default&lt;/li&gt;
&lt;li&gt;Full type annotation (yay, code editors)&lt;/li&gt;
&lt;li&gt;Direct requests to &lt;a href="https://www.python-httpx.org/advanced/#calling-into-python-web-apps"&gt;WSGI&lt;/a&gt; and &lt;a href="https://www.python-httpx.org/async/#calling-into-python-web-apps"&gt;ASGI&lt;/a&gt; applications&lt;/li&gt;
&lt;li&gt;Plus, all the standard features of requests - list &lt;a href="https://www.python-httpx.org/#features"&gt;here&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="simple-example---sync"&gt;Simple example - sync&lt;/h2&gt;
&lt;p&gt;As HTTPX supports the core requests API, to do a simple get of a webpage we can use &lt;code&gt;httpx.get()&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://httpbin.org/get&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# json output cleaned up for ease of reading.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;args&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;headers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Accept&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;*/*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Accept-Encoding&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gzip, deflate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;httpbin.org&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python-httpx/0.13.3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;X-Amzn-Trace-Id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Root=1-5f014d41-5cef343010793f4686d7c258&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;origin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;19.13.3.36&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;url&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;http://httpbin.org/get&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="client-is-the-new-session"&gt;Client is the new Session&lt;/h2&gt;
&lt;p&gt;When using requests, if you needed to do anything more than receieve simple data then the Session instance was necessary.&lt;/p&gt;
&lt;p&gt;HTTPX does not have a Session API instead it has &lt;code&gt;Client&lt;/code&gt; and &lt;code&gt;AsyncClient&lt;/code&gt; for obvious use cases.&lt;/p&gt;
&lt;p&gt;From the HTTPX docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you do anything more than experimentation, one-off scripts, or prototypes, then you should use a Client instance.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For more reasons why you should use &lt;code&gt;Client&lt;/code&gt; read the &lt;a href="https://www.python-httpx.org/advanced/#why-use-a-client"&gt;docs&lt;/a&gt;. But the core reasons are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduced latency across requests (no handshaking).&lt;/li&gt;
&lt;li&gt;Reduced CPU usage and round-trips.&lt;/li&gt;
&lt;li&gt;Reduced network congestion.&lt;/li&gt;
&lt;li&gt;Without &lt;code&gt;Client&lt;/code&gt; you don&amp;rsquo;t get HTTP/2 support&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To use a the &lt;code&gt;Client&lt;/code&gt; interface, its recommended to use a context manager.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To make a request with the &lt;code&gt;Client&lt;/code&gt; its as simple as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://httpbin.org/get&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;One of the really cool features is &lt;code&gt;base_url&lt;/code&gt; which allows for URL prepending.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# base_url example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://httpbin.org&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/headers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;host&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;httpbin.org&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user-agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;python-httpx/0.13.3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;accept&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;*/*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;accept-encoding&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;gzip, deflate&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;connection&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;keep-alive&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="asyncclient-simple"&gt;AsyncClient simple&lt;/h2&gt;
&lt;p&gt;To make an async call using the &lt;code&gt;Client&lt;/code&gt; interface is as simple as adding the &lt;code&gt;async&lt;/code&gt; keyword to the context manager and swapping &lt;code&gt;Client&lt;/code&gt; with &lt;code&gt;AsyncClient&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# AsyncClient example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://httpbin.org/get&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is some API differences when using the &lt;code&gt;AsyncClient&lt;/code&gt; that are worth being &lt;a href="https://www.python-httpx.org/async/#api-differences"&gt;aware&lt;/a&gt; of.&lt;/p&gt;
&lt;h2 id="asyncclient-real-example"&gt;AsyncClient real example&lt;/h2&gt;
&lt;p&gt;I am not a python async expert so I did have some issues getting it to function as expected.
When I needed to concurrently get several json endpoints worth of data a lot of experiementation was needed on my part.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/florimondmanca"&gt;Florimond Manca&lt;/a&gt;s &lt;a href="https://dev.to/florimondmanca/httpx-for-hacktoberfest-help-build-the-future-of-python-http-16pj"&gt;post&lt;/a&gt; set out the ground work for solving my problem; I needed a way to call a list of url&amp;rsquo;s looping over the endpoint from a list id&amp;rsquo;s.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Florimond&amp;#39;s example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;asyncio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# We&amp;#39;re going to fetch tag pages concurrently...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://dev.to/t/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hacktoberfest&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;opensource&amp;#39;&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="n"&gt;OK&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://dev.to/t/hacktoberfest&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://dev.to/t/python&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://dev.to/t/opensource&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This worked perfectly in an Ipython terminal but I needed to integrate into a sync class (swapping out the slow synchronous api call&amp;rsquo;s for a faster async version).&lt;/p&gt;
&lt;h3 id="my-version"&gt;My version&lt;/h3&gt;
&lt;p&gt;I needed three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A method to call each endpoint,&lt;/li&gt;
&lt;li&gt;AsyncClient for executing the requests, and&lt;/li&gt;
&lt;li&gt;Something to kick off these actions.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following is a snippet which meets all three critieria. It takes Florimond&amp;rsquo;s demonstration example and shows how it could be implemented into a real piece of callable code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Return async response object for HackerNews item.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; :param item_id: json id for HackerNews item
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; :param client: httpx.AsyncClient method
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; :return: httpx.AsyncClient response object
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://hacker-news.firebaseio.com/v0/item/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;story_metadata&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; Return a list of responses from the given list of HackerNews item id&amp;#39;s.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; :return: list of HackerNews response objects
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;23709004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23717964&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23723433&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23725506&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;23713605&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;responses&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_list&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_async_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coro&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; A helper method to create async coroutines.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; :return: result set from the async function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coro&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adding async here led to a speed increase of 50% over a relatively small selection of items - it only grabs the top ten articles. The return time went from 3+ seconds to ~1.1-1.5, which is consistent with network latency rather than python synchronicity.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;Over the last month or so, whenever I have needed a requests like interface I have elected to use &lt;a href="https://www.python-httpx.org/"&gt;HTTPX&lt;/a&gt; instead.
In this time there has been only one thing missing; a caching adapter. Previously, I would lean heavily upon &lt;a href="https://github.com/reclosedev/requests-cache"&gt;requests-cache&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Apart from this, the experience has been seemless in fact I find HTTPX easier to use.
It has a very active &lt;a href="https://gitter.im/encode/community"&gt;community&lt;/a&gt;, great &lt;a href="https://github.com/encode"&gt;leadership&lt;/a&gt; and highly active development.
Being a relatively new package it is very possible to get involved and contribute as well.&lt;/p&gt;
&lt;h3 id="gist"&gt;Gist&lt;/h3&gt;
&lt;p&gt;A full copy of the working code is below as well as &lt;a href="https://gist.github.com/danielmichaels/b2ffb53736a6a9157b12ea4a1388673b"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;script src="https://gist.github.com/danielmichaels/b2ffb53736a6a9157b12ea4a1388673b.js"&gt;&lt;/script&gt;
</description></item><item><title>DNS the easy parts</title><link>https://danielms.site/blog/dns-the-easy-parts/</link><pubDate>Sun, 16 Jun 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/dns-the-easy-parts/</guid><description>&lt;h1 id="dns"&gt;DNS&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/dns.png" alt="DNS" title="dns logo"&gt;&lt;/p&gt;
&lt;p&gt;The Internet&amp;rsquo;s success is predicated on the Domain Name System or DNS. We&amp;rsquo;ve all heard of it, many of us loosely understand it but, like all things, when you need to fix it knowledge gaps become painfully obvious.&lt;/p&gt;
&lt;p&gt;When it comes to diagnosing network issues, understanding adblocking, speeding up queries or protecting privacy, you can bet DNS will be mentioned. Unlike other protocols such as Network Address Translation , or Open Shortest Path First, DNS is a network fundamental that every engineer should have down. Sadly, during my CCNA studies, it did not get much more than the cursory explanation.&lt;/p&gt;
&lt;h1 id="overview"&gt;Overview&lt;/h1&gt;
&lt;p&gt;DNS is a protocol that can map IP addresses to human readable names. When a user types &lt;code&gt;example.com&lt;/code&gt; into their browser bar and hits enter, the machine does not know how to reach &lt;code&gt;example.com&lt;/code&gt; without first translating it into an IP routable address.&lt;/p&gt;
&lt;p&gt;Before any network activity takes place between the client and the destination, a DNS lookup must first be conducted. Luckily, DNS is fast but it does add latency to the request. If this transaction fails the client will be unable to make a connection to the server. A user could, alternatively manually enter the servers IP address and this would work - as long as the server is up. But if we could remember IP addresses as readily as we do names, DNS would be redundant.&lt;/p&gt;
&lt;p&gt;It is worth noting that DNS also allows us to clump many IP addresses under one namespace. For instance, we can reach hosts on our local network using domain names instead of IP addresses. We use DNS for more than just browsing the web; emails, IoT devices and many other services that make networks invisible to the majority of users are powered by this system.&lt;/p&gt;
&lt;p&gt;From ten thousand feet, lets examine how a simple name to IP address translation takes place.&lt;/p&gt;
&lt;h2 id="dns-resolution"&gt;DNS Resolution&lt;/h2&gt;
&lt;p&gt;In the below diagram we have a series queries starting with the browser asking for the address to &lt;code&gt;example.com&lt;/code&gt; and ending with the server delivering content back to the browser.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/dns-lookup-diagram.png" alt="dns-lookup" title="dns ten step lookup - simple"&gt;
Figure 1. Simple DNS Lookup - &lt;a href="https://www.cloudflare.com/learning/dns/what-is-dns/"&gt;attrib&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When our above client types &lt;code&gt;example.com&lt;/code&gt; into the browser and hits enter, a series of steps are taken by the client and external servers to translate the human readable name into a machine readable IP address.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Each step broken down:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;After the user hits enter, the browser will check to see if the address has been cached, if not the operating systems cache will queried, and if both fail, the system will send out a request to a DNS resolver.&lt;/li&gt;
&lt;li&gt;Our resolver will parse its own cache and if nothing matches, will then forward a request to one of the DNS root name servers. Root name servers are at the top of the DNS hierarchy and are represented by the right most &lt;code&gt;.&lt;/code&gt; of our websites name.&lt;/li&gt;
&lt;li&gt;Once received, the root name server will check its &lt;em&gt;zone file&lt;/em&gt; for the Top-Level Domain (TLD) matching the request - in this case &lt;code&gt;com&lt;/code&gt;. It returns the address of &lt;code&gt;com&lt;/code&gt;&amp;rsquo;s name server back to our resolver.&lt;/li&gt;
&lt;li&gt;Our resolver now forwards a new request, this time to the &lt;code&gt;com&lt;/code&gt; TLD &lt;em&gt;authoritative&lt;/em&gt; name server.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;com&lt;/code&gt; name server checks its zone file for the subdomain &lt;code&gt;example&lt;/code&gt; and returns that address to the resolver.&lt;/li&gt;
&lt;li&gt;Now our resolver can ask the DNS server responsible (authoritative) for the &lt;code&gt;example&lt;/code&gt; domains address.&lt;/li&gt;
&lt;li&gt;The server responds with the address to &lt;code&gt;example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Our resolver, having sent out three requests up to this point, will now forward the correct IP address to the client. It will also save the address in its cache for future lookups.&lt;/li&gt;
&lt;li&gt;The browser will now initiate a connection using &lt;code&gt;HTTP&lt;/code&gt; with &lt;code&gt;example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;example.com&lt;/code&gt; having received the connection request, begins transferring data to the clients browser.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a very simplistic resolution. It works well in common household networks where your wireless router is not a DNS server. If you are instituting a firewall or managing your own name server, the below diagram is a more likely reflection of what is happening.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/dns-resolver-forwarding.JPG" alt="DNS resolver in LAN"&gt;
Figure 2. Forwarding Queries&lt;/p&gt;
&lt;p&gt;In the above example, the local DNS server sends a query to a recursive resolver from a large DNS provider. As stated in Figure 2, this can also be called a Forward DNS Server. The forward resolver will conduct all the iterative requests on our behalf before returning the answer back to our local DNS server, who forwards it to the client.&lt;/p&gt;
&lt;h2 id="why-root"&gt;Why Root?&lt;/h2&gt;
&lt;p&gt;DNS works in a hierarchical structure with the root or &lt;code&gt;.&lt;/code&gt; being at the top of the tree - very similar to the Linux file structure.&lt;/p&gt;
&lt;p&gt;So why does the recursive resolver go straight to the root name server, and not say the &lt;code&gt;com&lt;/code&gt; domain?&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/dns-root.png" alt="DNS hierarchy" title="DNS heirarchy"&gt;&lt;/p&gt;
&lt;p&gt;Looking at the above example we can see that everything is connected to root. So, the resolver must start with the only known address it has; root. Logically, it is the only server that can reach &lt;em&gt;all&lt;/em&gt; possible domains.&lt;/p&gt;
&lt;h2 id="top-level-domains"&gt;Top-Level Domains&lt;/h2&gt;
&lt;p&gt;After querying the root server, it will return the TLD name server for the domain being queried. The TLD server contains all the information for domains that share a common extension, such as &lt;code&gt;edu&lt;/code&gt;, &lt;code&gt;com&lt;/code&gt;, or whatever comes directly after the last dot in the url. More simply, the name server for &lt;code&gt;net&lt;/code&gt; will have the address for every website that ends with &lt;code&gt;net&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, when the user requests &lt;code&gt;example.com&lt;/code&gt;, the TLD name server of the &lt;code&gt;com&lt;/code&gt; domain will reply with the address of the authoritative server for the &lt;code&gt;example&lt;/code&gt; domain. Simply put, the name server who looks after the &lt;code&gt;com&lt;/code&gt; domain knows where to find every single domain that uses a &lt;code&gt;.com&lt;/code&gt; and will return the websites address back to the resolver, completing the lookup.&lt;/p&gt;
&lt;p&gt;Top-level domains can be broken into two groups:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generic (gTLD):
&lt;ul&gt;
&lt;li&gt;Addresses such as &lt;code&gt;com&lt;/code&gt;, &lt;code&gt;net&lt;/code&gt;, and &lt;code&gt;gov&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Country code (ccTLD):
&lt;ul&gt;
&lt;li&gt;Country specific, &lt;code&gt;jp&lt;/code&gt;, &lt;code&gt;au&lt;/code&gt;, &lt;code&gt;ca&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With an address such as &lt;code&gt;example.com.au&lt;/code&gt; the resolver will still query the root name servers but it will send the resolver to the country specific top level domain of &lt;code&gt;au&lt;/code&gt;. The domain to the &lt;strong&gt;right&lt;/strong&gt; of the last &lt;code&gt;.&lt;/code&gt; will always be queried first.&lt;/p&gt;
&lt;p&gt;We can demonstrate this by running &lt;code&gt;drill bbc.co.uk -T&lt;/code&gt; or &lt;code&gt;dig bbc.co.uk +trace&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="subdomains"&gt;Subdomains&lt;/h2&gt;
&lt;p&gt;Technically, after the TLD, each domain down the tree can be referenced as a subdomain; &lt;code&gt;example&lt;/code&gt; in &lt;code&gt;example.com&lt;/code&gt; being a subdomain of the &lt;code&gt;com&lt;/code&gt; TLD.&lt;/p&gt;
&lt;p&gt;Typically, subdomains more often refer to addresses such as &lt;code&gt;api.github.com&lt;/code&gt; with &lt;code&gt;api&lt;/code&gt; being a subdomain of &lt;code&gt;github&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can see the resolution by running &lt;code&gt;drill m.facebook.com -T&lt;/code&gt; or &lt;code&gt;dig m.facebook.com +trace&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At the date of publication, the facebook lookup provided a good example of subdomain and CNAME resolution. I have included a truncated output below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt; drill m.facebook.com -T
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...snip...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.	518400	IN	NS	l.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.	518400	IN	NS	i.root-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;com.	172800	IN	NS	l.gtld-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...snip...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;com.	172800	IN	NS	m.gtld-servers.net.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;facebook.com.	172800	IN	NS	a.ns.facebook.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;facebook.com.	172800	IN	NS	b.ns.facebook.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;m.facebook.com.	3600	IN	CNAME	star-mini.c10r.facebook.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;c10r.facebook.com.	3600	IN	NS	a.ns.c10r.facebook.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;c10r.facebook.com.	3600	IN	NS	b.ns.c10r.facebook.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;star-mini.c10r.facebook.com.	60	IN	A	157.240.8.35
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="terms"&gt;Terms&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Essentially all technical efforts wallow in acronyms and special &amp;ldquo;terms of art&amp;rdquo; [&amp;hellip;] with several non-obvious terms to confuse those who have not been involved for a while.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;RFC 4144 - How to Gain Prominence and Influence in Standards Organizations&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sums up many of the terms used to express components of the domain name system. A brief description of the main offenders follows and how to examine them using &lt;code&gt;drill&lt;/code&gt;. If &lt;code&gt;drill&lt;/code&gt; is unfamiliar to you, &lt;code&gt;dig&lt;/code&gt; will work just the same. &lt;code&gt;nslookup&lt;/code&gt; is not covered in this post, but will essentially do the same thing.&lt;/p&gt;
&lt;h3 id="dns-resolver"&gt;DNS resolver&lt;/h3&gt;
&lt;p&gt;The &amp;lsquo;resolver&amp;rsquo; is the agent between the client and the name servers. A resolver will start the queries that eventually return a valid address translation for the client.&lt;/p&gt;
&lt;p&gt;A recursive resolver knows how to traverse the tree to deliver a response to a query.&lt;/p&gt;
&lt;h3 id="authoritative-server"&gt;Authoritative Server&lt;/h3&gt;
&lt;p&gt;Owns the domain, or has all the records for that domain. E.g. the &lt;code&gt;com&lt;/code&gt; name server, knows the whereabouts of every single domain that uses it. Popular sites such as &lt;code&gt;google.com&lt;/code&gt;, &lt;code&gt;yahoo.com&lt;/code&gt; and &lt;code&gt;github.com&lt;/code&gt; are all domains under the &lt;code&gt;com&lt;/code&gt; authoritative server, it knows the address to each of those sites DNS server within their network.&lt;/p&gt;
&lt;h3 id="top-level-domain-tld"&gt;Top-level Domain (TLD)&lt;/h3&gt;
&lt;p&gt;Domains that are one level below the root are known as top level domains.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Examples:
&lt;code&gt;com&lt;/code&gt;, &lt;code&gt;jp&lt;/code&gt;, &lt;code&gt;edu&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Country Code TLD&amp;rsquo;s such as &lt;code&gt;au&lt;/code&gt;, &lt;code&gt;uk&lt;/code&gt; and &lt;code&gt;nz&lt;/code&gt; are examples. &lt;code&gt;taste.com.au&lt;/code&gt; will be resolved in the following order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt; - Root&lt;/li&gt;
&lt;li&gt;-&amp;gt; &lt;code&gt;.au&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;-&amp;gt; &lt;code&gt;com.au&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;-&amp;gt; &lt;code&gt;taste.com.au&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This can be seen via &lt;code&gt;drill -T taste.com.au&lt;/code&gt; which will trace (what &lt;code&gt;-T&lt;/code&gt; does) through each root name server all the way to the A record of &lt;code&gt;taste.com.au&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="recursive-request"&gt;Recursive request&lt;/h3&gt;
&lt;p&gt;When the client needs to get the address of a URL, it first checks its cache and if not found will send a request to its recursive resolver asking for the address. Often, the resolver being queried will be your ISP&amp;rsquo;s default name server (please don&amp;rsquo;t do this; they monetize it), or something like Google&amp;rsquo;s public DNS server; &lt;code&gt;8.8.8.8&lt;/code&gt;. &lt;strong&gt;My opinion&lt;/strong&gt;: use &lt;code&gt;1.1.1.1&lt;/code&gt; or some other provider - Google are using you, too.
The recursive resolver will then begin a series of iterative requests on your behalf.&lt;/p&gt;
&lt;p&gt;For the most part recursive queries begin with the client and end at the recursive resolver in a kind of A-&amp;gt;B, B-&amp;gt;A relationship. This is because the resolver is doing iterative queries on your behalf and here lies the big difference between recursive and iterative requests.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Recursive queries require an answer&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;As &lt;a href="https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/reviewing-dns-concepts#resolving-names-by-using-root-hints"&gt;Microsoft&lt;/a&gt; puts it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;recursive query indicates that the client wants a definitive answer to its query. The response to the recursive query must be a valid address or a message indicating that the address cannot be found.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Meaning a recursive can only reply with either the address, or an error (NXDOMAIN) but not a referral to another name server.&lt;/p&gt;
&lt;h3 id="iterative-request"&gt;Iterative request&lt;/h3&gt;
&lt;p&gt;Generally, the queries &lt;em&gt;from&lt;/em&gt; a recursive resolver to other name servers are iterative. They leave the resolver and start at the top of the DNS tree via the root name servers. The root replies with the appropriate domains name server. The resolver then makes another query to that server. The iterations continue down the hierarchy until an address is resolved.&lt;/p&gt;
&lt;p&gt;Unlike recursive queries, iterative requests will accept any answer; address, error or a referral to someone who knows more.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/plan/reviewing-dns-concepts#resolving-names-by-using-root-hints"&gt;Microsoft&lt;/a&gt; again:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An iterative query indicates that the server will accept a referral to another server in place of a definitive answer to the query.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="non-iterative-request"&gt;Non-Iterative request&lt;/h3&gt;
&lt;p&gt;When a query is made and the resolver has the address mapping in its cache, it will refer the client immediately to it. No further queries are made and this is known as a non-iterative request.&lt;/p&gt;
&lt;h2 id="record-types"&gt;Record Types&lt;/h2&gt;
&lt;h3 id="a"&gt;A:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;IPv4 address associated to domain name.&lt;/li&gt;
&lt;li&gt;Running &lt;code&gt;drill example.com&lt;/code&gt; defaults to returning an A record.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="aaaa"&gt;AAAA:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;IPv6 address association.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drill aaaa example.com&lt;/code&gt; will explicitly return the IPv6 address, if it has one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mail-exchanger-mx"&gt;Mail Exchanger (MX):&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Mail exchanger records containing the emails associated to the domain.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drill mx google.com&lt;/code&gt; will return all the mail exchangers, and each ones priority.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="name-server-ns"&gt;Name Server (NS):&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A list of name servers connected to the domain.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drill ns cloudflare.com&lt;/code&gt; outputs their name server domains.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="pointer-ptr"&gt;Pointer (PTR):&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Pointer file for reverse DNS lookups&lt;/li&gt;
&lt;li&gt;For example, do the following: &lt;code&gt;drill defence.gov.au&lt;/code&gt;. Take the IP address - &lt;code&gt;203.6.74.5&lt;/code&gt; and do a reverse lookup using only the IP address. &lt;code&gt;drill -x 203.6.74.5&lt;/code&gt;. It should return &lt;code&gt;www.defence.gov.au.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;When both the forward (name-to-address) and reverse (address-to-name) entries match, this is known as a &lt;em&gt;forward-confirmed reverse DNS&lt;/em&gt;. Its a weak form of authentication - it proves the IP and domain are owned by the same entity. Sender Policy Framework and MX records are often better indicators of authenticity. Nonetheless, it can be a method used to whitelist a domain, or a reason for other mail exchangers to blacklist, or drop mail to that domain, if the lookup fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="canonical-name-cname"&gt;Canonical Name (CNAME):&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Can be used to alias one name to another, most often to an A record. It operates a bit like a treasure map with clues pointing to the treasure - the A record.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;drill blog.cloudflare.com&lt;/code&gt; returns &lt;code&gt;CNAME cloudflare.ghost.io&lt;/code&gt; which directs to the &lt;code&gt;A&lt;/code&gt; record.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As a example of how to read a zone file, or response with CNAME information:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; NAME TYPE VALUE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--------------------------------------------------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bar.example.com. CNAME foo.example.com.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;foo.example.com. A 192.0.2.23
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This should be interpreted as:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;bar.example.com.&lt;/code&gt; is an alias for the CNAME &lt;code&gt;foo.example.com.&lt;/code&gt;. A client request for &lt;code&gt;bar.example.com.&lt;/code&gt; will be returned &lt;code&gt;foo.example.com.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The next query to the &lt;code&gt;example&lt;/code&gt; domain will be asking for the &lt;code&gt;foo&lt;/code&gt; subdomain. Which will be returned an &lt;code&gt;A&lt;/code&gt; record with address &lt;code&gt;192.0.2.23&lt;/code&gt;.
This adds latency as another round trip must be conducted and generally chaining CNAME&amp;rsquo;s is considered bad form.&lt;/p&gt;
&lt;h3 id="text-record-txt"&gt;Text record (TXT):&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Contains extra data such a sender policy framework and encryption information.&lt;/li&gt;
&lt;li&gt;Often contain valuable information about the domain, for instance:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt; drill telstra.com.au txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;telstra.com.au. &lt;span class="m"&gt;1303&lt;/span&gt; IN TXT &lt;span class="s2"&gt;&amp;#34;v=spf1 include:_spf.telstra.com.au ~all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;telstra.com.au. &lt;span class="m"&gt;1303&lt;/span&gt; IN TXT &lt;span class="s2"&gt;&amp;#34;google-site-verification=GeZnWLAmVtLSHbjW5VEawrbtjQzhSY-e4wsT9VBaJkU&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;telstra.com.au. &lt;span class="m"&gt;1303&lt;/span&gt; IN TXT &lt;span class="s2"&gt;&amp;#34;7EC66119-3393-46B7-9DA9-9AD7643925FA&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The TXT returned some information about the &lt;code&gt;telstra.com.au&lt;/code&gt; domain; they use Google Apps in their systems.&lt;/p&gt;
&lt;p&gt;A better one is &lt;code&gt;vodafone.com.au&lt;/code&gt; who I know for certain use Salesforce (I saw it on their systems in store recently) and that is readily confirmed in their TXT response.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; drill vodafone.com.au txt -t
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vodafone.com.au.	506	IN	TXT	&lt;span class="s2"&gt;&amp;#34;MS=ms76000886&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vodafone.com.au.	506	IN	TXT	&lt;span class="s2"&gt;&amp;#34;407D-EB23-4E8F-A96D-CA1D-27F0-1FD5-B8DC&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vodafone.com.au.	506	IN	TXT	&lt;span class="s2"&gt;&amp;#34;google-site-verification=s_5FhJrgc0EtEXFv6eikEih2i1fjH18PIz2e93qxOLE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vodafone.com.au.	506	IN	TXT	&lt;span class="s2"&gt;&amp;#34;NrkgBWZQJfU+vkg4E7z1RchofBb+WL+Ab0NdlXM6OZ4lo5KJLtj7U8LVEV+iASwT/7Zrn4OqBQ5ETPOrPn9cwA==&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vodafone.com.au.	506	IN	TXT	&lt;span class="s2"&gt;&amp;#34;v=spf1 ip4:54.171.206.163 ip4:101.119.57.42 ip4:119.11.1.42 ip4:101.119.57.43 ip4:119.11.1.43 ip4:101.119.57.14 ip4:119.11.1.14 ip4:216.9.247.4 ip4:216.9.247.48 ip4:216.9.247.49 ip4:216.9.247.68&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; include:_spf.salesforce.com include:herald.responsetek.com include:srs.bis3.ap.blackberry.com include:taleo.net include:production.fxdms.net a:mail1.hutchison.com.au a:mail2.hutchison.com.au a:mail3.hutchison.com.au a:mail4.hutchison.com.au ~all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vodafone.com.au.	506	IN	TXT	&lt;span class="s2"&gt;&amp;#34;BR6ST3VIM&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vodafone.com.au.	506	IN	TXT	&lt;span class="s2"&gt;&amp;#34;MS=ms13634633&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-t&lt;/code&gt; flag indicated the response needed TCP as the message size was too large to fit in one UDP packet.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;drill&lt;/code&gt; lets you know:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; WARNING: The answer packet was truncated&lt;span class="p"&gt;;&lt;/span&gt; you might want to
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;;;&lt;/span&gt; query again with TCP &lt;span class="o"&gt;(&lt;/span&gt;-t argument&lt;span class="o"&gt;)&lt;/span&gt;, or EDNS0 &lt;span class="o"&gt;(&lt;/span&gt;-b &lt;span class="k"&gt;for&lt;/span&gt; buffer size&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="start-of-authority-soa"&gt;Start of Authority (SOA):&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Contains administrative info about a zone such as domain owner, contact details and serial number.&lt;/li&gt;
&lt;li&gt;Serial number is used to determine the currency of the zone&amp;rsquo;s data.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drill SOA taste.com.au
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span class="p"&gt;;;&lt;/span&gt; QUESTION SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span class="p"&gt;;;&lt;/span&gt; taste.com.au. IN SOA
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;span class="p"&gt;;;&lt;/span&gt; ANSWER SECTION:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; taste.com.au. &lt;span class="m"&gt;176&lt;/span&gt; IN SOA dns0.news.com.au. hostmaster.news.com.au. &lt;span class="m"&gt;2019060600&lt;/span&gt; &lt;span class="m"&gt;900&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt; &lt;span class="m"&gt;604800&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each tabbed section in detail:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IN&lt;/code&gt; and &lt;code&gt;SOA&lt;/code&gt;; Internet and Start of Authority.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dns0.news.com.au&lt;/code&gt; is the primary name server.&lt;/li&gt;
&lt;li&gt;The email contact for the domain; &lt;code&gt;hostmaster.news.com.au&lt;/code&gt; is equal to &lt;code&gt;hostmaster@news.com.au&lt;/code&gt;. The first &lt;code&gt;.&lt;/code&gt; should be replaced by an &lt;code&gt;@&lt;/code&gt; symbol.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2019060600&lt;/code&gt; is the current serial number for the domain. Serial numbers are used by other DNS servers to check its trustworthiness in delivering up to date information.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;900&lt;/code&gt; The time in seconds the secondary name server should wait before checking the master&amp;rsquo;s SOA record for changes. Also known as the &lt;code&gt;refresh&lt;/code&gt; rate. More aptly this means; how long am I willing to accept my secondary server having out-of-date information.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;600&lt;/code&gt; is the &lt;code&gt;retry&lt;/code&gt; time it should wait before trying another &lt;code&gt;refresh&lt;/code&gt; if the last one failed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;604800&lt;/code&gt; is the &lt;code&gt;expire&lt;/code&gt; counter in seconds. It lets the secondary name server know how long to hold their information before it is no longer considered authoritative. Generally, this is a large number, and should always be greater than &lt;code&gt;refresh&lt;/code&gt; and &lt;code&gt;retry&lt;/code&gt; counters.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;300&lt;/code&gt; is the &lt;code&gt;minimum&lt;/code&gt; time-to-live in seconds before the records in the zone are considered invalid.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Today, the &lt;code&gt;refresh&lt;/code&gt;, &lt;code&gt;retry&lt;/code&gt;, &lt;code&gt;expire&lt;/code&gt; and &lt;code&gt;minimum&lt;/code&gt; are still important but used much less frequently. RFC&amp;rsquo;s &lt;a href="https://www.ietf.org/rfc/rfc1996.txt"&gt;1996&lt;/a&gt; and &lt;a href="https://www.ietf.org/rfc/rfc2136.txt"&gt;2136&lt;/a&gt; have included the NOTIFY and UPDATE &lt;a href="http://networksorcery.com/enp/protocol/dns.htm#Rcode,%20Return%20code"&gt;opcodes&lt;/a&gt; to allow the master DNS server to send updates to its slaves, rather than waiting for them to poll the master for changes.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;The domain name system is far too technically rich to even scratch the surface in one short post, but hopefully it helps to cover off a few confusing points. If nothing else, it may serve as a launch pad for some to go out and read more on this incredible and interesting protocol. One which we use hundreds of times a day.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want to use a state of the art DNS service that respects privacy, is customisable, easy to use and secure then check out &lt;a href="https://nextdns.io/?from=3bnvwes5"&gt;NextDNS&lt;/a&gt;. It&amp;rsquo;s free to sign up, can be configured on routers, browsers and devices. My android devices all override their system DNS and block ads so effectively it feels like internet is free of them! If you&amp;rsquo;re interested, try it &lt;a href="https://nextdns.io/?from=3bnvwes5"&gt;here&lt;/a&gt; at my affliate link.&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>NATS as C2</title><link>https://danielms.site/zet/2026/nats-as-c2/</link><pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/nats-as-c2/</guid><description>&lt;h1 id="nats-as-c2"&gt;NATS as C2&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://www.sysdig.com/blog/nats-as-c2-inside-a-new-technique-attackers-are-using-to-harvest-cloud-credentials-and-ai-api-keys"&gt;https://www.sysdig.com/blog/nats-as-c2-inside-a-new-technique-attackers-are-using-to-harvest-cloud-credentials-and-ai-api-keys&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using NATS as an outbound connector/transport between my control server and pfSense firewalls - looks like they&amp;rsquo;re also leveraging this same technique. In my usecase its flawless!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been telling security friends that they are sleeping on NATS. Maybe after reading this they&amp;rsquo;ll actually read up on it instead of default to what they know; rabbit and kafka.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Tasks are queued centrally, workers pull and explicitly ack, and a dropped worker returns its in-flight tasks to the queue for redelivery. This matches the architectural argument earlier in this writeup: NATS-as-C2 gives operators durability and at-least-once delivery without bespoke client code.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;NATS servers provide three properties that scanner-pool operators historically had to engineer themselves:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wire-level authorization: Per-subject ACLs are enforced by the broker, not by client-side checks that a captured node can disable.&lt;/li&gt;
&lt;li&gt;One-to-many fan-out: A single publish to result.scan reaches every aggregator without the worker enumerating peers, which improves OPSEC and simplifies horizontal scaling.&lt;/li&gt;
&lt;li&gt;First-class auth and durability: Username/password, TLS, and nkey auth are native, and JetStream provides durable queues so a worker can drop offline without losing its work.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;The KeyHunter operator that the Sysdig TRT discovered is using NATS for the same reasons engineering teams adopt it: subject-scoped authorization, native fan-out, and durable queues. None of those properties alone are unique to legitimate workloads, and applying them to a credential-hunting worker pool produces a botnet that is more liable and scalable than the typical HTTP-panel architecture&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Take away the C2 nerfarious nature of this application and you&amp;rsquo;re left with a legitimate tool that is incredibly powerful and sadly overlooked by many.&lt;/p&gt;
&lt;p&gt;I implore people to give NATS a try.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#nats #c2
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Shunt docs page is live</title><link>https://danielms.site/zet/2026/shunt-docs-page-is-live/</link><pubDate>Sun, 26 Apr 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/shunt-docs-page-is-live/</guid><description>&lt;h1 id="shunt-docs-page-is-live"&gt;Shunt docs page is live&lt;/h1&gt;
&lt;p&gt;I created a &lt;code&gt;mkdocs&lt;/code&gt; page for my NATS based rule router; [shunt].&lt;/p&gt;
&lt;p&gt;I use this extensively in my homelab. For instance,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Making all my Zigbee2MQTT events available in VictoriaMetrics&lt;/li&gt;
&lt;li&gt;Notifications automations by making HTTP requests into my Knative handlers&lt;/li&gt;
&lt;li&gt;Telling me when my plants need watering&lt;/li&gt;
&lt;li&gt;Various other&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some examples use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Telegram hooks for Jellysearr so I know that I need to approve kids requests (and when they are done)&lt;/li&gt;
&lt;li&gt;Synadia Control Plane alert webhooks into Knative which trigger telegram&lt;/li&gt;
&lt;li&gt;Motion sensors lights for my office&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An example shunt rule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;z2m-metrics&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;zigbee2mqtt.*&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http://fn-z2m-metrics.knative-functions.svc.cluster.local/ingest&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;POST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;X-Z2M-Device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{@subject.1}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;passthrough&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;initialDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2s&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;maxDelay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;10s&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will listen for every message on the &lt;code&gt;zigbee2mqtt.*&lt;/code&gt; subject and make a POST with the &lt;code&gt;*&lt;/code&gt; subject token as the header. Internally, my knative function will receive the request stripping the header for our metrics endpoint.&lt;/p&gt;
&lt;p&gt;For the uninitiated, knative functions are HTTP based and thats why we POST into the handler rather than listen on another subject and have &lt;code&gt;shunt&lt;/code&gt; publish to it. Knative eventing works basically the same way but &lt;code&gt;shunt&lt;/code&gt; means we can avoid the overhead of &lt;code&gt;eventing&lt;/code&gt; completely and rely solely on &lt;code&gt;serving&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is a noisy endpoint and shunt works pretty well here.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#shunt #nats #knative
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Zed's new Agent first panel is good</title><link>https://danielms.site/zet/2026/zeds-new-agent-first-panel-is-good/</link><pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/zeds-new-agent-first-panel-is-good/</guid><description>&lt;h1 id="zeds-new-agent-first-panel-is-good"&gt;Zed&amp;rsquo;s new Agent first panel is good&lt;/h1&gt;
&lt;p&gt;I really like Zed&amp;rsquo;s new Agent first panel.&lt;/p&gt;
&lt;p&gt;Bummer that I just spent time setting up my hotkeys to open the agent panel e.g. &lt;code&gt;&amp;lt;leader&amp;gt;ccc&lt;/code&gt; - kind of moot now. But, this is a better change!&lt;/p&gt;
&lt;p&gt;I still prefer using JetBrains products (especially for debugging) but for pure &amp;ldquo;agentic&amp;rdquo; style work (such as project I don&amp;rsquo;t care &lt;em&gt;that&lt;/em&gt; much about) it&amp;rsquo;s
a better, faster experience.&lt;/p&gt;
&lt;p&gt;There might be a future where I&amp;rsquo;d switch solely to Zed over JetBrains - but I still think that future is a way off yet.&lt;/p&gt;
&lt;p&gt;Love seeing teams try new things like this too - the &amp;ldquo;agentic&amp;rdquo; world is in so much flux right now so who knows if they&amp;rsquo;ll even keep this change. I hope they do.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#zed #ide
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Agents should use config directories properly</title><link>https://danielms.site/zet/2026/agents-should-use-config-directories-properly/</link><pubDate>Mon, 13 Apr 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/agents-should-use-config-directories-properly/</guid><description>&lt;h1 id="agents-should-use-config-directories-properly"&gt;Agents should use config directories properly&lt;/h1&gt;
&lt;p&gt;Why do all the agents/LLM&amp;rsquo;s use &lt;code&gt;~/.$foo&lt;/code&gt; instead of &lt;code&gt;~/.config/$foo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Its lazy programming, teaches others that it&amp;rsquo;s okay to do this (it&amp;rsquo;s not) and basically ensures for the rest of time we have our home directories filled up with shit.&lt;/p&gt;
&lt;p&gt;Do a &lt;code&gt;ls -la&lt;/code&gt; on your &lt;code&gt;$HOME&lt;/code&gt; and look at home much junk there is. &lt;code&gt;$XDG_CONFIG_HOME&lt;/code&gt; exists for a reason.&lt;/p&gt;
&lt;p&gt;I feel these companies have a duty of care because everyone will follow them blindly.&lt;/p&gt;
&lt;p&gt;Thing is we can plainly see none of them give a shit. Look at Anthropic stealing IP. Scam Altman (say no more).&lt;/p&gt;
&lt;p&gt;Talk about grinding my gears.&lt;/p&gt;
&lt;p&gt;At least OpenCode gets right - literally one of the few shining lights in this shitshow.&lt;/p&gt;
&lt;p&gt;Culprit list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ollama&lt;/li&gt;
&lt;li&gt;openai (.codex)&lt;/li&gt;
&lt;li&gt;anthropic (.cluade)&lt;/li&gt;
&lt;li&gt;pi&lt;/li&gt;
&lt;li&gt;amp&lt;/li&gt;
&lt;li&gt;crush&lt;/li&gt;
&lt;li&gt;google (.gemini)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #ai
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>TIL: raycast for mac file search</title><link>https://danielms.site/zet/2026/til-raycast-for-mac-file-search/</link><pubDate>Fri, 10 Apr 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/til-raycast-for-mac-file-search/</guid><description>&lt;h1 id="til-raycast-for-mac-file-search"&gt;TIL: raycast for mac file search&lt;/h1&gt;
&lt;p&gt;Geez I feel like a right snapper head.&lt;/p&gt;
&lt;p&gt;I &lt;strong&gt;hate&lt;/strong&gt; finder, in fact I legitimately hate most of MacOS defaults like what hell is wrong with that file explorer - utter trash.&lt;/p&gt;
&lt;p&gt;Anyway TIL that I can use Raycast to actually search like a sane person.&lt;/p&gt;
&lt;p&gt;If I could delete finder and the poor excuse for a file explorer from MacOS I would.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a POS and anyone who likes it is a victim of some Stockholm syndrome like disease also known as the &amp;ldquo;cult of mac&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Otherwise super impressed with my MBP!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #raycast 
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>I built a Tauri app for my home lab</title><link>https://danielms.site/zet/2026/i-built-a-tauri-app-for-my-home-lab/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/i-built-a-tauri-app-for-my-home-lab/</guid><description>&lt;h1 id="i-built-a-tauri-app-for-my-home-lab"&gt;I built a Tauri app for my home lab&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ve been leaning a lot more into AI coding lately.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s made me rethink a couple things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can use &lt;strong&gt;any&lt;/strong&gt; language&lt;/li&gt;
&lt;li&gt;Reliability and error detection at compile time is more important than ever&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rust is a trade-off between compile-time and memory safety at the cost of developer experience and cognitive load. It takes a lot longer to develop a Rust app than a Go one and the results
for &lt;em&gt;most&lt;/em&gt; app&amp;rsquo;s (that I would built) aren&amp;rsquo;t worth the &amp;ldquo;Rust cost&amp;rdquo; IMO.&lt;/p&gt;
&lt;p&gt;AI kind of changes the game here.&lt;/p&gt;
&lt;p&gt;It iterates so fast that Rust on-boarding and learning can be &amp;ldquo;just-in-time&amp;rdquo; or &amp;ldquo;just-enough&amp;rdquo;. Now you can &amp;ldquo;write&amp;rdquo; Rust and learn it as you go - it&amp;rsquo;s like learning to code by
reading someone else&amp;rsquo;s code except its &lt;em&gt;your&lt;/em&gt; code for &lt;em&gt;your&lt;/em&gt; idea.&lt;/p&gt;
&lt;p&gt;And, now you get the sweet compile time safety. It also has the benefit of keeping AI in check - it writes something and breaks an contract somewhere, well it&amp;rsquo;ll detect that and the knock out
the fix super fast.&lt;/p&gt;
&lt;p&gt;I still love Go, I still opt for it (for 95% of things) but think AI is changing the way we look at certain objectives.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;So I wrote a Tauri app. It&amp;rsquo;s a MacOS dock based app for monitoring all my Knative applications status. It connects to your kube context (so technically could monitor any Knative stack).&lt;/p&gt;
&lt;p&gt;It can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ping it (to wake it up)&lt;/li&gt;
&lt;li&gt;view its ingress (if its serving a page/API etc will show it)&lt;/li&gt;
&lt;li&gt;view logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&amp;rsquo;t need too much more than that.&lt;/p&gt;
&lt;p&gt;I found the Tauri experience to be top-notch compared to Wails (a Go Tauri like app builder).&lt;/p&gt;
&lt;p&gt;Repo: &lt;a href="https://github.com/danielmichaels/knative-dash"&gt;https://github.com/danielmichaels/knative-dash&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rust #ai #tauri
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Rust, cap.so and capturing login links</title><link>https://danielms.site/zet/2026/rust-cap.so-and-capturing-login-links/</link><pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/rust-cap.so-and-capturing-login-links/</guid><description>&lt;h1 id="rust-capso-and-capturing-login-links"&gt;Rust, cap.so and capturing login links&lt;/h1&gt;
&lt;p&gt;I recent spun up a &lt;a href="https://cap.so"&gt;cap&lt;/a&gt; instance on my Coolify instance. It&amp;rsquo;s free and open source
which is great but it has some limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only sends magic login/sign up links&lt;/li&gt;
&lt;li&gt;Only supports &lt;a href="https://resend.com"&gt;Resend&lt;/a&gt; as the Email provider&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whilst Resend is great and has a free tier I don&amp;rsquo;t want to use it. Firstly, it only supports a single domain
and it cannot be a wildcard. Secondly, I have a SMTP provider already that I like to use for this sort of stuff.&lt;/p&gt;
&lt;p&gt;I noticed that if you don&amp;rsquo;t set the Resend provider it will output the magic link code to the containers &lt;code&gt;stdout&lt;/code&gt;. Not very helpful when you want to login on your phone.&lt;/p&gt;
&lt;p&gt;It got me thinking - could I trap the login code and forward to my phone somehow?&lt;/p&gt;
&lt;p&gt;Which is when I set Claude to work building a app to do exactly that.&lt;/p&gt;
&lt;p&gt;I recorded a quick &lt;a href="https://youtu.be/SZs7vQHSMLY"&gt;video&lt;/a&gt; of it in action and have the repo available on &lt;a href="https://github.com/danielmichaels/cap-link-notifier"&gt;github&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But basically I created a Rust app that listens to the Cap container stdout for the text containing the code and when found it sends it to my telegram channel.&lt;/p&gt;
&lt;p&gt;Coolify runs Cap as a docker compose file so I built a container and added it to that compose file. Now it exists within that compose network giving easy access to the docker logs.&lt;/p&gt;
&lt;p&gt;Pretty simple and now I can login from anywhere!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rust #coolify
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>TIL: Remarkable can share to screen</title><link>https://danielms.site/zet/2026/til-remarkable-can-share-to-screen/</link><pubDate>Wed, 11 Mar 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/til-remarkable-can-share-to-screen/</guid><description>&lt;h1 id="til-remarkable-can-share-to-screen"&gt;TIL: Remarkable can share to screen&lt;/h1&gt;
&lt;p&gt;If you are on the same network and have the Remarkable software installed it will sync from the tablet to your app on the computer.&lt;/p&gt;
&lt;p&gt;Which is awesome for screen sharing and recording videos on OBS etc.&lt;/p&gt;
&lt;p&gt;Such a great find.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #remarkable
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Dokploy Clourdflared Tailscale and Proxmox</title><link>https://danielms.site/zet/2026/dokploy-clourdflared-tailscale-and-proxmox/</link><pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/dokploy-clourdflared-tailscale-and-proxmox/</guid><description>&lt;h1 id="dokploy-clourdflared-tailscale-and-proxmox"&gt;Dokploy Clourdflared Tailscale and Proxmox&lt;/h1&gt;
&lt;p&gt;I am pretty happy with this little experiment:&lt;/p&gt;
&lt;p&gt;Can I host Dokploy inside my LAN using Proxmox and have an application auto-deploy on git push from GitHub?&lt;/p&gt;
&lt;p&gt;Well, yes.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Installing Dokploy isn&amp;rsquo;t covered here but I just following the PVE Helper Scripts and installed it into a Ubuntu LXC (and then snapshotted it).&lt;/p&gt;
&lt;p&gt;I hooked up GitHub and a particular repo which has a docker-compose file. Then deployed it using that fille. Note that I don&amp;rsquo;t build this but instead pull from &amp;lt;ghcr.io&amp;gt; - this saves a lot of compute/memory on the host Dokploy and circumvents build issues.&lt;/p&gt;
&lt;p&gt;I also setup a Cloudflared container in another Dokploy project using a compose file as well - passing in the API key from Cloudflare. All apples.&lt;/p&gt;
&lt;p&gt;The trickiest and more painful part when setting up the tunnels was the subdomain limitations I found on Cloudflare.&lt;/p&gt;
&lt;p&gt;These did not work:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app.dev.domain.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;These did:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app-dev.domain.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I got the &lt;code&gt;app.dev.domain.com&lt;/code&gt; to work initially but then I needed &lt;code&gt;river.dev.domain.com&lt;/code&gt; but it would not allow another subdomain. I removed the &lt;code&gt;app.dev.&lt;/code&gt; domain and then could using any nested subdomains at all. Instead had to resort to using &lt;code&gt;$app-dev.$domain&lt;/code&gt; which looks dumb IMO. But, it works so moving on.&lt;/p&gt;
&lt;p&gt;Now I got all that working pretty easily. Could access the applications from my LAN on a domain I controlled. But, Dokploy only offers a URL for re-deploying when using a Compose project which means it needs internet access.&lt;/p&gt;
&lt;p&gt;Here comes Tailscale and GitHub actions to the rescue.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Secret&lt;/th&gt;
					&lt;th&gt;Source&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;TS_OAUTH_CLIENT_ID&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Tailscale admin → OAuth clients&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;TS_OAUTH_SECRET&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Tailscale admin → OAuth clients&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DOKPLOY_API_KEY&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Dokploy → Settings → Profile → API&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DOKPLOY_COMPOSE_ID&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;One-time API query above&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;DOKPLOY_URL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;http://&amp;lt;tailscale-ip-or-hostname&amp;gt;:3000&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Github Action snippet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# this step comes after build and push to GHCR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy to Dokploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;needs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;merge&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;success()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Connect to Tailscale&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tailscale/github-action@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oauth-client-id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.TS_OAUTH_CLIENT_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;oauth-secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.TS_OAUTH_SECRET }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tag:ci&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Trigger Dokploy deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; curl -fsSL -X POST \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;${{ secrets.DOKPLOY_URL }}/api/compose.deploy&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#39;Content-Type: application/json&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#34;x-api-key: ${{ secrets.DOKPLOY_API_KEY }}&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -d &amp;#34;{\&amp;#34;composeId\&amp;#34;: \&amp;#34;${{ secrets.DOKPLOY_COMPOSE_ID }}\&amp;#34;}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;I like the OAuth approach as its ephemeral and really wasn&amp;#39;t that much extra work to get set up.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;So now, instead of deploying Dokploy to a VPS costing me money I now get all benefits (and trade-offs too) of running it in my homelab but having it internet accessible. &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;Also it was my first time using Dokploy (I run Coolify in another VPS) and I think its got some nice&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;features but for my usecase I think they&amp;#39;re equally as good. I do like that Dokploy supports multi-instance containers due to its native `docker compose` support. With that also comes the really nice ability to run NATS in an isolated compose setup. &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;#dokploy #tailscale #cicd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>GHA build and push faster</title><link>https://danielms.site/zet/2026/gha-build-and-push-faster/</link><pubDate>Fri, 06 Mar 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/gha-build-and-push-faster/</guid><description>&lt;h1 id="gha-build-and-push-faster"&gt;GHA build and push faster&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ve been using the same GHA build and push for my local/private/personal projects for years and they take forever.&lt;/p&gt;
&lt;p&gt;I asked claude to make it faster and knocked a 15m build to 4m with this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build and Push to GHCR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&gt;main]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;v*.*.*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;REGISTRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghcr.io&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;IMAGE_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.repository }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build ${{ matrix.platform }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.runner }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;linux/amd64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;linux/arm64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-24.04-arm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Docker Buildx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker/setup-buildx-action@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Log in to GHCR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker/login-action@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ env.REGISTRY }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.actor }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Extract metadata&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;meta&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker/metadata-action@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Build and push by digest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker/build-push-action@v6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;platforms&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.platform }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ steps.meta.outputs.labels }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cache-from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;type=gha,scope=${{ matrix.platform }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cache-to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;type=gha,mode=max,scope=${{ matrix.platform }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Export digest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mkdir -p /tmp/digests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;${{ steps.build.outputs.digest }}&amp;#34; &amp;gt; /tmp/digests/${{ matrix.platform == &amp;#39;linux/amd64&amp;#39; &amp;amp;&amp;amp; &amp;#39;amd64&amp;#39; || &amp;#39;arm64&amp;#39; }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Upload digest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/upload-artifact@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;digest-${{ matrix.platform == &amp;#39;linux/amd64&amp;#39; &amp;amp;&amp;amp; &amp;#39;amd64&amp;#39; || &amp;#39;arm64&amp;#39; }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/tmp/digests/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if-no-files-found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retention-days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Merge manifests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;needs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;packages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;write&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Download digests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/download-artifact@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/tmp/digests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;digest-*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;merge-multiple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Docker Buildx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker/setup-buildx-action@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Log in to GHCR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker/login-action@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;registry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ env.REGISTRY }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ github.actor }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Extract metadata&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;meta&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker/metadata-action@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; type=ref,event=branch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; type=semver,pattern={{version}}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; type=semver,pattern={{major}}.{{minor}}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; type=sha,prefix=sha-,format=short&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Create and push manifest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; DIGESTS=$(cat /tmp/digests/amd64 /tmp/digests/arm64 | xargs -I{} echo &amp;#34;${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@{}&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; TAGS=$(echo &amp;#34;${{ steps.meta.outputs.tags }}&amp;#34; | xargs -I{} echo &amp;#34;--tag {}&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; docker buildx imagetools create $TAGS $DIGESTS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Instead of using QEMU.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #cicd
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Remove firefox autocomplete history</title><link>https://danielms.site/zet/2026/remove-firefox-autocomplete-history/</link><pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/remove-firefox-autocomplete-history/</guid><description>&lt;h1 id="remove-firefox-autocomplete-history"&gt;Remove firefox autocomplete history&lt;/h1&gt;
&lt;p&gt;Because I constantly forget how to do this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click form&lt;/li&gt;
&lt;li&gt;Press Down arrow key&lt;/li&gt;
&lt;li&gt;Should be highlighted, if so:&lt;/li&gt;
&lt;li&gt;Shift+Backspace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til 
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Codex has a pretty slick UI</title><link>https://danielms.site/zet/2026/codex-has-a-pretty-slick-ui/</link><pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/codex-has-a-pretty-slick-ui/</guid><description>&lt;h1 id="codex-has-a-pretty-slick-ui"&gt;Codex has a pretty slick UI&lt;/h1&gt;
&lt;p&gt;Contrasting Claude Code against Codex its plainly obvious that Codex&amp;rsquo;s rust implementation is &lt;em&gt;blazingly fast&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Whether its better or not is moot. In the age of AI I do not get why you would use node to build &lt;del&gt;(anything..)&lt;/del&gt; a CLI app. Granted Claude Code kicked off the terminal AI revolution but still like&amp;hellip;why?&lt;/p&gt;
&lt;p&gt;Really impressed by how smooth Codex works in comparison and I hope many more tools adopt Go, Rust, Zig as their UI&amp;rsquo;s (like OpenCode have as well)&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ai #rust #cli
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>I suddenly feel compelled to start a factory at home</title><link>https://danielms.site/zet/2026/i-suddenly-feel-compelled-to-start-a-factory-at-home/</link><pubDate>Tue, 24 Feb 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/i-suddenly-feel-compelled-to-start-a-factory-at-home/</guid><description>&lt;h1 id="i-suddenly-feel-compelled-to-start-a-factory-at-home"&gt;I suddenly feel compelled to start a factory at home&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://youtu.be/hqGFcwyXYI0"&gt;https://youtu.be/hqGFcwyXYI0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know what I&amp;rsquo;d create, build or sell but I do know that I need a homemade factory!&lt;/p&gt;
&lt;p&gt;Amazing what some cheap plywood and Arduino&amp;rsquo;s can net you. More evidence that I need to keep going with my OT learning and to keep setting up my
IoT home network but with more physical controls.&lt;/p&gt;
&lt;p&gt;Some random things I would like to build:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Water tank overflow regulator&lt;/li&gt;
&lt;li&gt;Garden water tank fill control system (guttering fed with active cut over when full)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Random little projects that would be useful around the house and get me more hands on with control systems and hardware.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#factory #iot #youtube
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Singleton row pattern in application design</title><link>https://danielms.site/zet/2026/singleton-row-pattern-in-application-design/</link><pubDate>Thu, 19 Feb 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/singleton-row-pattern-in-application-design/</guid><description>&lt;h1 id="singleton-row-pattern-in-application-design"&gt;Singleton row pattern in application design&lt;/h1&gt;
&lt;p&gt;Anti-pattern? It depends.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m using it in a situation where only one instance of a key can ever exist. New key? We update the existing. It is a global state mechanic. The alternative is an Entity-Attribute-Value table - also a &amp;ldquo;it depends&amp;rdquo; anti-pattern.&lt;/p&gt;
&lt;p&gt;In this case I&amp;rsquo;m creating an identifier with the same value, always. All &lt;code&gt;INSERT&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; operations target the same known &lt;code&gt;id&lt;/code&gt;. Has a nice benefit of making upsert behaviours deterministic.&lt;/p&gt;
&lt;p&gt;Whilst I have this feeling of it being &amp;ldquo;wasteful&amp;rdquo; to use a single table for a single row - I think it works (we&amp;rsquo;ll see what my workmates say!).&lt;/p&gt;
&lt;p&gt;Some alternatives; &lt;code&gt;is_active&lt;/code&gt; &lt;code&gt;BOOLEAN&lt;/code&gt;. This is nice because we have a historical log. We just insert new keys with &lt;code&gt;is_active=1&lt;/code&gt; and set all the others to &lt;code&gt;is_active=0&lt;/code&gt;. Or, &lt;code&gt;ORDER BY created DESC LIMIT 1&lt;/code&gt;. But, it adds some complexity like deactivating old rows when activating the new one, queries are a little more complex (I guess not &lt;em&gt;too&lt;/em&gt; complex - &lt;code&gt;WHERE is_active == 1&lt;/code&gt;). I think the downside here is AFAIK the database doesn&amp;rsquo;t provide a guarantee only a single row is active.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It depends&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Is the right call here but for my current use case it works well enough. Should I ever need more historical analysis then moving to the &lt;code&gt;is_active&lt;/code&gt; model is a no-brainer.&lt;/p&gt;
&lt;p&gt;Also, I think you could have the singleton and a separate history table. I have done this in the past as a cheap timeseries like purview of the changes - it that example it was tracking DNS records over time; current versus historical.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#database #design
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>SCP nats-box creds k8s</title><link>https://danielms.site/zet/2026/scp-nats-box-creds-k8s/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/scp-nats-box-creds-k8s/</guid><description>&lt;h1 id="scp-nats-box-creds-k8s"&gt;SCP nats-box creds k8s&lt;/h1&gt;
&lt;p&gt;Note to self, for k8s local testing a quick way to do some debugging with access to nats is via &lt;code&gt;nats-box&lt;/code&gt;. In control plane you need creds so I just cp a creds file into the container.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;kubectl&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt; &lt;span class="o"&gt;~/&lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="n"&gt;nats&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;box&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;664&lt;/span&gt;&lt;span class="n"&gt;bcb786c&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;wt22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;my&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="n"&gt;nats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then use it from inside the container.&lt;/p&gt;
&lt;p&gt;If you have network path to the cluster/control plane server then you can just use &lt;code&gt;nats&lt;/code&gt; CLI as is. E.g. &lt;code&gt;nats -s $NATS_URL --creds my.creds rtt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#nats #debug #k8s
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>FluxCD soldiers 5</title><link>https://danielms.site/zet/2026/fluxcd-soldiers-5/</link><pubDate>Thu, 12 Feb 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/fluxcd-soldiers-5/</guid><description>&lt;h1 id="fluxcd-soldiers-5"&gt;FluxCD soldiers 5&lt;/h1&gt;
&lt;h2 id="1-force-reconcile-most-common"&gt;1. Force Reconcile (most common)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reconcile a kustomization and pull latest git source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux reconcile kustomization &amp;lt;name&amp;gt; --with-source
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reconcile just the git source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux reconcile &lt;span class="nb"&gt;source&lt;/span&gt; git flux-system
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reconcile a specific HelmRelease&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux reconcile helmrelease &amp;lt;name&amp;gt; -n &amp;lt;namespace&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reconcile a HelmRepository (force re-fetch chart index)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux reconcile &lt;span class="nb"&gt;source&lt;/span&gt; helm &amp;lt;name&amp;gt; -n &amp;lt;namespace&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; You pushed a commit and don&amp;rsquo;t want to wait for the poll interval.
&lt;code&gt;--with-source&lt;/code&gt; on a kustomization reconciles its referenced GitRepository first.&lt;/p&gt;
&lt;h2 id="2-suspend--resume-reset-failure-state"&gt;2. Suspend / Resume (reset failure state)&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux &lt;span class="nb"&gt;suspend&lt;/span&gt; helmrelease &amp;lt;name&amp;gt; -n &amp;lt;namespace&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux resume helmrelease &amp;lt;name&amp;gt; -n &amp;lt;namespace&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;When:&lt;/strong&gt; A HelmRelease has exhausted its retry budget (e.g. 10 failures) and is stuck.
Even after pushing a fix, Flux won&amp;rsquo;t retry because the failure counter is maxed out.
Suspend/resume resets the counter and forces a fresh reconciliation with current values.&lt;/p&gt;
&lt;p&gt;Also works for kustomizations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux &lt;span class="nb"&gt;suspend&lt;/span&gt; kustomization &amp;lt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux resume kustomization &amp;lt;name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why not just reconcile?&lt;/strong&gt; &lt;code&gt;flux reconcile&lt;/code&gt; only triggers a new attempt if retries remain.
Once retries are exhausted, the controller stops trying. Suspend/resume is the reset.&lt;/p&gt;
&lt;h2 id="3-check-status"&gt;3. Check Status&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Overview of all Flux resources&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux get all
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Specific resource types&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux get kustomizations
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux get helmreleases -A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux get sources git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux get sources helm -A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Detailed events on a HelmRelease&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl describe helmrelease &amp;lt;name&amp;gt; -n &amp;lt;namespace&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Watch for changes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux get helmreleases -A -w
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="4-debugging"&gt;4. Debugging&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Check Flux controller logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux logs --kind&lt;span class="o"&gt;=&lt;/span&gt;HelmRelease --name&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;name&amp;gt; --namespace&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;namespace&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Check all Flux controller logs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux logs --all-namespaces
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Verify local values against chart schema before pushing&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo add &amp;lt;repo&amp;gt; &amp;lt;url&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm template &amp;lt;release&amp;gt; &amp;lt;repo&amp;gt;/&amp;lt;chart&amp;gt; -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Check what revision Flux sees&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flux get sources git
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#flux #kubernetes
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>AI on the cow</title><link>https://danielms.site/zet/2026/ai-on-the-cow/</link><pubDate>Mon, 02 Feb 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/ai-on-the-cow/</guid><description>&lt;h1 id="ai-on-the-cow"&gt;AI on the cow&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m AI exhausted. Most AI-first products/business are junk (clawdbot being an temporal example at time of writing).&lt;/p&gt;
&lt;p&gt;This on the other hand is a real, tangible use case. They&amp;rsquo;re running a tiny model on constrained embedded controllers attached to a cows neck.&lt;/p&gt;
&lt;p&gt;It runs inference on the telemetry of the cow in 7ms which (as they state) significantly reduces battery draw. If anomoly is detected then it will
wake the board and send packets of BLE-LE to a base station. The base station then forwards it to the (Go) backend for processing and storing into
TimescaleDB.&lt;/p&gt;
&lt;p&gt;And, I&amp;rsquo;m glossing over all the embedded work they&amp;rsquo;ve done like design their own chips and their own pick-n-place machine to prototype boards!&lt;/p&gt;
&lt;p&gt;Honestly amazing and reminds that I love embedded stuff - might whip out the arduino&amp;rsquo;s and esp32&amp;rsquo;s!&lt;/p&gt;
&lt;p&gt;Link: &lt;a href="https://www.youtube.com/watch?v=E4Ng1iW_tfs"&gt;https://www.youtube.com/watch?v=E4Ng1iW_tfs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#iot #cow #embedded #ai
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>TIL: Go's fnv hash</title><link>https://danielms.site/zet/2026/til-gos-fnv-hash/</link><pubDate>Wed, 14 Jan 2026 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2026/til-gos-fnv-hash/</guid><description>&lt;h1 id="til-gos-fnv-hash"&gt;TIL: Go&amp;rsquo;s fnv hash&lt;/h1&gt;
&lt;p&gt;A colleague introduced me to &lt;code&gt;hash/fnv&lt;/code&gt; from the Go stdlib today.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a very simple and fast hashing lib when you don&amp;rsquo;t need cryptographic hashing.&lt;/p&gt;
&lt;p&gt;Some use cases for me to remember:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hash map keys&lt;/li&gt;
&lt;li&gt;Deduplication&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://go.dev/play/p/YqlSV0GZ3fQ"&gt;demo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #hashing
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Tailscale with pfSense</title><link>https://danielms.site/zet/2025/tailscale-with-pfsense/</link><pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/tailscale-with-pfsense/</guid><description>&lt;h1 id="tailscale-with-pfsense"&gt;Tailscale with pfSense&lt;/h1&gt;
&lt;p&gt;I &lt;strong&gt;finally&lt;/strong&gt; set up Tailscale on pfSense and what a massive improvement to my life it is!&lt;/p&gt;
&lt;p&gt;Being able to connect to Tailscale and then get instant access to everything running on my LAN is massive.&lt;/p&gt;
&lt;p&gt;Now I can do things like host apps in my public Coolify and have that container connected to TS giving it access to non-public assets running in my home network. It also doesn&amp;rsquo;t consume my tailscale machine limits because I&amp;rsquo;m just exposing the subnets - that being said those services do not get a TS routable address. I don&amp;rsquo;t mind though as the direct subnet connection is fine as these services are on static IPs.&lt;/p&gt;
&lt;p&gt;I dislike the Cloudflare tunnels approach - this is much better for &lt;em&gt;this&lt;/em&gt; use case. That being said, I do host Pangolin for other access patterns.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#tailscale
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Github is the Microsoft of VCS</title><link>https://danielms.site/zet/2025/github-is-the-microsoft-of-vcs/</link><pubDate>Tue, 16 Dec 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/github-is-the-microsoft-of-vcs/</guid><description>&lt;h1 id="github-is-the-microsoft-of-vcs"&gt;Github is the Microsoft of VCS&lt;/h1&gt;
&lt;p&gt;And the irony of Microsoft owning Github is not lost on me.&lt;/p&gt;
&lt;p&gt;Github is the de facto, first to market hosted git repo.&lt;/p&gt;
&lt;p&gt;We all know it got market share because it was first to market, in SV and gave you free private repo&amp;rsquo;s from the get go.&lt;/p&gt;
&lt;p&gt;People equate github with git - I&amp;rsquo;m sure some even think Github created git!&lt;/p&gt;
&lt;p&gt;Anyway, after the new pricing for Github Actions self-hosted runners I think its become so painfully obvious that its a dying product.&lt;/p&gt;
&lt;p&gt;GitLab is superior. Fact. End of story.&lt;/p&gt;
&lt;p&gt;If you haven&amp;rsquo;t used GitLab in a commerical production environment you really don&amp;rsquo;t know what you&amp;rsquo;re missing out on. GitLab CE/free-tier just ain&amp;rsquo;t the same.&lt;/p&gt;
&lt;p&gt;Gitea, Forejo aren&amp;rsquo;t it either - they&amp;rsquo;re just GitHub dressed up in a new suit. I mean Gitea modelled their &amp;ldquo;actions&amp;rdquo; after GHA - what the hell.&lt;/p&gt;
&lt;p&gt;I swear I&amp;rsquo;m becoming more GitLab pilled every day!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#git #rant #github #gitlab
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>I miss being a sniper sometimes</title><link>https://danielms.site/zet/2025/i-miss-being-a-sniper-sometimes/</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/i-miss-being-a-sniper-sometimes/</guid><description>&lt;h1 id="i-miss-being-a-sniper-sometimes"&gt;I miss being a sniper sometimes&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=MvvBqcWNW_s"&gt;https://www.youtube.com/watch?v=MvvBqcWNW_s&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&amp;rsquo;t watch much of this stuff any more but they got with that thumbnail!&lt;/p&gt;
&lt;p&gt;Some times I do miss that life. Honing your craft, really getting good at something. Pushing the limits
and being relied upon to make decisions that matter.&lt;/p&gt;
&lt;p&gt;Also having an absolute blast doing it too!&lt;/p&gt;
&lt;p&gt;Being paid to become world class at long range shooting isn&amp;rsquo;t normal. I need to remember I lived a life
that was abnormal - did a job that was a lifestyle and all consuming.&lt;/p&gt;
&lt;p&gt;That time is over but geez it&amp;rsquo;s hard not to miss the &lt;strong&gt;good&lt;/strong&gt; parts.&lt;/p&gt;
&lt;p&gt;Also so good to see Nightforce absolutely dominating - I think I saw a Leupold once the rest were NF&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#random
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Huma API demo repo</title><link>https://danielms.site/zet/2025/huma-api-demo-repo/</link><pubDate>Mon, 24 Nov 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/huma-api-demo-repo/</guid><description>&lt;h1 id="huma-api-demo-repo"&gt;Huma API demo repo&lt;/h1&gt;
&lt;p&gt;I created and pushed a Huma demo application to GitHub.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using Huma in my personal projects for a while now and so far am very happy with it. It &amp;ldquo;just works&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Unless you are writing a trivial API I think you &lt;strong&gt;must&lt;/strong&gt; have an OpenAPI specification available to your users and your future self.&lt;/p&gt;
&lt;p&gt;Huma takes a code first approach - similar to FastAPI. I really like its validation model and how nicely it outputs errors.&lt;/p&gt;
&lt;p&gt;When leveraged with &lt;code&gt;restish&lt;/code&gt; it is a great pairing.&lt;/p&gt;
&lt;p&gt;Repo is at &lt;a href="https://github.com/danielmichaels/huma-demo"&gt;https://github.com/danielmichaels/huma-demo&lt;/a&gt; and show cases unauthenticated routes alongside authenticated.&lt;/p&gt;
&lt;p&gt;API key and Cookie authentication is demonstrated via the API, API docs and a HTML index page.&lt;/p&gt;
&lt;p&gt;Claude did a great job slopping out the HTML/JS to demo the cookie auth.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#huma #go #api
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>TIL about fmt.Appendf</title><link>https://danielms.site/zet/2025/til-about-fmt.appendf/</link><pubDate>Mon, 24 Nov 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/til-about-fmt.appendf/</guid><description>&lt;h1 id="til-about-fmtappendf"&gt;TIL about fmt.Appendf&lt;/h1&gt;
&lt;p&gt;Turn this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{&amp;#34;request_id&amp;#34;:%d}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Int63&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Into&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`{&amp;#34;request_id&amp;#34;:%d}`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Int63&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I really like this; much simpler to type and quite clean in mu opinion.&lt;/p&gt;
&lt;p&gt;Another benefit is reduced allocations. It allows us to extend a byte slice instead of generating new ones.&lt;/p&gt;
&lt;p&gt;The source is quite easy to understand IMO&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ref: https://cs.opensource.google/go/go/+/master:src/fmt/print.go;l=247&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Appendf formats according to a format specifier, appends the result to the byte&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// slice, and returns the updated slice.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Appendf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newPrinter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;doPrintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #til
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Ziglings is an engaging way to learn zig basics</title><link>https://danielms.site/zet/2025/ziglings-is-an-engaging-way-to-learn-zig-basics/</link><pubDate>Sat, 08 Nov 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/ziglings-is-an-engaging-way-to-learn-zig-basics/</guid><description>&lt;h1 id="ziglings-is-an-engaging-way-to-learn-zig-basics"&gt;Ziglings is an engaging way to learn zig basics&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m learning &lt;code&gt;zig&lt;/code&gt; (enough to get by) and it&amp;rsquo;s an engaging and simple way to get started.&lt;/p&gt;
&lt;p&gt;To learn a new language I&amp;rsquo;d typically find a blog or series of articles and follow along. This can be a little tedious because I like to type things out and run the code myself.&lt;/p&gt;
&lt;p&gt;Often I&amp;rsquo;d sick of the tedium of writing the boilerplate, or find articles that miss all the context or supporting code the scaffolds the learning point.&lt;/p&gt;
&lt;p&gt;Ziglings (like Rustlings, which I haven&amp;rsquo;t tried) instead sets up all of that for you in a single repo.&lt;/p&gt;
&lt;p&gt;Each &amp;ldquo;zigling&amp;rdquo; is an intentionally broken program with comments on how to get it working. This is great because you can use whatever IDE you&amp;rsquo;re comfortable
with and test things quickly.&lt;/p&gt;
&lt;p&gt;I found a couple exercises didn&amp;rsquo;t explain clearly for me which is &lt;strong&gt;great&lt;/strong&gt; because it forces me to research why. For me that improves my learning outcomes.&lt;/p&gt;
&lt;p&gt;Anyway I&amp;rsquo;m 30+ &lt;code&gt;ziglings&lt;/code&gt; in and the language has some cool features. So far I think my favourite is &lt;code&gt;errdefer&lt;/code&gt; which is like &lt;code&gt;defer&lt;/code&gt; but only executes on an error.&lt;/p&gt;
&lt;p&gt;Snippet from the exercise:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-zig" data-lang="zig"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;makeNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MyErr&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;u32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Getting number...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Please make the &amp;#34;failed&amp;#34; message print ONLY if the makeNumber()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// function exits with an error:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;errdefer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;getNumber&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;lt;-- This could fail!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;increaseNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;lt;-- This could ALSO fail!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;got {}. &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.{&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Outputs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Getting number...got 5. Getting number...failed!
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Whilst &lt;code&gt;zig&lt;/code&gt; is potentially more low level than I typically need I am interested in learning it because of that. I need a reason to go deeper in my
computer science fundamentals (I am self-taught).&lt;/p&gt;
&lt;p&gt;After the exercises I think I&amp;rsquo;ll write a simple JWT decoder CLI - it&amp;rsquo;s something I use a lot in my &lt;code&gt;$dayjob&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#zig #learning
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Claude Code update</title><link>https://danielms.site/zet/2025/claude-code-update/</link><pubDate>Fri, 07 Nov 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/claude-code-update/</guid><description>&lt;h1 id="claude-code-update"&gt;Claude Code update&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;What cannot be understood will be rewritten - Bill Kennedy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Okay I&amp;rsquo;ve come around on this tool a bit.&lt;/p&gt;
&lt;p&gt;Used correctly with intent and under firm guidance, it can be very powerful.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s still a massive idiot often enough that its brilliance becomes offset to the point where its untrustworthy.&lt;/p&gt;
&lt;p&gt;I am not convinced anyone blindly accepting this code won&amp;rsquo;t be bitten in the arse badly in the future. It writes excellent legacy code.&lt;/p&gt;
&lt;p&gt;That being said, I believe it is a massive help more often than not (when wielded correctly). My main use cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rubber ducking&lt;/li&gt;
&lt;li&gt;Architectural conversations&lt;/li&gt;
&lt;li&gt;Writing code when &lt;strong&gt;I know exactly&lt;/strong&gt; what I want, and ensuring it does exactly that&lt;/li&gt;
&lt;li&gt;Questioning my assumptions/code (basically rubber ducky as well)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Where I &lt;strong&gt;really&lt;/strong&gt; love it is writing throwaway code.&lt;/p&gt;
&lt;p&gt;Like a lot of dev&amp;rsquo;s there is literally not enough time to do all the fun/interesting things you can think up. Writing code takes time
and I have a life outside of my chair. But, with Claude I can speed run some of these and get it to write pure legacy code because I literally
don&amp;rsquo;t give two hoots if it blows up later - it&amp;rsquo;s an experiment, fun, exploration, single-shot script etc&lt;/p&gt;
&lt;p&gt;For that, Claude is amazing and worth the money.&lt;/p&gt;
&lt;p&gt;As an example, I have a Brother thermal printer and hate that I have to plug it into a laptop/desktop to print stuff. So I decided I&amp;rsquo;d turn it into a
always available service on my network. Plug it into a NUC/pi, write a flask server with a couple endpoints, use those to talk over &lt;code&gt;pyusb&lt;/code&gt; and print
the labels my wife needs. Tell claude to create the HTML forms and then use the forms submit data to the flask server. Put both on tailscale. Dockerise so I can run the flask server on my LAN (connected to printer), and let other services interact with it.&lt;/p&gt;
&lt;p&gt;I could absolutely do all this without AI but it went from a weekends work to a couple hours (80% of that burnt just trying to get the printer to work because honestly f*ck printers bro).&lt;/p&gt;
&lt;p&gt;So yeah, I&amp;rsquo;m warmed to Claude Code but its still just a tool to be used. My next big challenge is to learn &lt;code&gt;zig&lt;/code&gt; without just relying on AI - if I can learn &lt;code&gt;zig&lt;/code&gt; with the help of AI but not be so reliant on it that I don&amp;rsquo;t actually learn it, then I&amp;rsquo;ll chalk that up as a big win too.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#claude #ai
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>restish is a great API CLI tool</title><link>https://danielms.site/zet/2025/restish-is-a-great-api-cli-tool/</link><pubDate>Mon, 29 Sep 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/restish-is-a-great-api-cli-tool/</guid><description>&lt;h1 id="restish-is-a-great-api-cli-tool"&gt;restish is a great API CLI tool&lt;/h1&gt;
&lt;p&gt;I really like &lt;a href="https://huma.rocks"&gt;huma&lt;/a&gt;. I think every API no matter how small should aim to have a OpenAPI document. It future proofs you, and with tools like Huma or &lt;a href="https://goa.design"&gt;goa&lt;/a&gt; its built in.&lt;/p&gt;
&lt;p&gt;In the Huma docs the creator recommends a tool called &lt;a href="http://rest.sh"&gt;restish&lt;/a&gt; - of which he is also the author. I played with it but to be honest didn&amp;rsquo;t really explore well enough to &amp;ldquo;get&amp;rdquo; it. Now I do.&lt;/p&gt;
&lt;p&gt;If you have a public OpenAPI spec/doc you can add it to a &lt;code&gt;restish&lt;/code&gt; namespace. Doing this saves the spec and lets &lt;code&gt;restish&lt;/code&gt; &amp;ldquo;see&amp;rdquo; all of it including its request and response bodies.&lt;/p&gt;
&lt;p&gt;Heres an example of Synadia Clouds API (it&amp;rsquo;s long):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;API &lt;span class="k"&gt;for&lt;/span&gt; Synadia Cloud
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Usage:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; restish prod &lt;span class="o"&gt;[&lt;/span&gt;flags&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; restish prod &lt;span class="o"&gt;[&lt;/span&gt;command&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Signing Key Groups Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; copy-account-sk-group Copy Account SK Group
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-account-sk-group Create Account Signing Key Group
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-account-sk Delete Account Signing
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-account-sk-group Delete Account Signing Key Group
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-account-sk Get Account Signing
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-account-sk-group Get Account Signing Key Group
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-account-sk-group List Account Signing Key Groups
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-account-sk-group-keys List Signing Keys
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rotate-account-sk Roate Active Signing Key
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-account-sk Update Account Signing
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-account-sk-group Update Account Signing Key Group
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NATS Users Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; assign-nats-user-team-app-user Assign Team App User to NATS User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; copy-nats-user Copy nats user
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-user Create NATS User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-nats-user Delete NATS User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; download-nats-user-bearer-jwt Get Bearer JWT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; download-nats-user-creds Get Creds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; download-nats-user-http-gw-token Get HTTP Gateway Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-nats-user Get NATS User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-account-sk-group-users List NATS Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-nats-user-connections List NATs User Connections
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-nats-user-issuances List nats user issuances
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-nats-user-team-app-users List Team App Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-users List NATS Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rotate-nats-user Rotate nats user nkey and seed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; un-assign-nats-user-team-app-user Unassign Team App User from NATS User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-nats-user Update NATS User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Accounts Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-account Create Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-or-update-nats-user-revocation Create or Update Revocation &lt;span class="k"&gt;for&lt;/span&gt; a NATS User NKey
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-account Delete Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-nats-user-revocation Delete a &lt;span class="k"&gt;for&lt;/span&gt; a NATS User NKey
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-account Get Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-account-export Export Account Seeds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-account-info Get Account Info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-account-metrics Get Account Metrics
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-nats-user-revocation Get Revocation &lt;span class="k"&gt;for&lt;/span&gt; a NATS User NKey
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-account-connections List Account Connections
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-accounts List Accounts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-accounts-overview-metrics List Accounts overview metrics
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-agent-tokens List Agent Tokens
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nats-core-websocket-viewer Subscribe to a NATS Core subject over websockets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; unmanage-account Unmanage Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-account Update Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Alerts Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; acknowledge-alert Acknowledge Alert
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-alert-rule Create Account Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-system-alert-rule Create System Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-alert-rule Delete Account Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-system-alert-rule Delete System Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-alert Get Alert
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-alert-rule Get Account Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-system-alert-rule Get System Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-alert-rules List Account Alert Rules
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-alerts List Alerts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-system-alert-rules List System Alert Rules
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; run-alert-rule Run Account Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; run-system-alert-rule Run System Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-alert-rule Update Account Alert Rule
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-system-alert-rule Update System Alert Rules
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App Users Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; assign-account-team-app-user Assign Team App User to Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; assign-system-team-app-user Assign Team App User to System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; assign-team-app-user Assign App User to Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-app-user Create App User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-app-user Delete App User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-app-user Get App User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-current-agent-token Get Current Agent Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-account-team-app-users List Account Team App Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-app-user-roles List Roles
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-app-users List App Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-system-team-app-users List System Team App Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-team-app-users List App Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; un-assign-account-team-app-user Unassign Team App User from Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; un-assign-system-team-app-user Unassign Team App User from System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; un-assign-team-app-user Unassign App User from Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-app-user Update App User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-team-app-user Update App User Team Assignment
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;JetStream Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-pull-consumer Delete Pull Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-push-consumer Delete Push Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-jet-stream-placement-options Get JetStream Placement Options
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-pull-consumer-info Get Pull Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-push-consumer-info Get Push Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-jet-stream-assets List JetStream Assets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-pull-consumer Update Pull Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-push-consumer Update Push Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;JetStream: KV Buckets Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-kv-bucket Create KV Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-kv-pull-consumer Create Pull Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-kv-push-consumer Create Push Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-kv-bucket Delete KV Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-kv-bucket Get KV Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-kv-buckets List KV buckets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-kv-consumers List Consumers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-kv-bucket Update KV Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;JetStream: Mirrors Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-mirror Create Mirror
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-mirror-pull-consumer Create Pull Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-mirror-push-consumer Create Push consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-mirror Delete Mirror
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-mirror Get Mirror
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-mirror-consumers List Consumers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-mirrors List Mirrors
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-mirror Update Mirror
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;JetStream: Object Buckets Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-obj-pull-consumer Create Pull Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-obj-push-consumer Create Push Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-object-bucket Create Object Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-object-bucket Delete Object Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-object-bucket Get Object Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-obj-consumers List Consumers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-object-buckets List Object buckets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-object-bucket Update Object Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;JetStream: Streams Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-pull-consumer Create Pull Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-push-consumer Create Push Consumer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-stream Create Stream
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-stream Delete Stream
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-stream-info Get Stream
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-streams List Streams
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; purge-kv-bucket Purge KV Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; purge-mirror Purge Mirror
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; purge-obj-bucket Purge Object Bucket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; purge-stream Purge Stream
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-stream Update Stream
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sharing Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-stream-export Create Stream Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-stream-import Create Stream Import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-stream-shares Create Stream Shares
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-subject-export Create Subject Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-subject-import Create Subject Import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-subject-shares Create Subject Shares
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-stream-export Delete Stream Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-stream-import Delete Stream Import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-stream-share Delete Stream Share
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-subject-export Delete Subject Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-subject-import Delete Subject Import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-subject-share Delete Subject Share
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-stream-export Get Stream Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-stream-import Get Stream Import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-subject-export Get Subject Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-subject-import Get Subject Import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-stream-exports List Stream Exports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-stream-exports-shared List Shared Stream Exports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-stream-imports List Stream Imports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-stream-shares List Stream Shares
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-subject-exports List Subject Exports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-subject-exports-shared List Shared Subject Exports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-subject-imports List Subject Imports
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-subject-shares List Subject Shares
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-subject-export Update Subject Export
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-subject-import Update Subject Import
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Agent Tokens Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-agent-token Delete Agent Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Auth Callout Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; add-auth-callout-target-account Configure Target Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; add-auth-callout-user Create Auth Callout User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-auth-callout Delete Auth Callout Config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-auth-callout-target-account Delete Target Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-auth-callout-user Delete Control Account User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-auth-callout Auth Callout Config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-auth-callout-authenticators Get List of Available Authenticators
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-auth-callout-target-accounts Get Target Account List
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-auth-callout-users Get Target Account List
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Authorization Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; check Check Authz Decisions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-policies Get Policy List
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-roles Get Authz roles List
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Component Versions Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-available-component-versions Get Available Component Versions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-available-component-versions-by-type Get Available Component Versions By Type
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Invitations Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; decide-invitation Accept or reject team invitation
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; invite-app-user Invite App Users
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-invitations List of pending invitations
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;JetStream: Consumers Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-consumers List Consumers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NATS User Issuances Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-nats-user-issuance Get nats user issuance
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Session Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; accept-terms Accept terms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-app-user-access-token Create Personal Access Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-personal-access-token Delete Personal Access Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-personal-access-token Get Personal Access Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-version Get Version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; leave-team Leave Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-app-user-access-tokens List Personal Access Tokens
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-personal-access-token Update Personal Access Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Platform Components Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; platform-component-connect Connect a Platform Component
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App Service Accounts Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-app-service-account Create App Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-app-service-account-token Create Acess Token &lt;span class="k"&gt;for&lt;/span&gt; App Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-app-service-account Delete App Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-app-service-account-token Delete App Service Account Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-app-service-account Get App Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-app-service-account-token Get App Service Account Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-app-service-account-tokens List Access Tokens &lt;span class="k"&gt;for&lt;/span&gt; App Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-app-service-accounts List App Service Accounts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-app-service-account Update App Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-app-service-account-token Update App Service Account Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Team Service Accounts Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-team-service-account-token Create Acess Token &lt;span class="k"&gt;for&lt;/span&gt; Team Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-team-service-account Delete Team Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-team-service-account-token Delete Team Service Account Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-team-service-account Get Team Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-team-service-account-token Get Team Service Account Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-team-service-account-tokens List Access Tokens &lt;span class="k"&gt;for&lt;/span&gt; Team Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-team-service-account Update Team Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-team-service-account-token Update Team Service Account Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Systems Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bulk-share Share assets across accounts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-system Create System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-system Delete System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; enable-auth-callout Enable Auth Callout For System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-component-token Get a component access token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-system Get System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-system-audit-job-result Get Audit Job Result
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-system-audit-job-status Get Audit Job Status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-system-export Export System Seeds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-system-limits Get System Limits
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-system-prometheus-metrics Get Prometheus Metrics
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; import-account Import Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; import-user Import User
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-auth-callout-configs List Auth Callout Configs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-clusters List Clusters
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-connections List Connections
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-servers List Servers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-system-audit-jobs List Audit Jobs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-system-info-accounts List System Accounts Info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-system-info-servers List System Servers info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-team-systems List Systems
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rotate-agent-token Rotate Agent Token
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; run-system-audit-check Run System Audit Check
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; system-jwt-sync Re-sync JWTs of all accounts in this system
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; unmanage-system Unmanage System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-platform-components Update Platform Components &lt;span class="k"&gt;for&lt;/span&gt; System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-system Update System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Teams Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-team Create Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; create-team-service-account Create Team Service Account
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-team Delete Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-team Get Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-team-limits Get Team Limits
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; import-system Import a System
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-team-info-app-users List info of App Users in Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-team-service-accounts List Team Service Accounts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-teams List Teams
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-team Update Team
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Workloads Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-workload-limits Get Workloads limits
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yep, that is every single endpoint from the doc.&lt;/p&gt;
&lt;p&gt;If I want to list all consumers but don&amp;rsquo;t remember what the request body is?&lt;/p&gt;
&lt;p&gt;Just use &lt;code&gt;restish &amp;lt;namespace&amp;gt; list-consumers&lt;/code&gt; and it&amp;rsquo;ll print this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;List Consumers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;## Argument Schema:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; stream-id: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;## Response 200 (application/json)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Success
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; items*: &lt;span class="o"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; allOf&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ack_floor*: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; consumer_seq*: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; last_active: &lt;span class="o"&gt;(&lt;/span&gt;string nullable:true&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; stream_seq*: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cluster: allOf&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; leader: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; name: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; replicas: &lt;span class="o"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; allOf&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; active*: &lt;span class="o"&gt;(&lt;/span&gt;integer format:int64&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; current*: &lt;span class="o"&gt;(&lt;/span&gt;boolean&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; lag: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; name*: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; offline: &lt;span class="o"&gt;(&lt;/span&gt;boolean&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; config*: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ack_policy*: &lt;span class="o"&gt;(&lt;/span&gt;string enum:none,all,explicit&lt;span class="o"&gt;)&lt;/span&gt; enums have been changed to match UnmarshalJSON in https://github.com/nats-io/jsm.go/blob/main/api/consumers.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ack_wait: &lt;span class="o"&gt;(&lt;/span&gt;integer format:int64&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; backoff: &lt;span class="o"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;integer format:int64&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; deliver_group: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; deliver_policy*: &lt;span class="o"&gt;(&lt;/span&gt;string enum:all,last,new,by_start_sequence,by_start_time,last_per_subject&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; deliver_subject: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; description: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; direct: &lt;span class="o"&gt;(&lt;/span&gt;boolean&lt;span class="o"&gt;)&lt;/span&gt; Don&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t add to general clients.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; durable_name: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; filter_subject: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; filter_subjects: &lt;span class="o"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; flow_control: &lt;span class="o"&gt;(&lt;/span&gt;boolean&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; headers_only: &lt;span class="o"&gt;(&lt;/span&gt;boolean&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; idle_heartbeat: &lt;span class="o"&gt;(&lt;/span&gt;integer format:int64&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; inactive_threshold: &lt;span class="o"&gt;(&lt;/span&gt;integer format:int64&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; max_ack_pending: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; max_batch: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; max_bytes: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; max_deliver: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; max_expires: &lt;span class="o"&gt;(&lt;/span&gt;integer format:int64&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; max_waiting: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mem_storage: &lt;span class="o"&gt;(&lt;/span&gt;boolean&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; metadata: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;any&amp;gt;: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; name: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; num_replicas*: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; opt_start_seq: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; opt_start_time: &lt;span class="o"&gt;(&lt;/span&gt;string nullable:true&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rate_limit_bps: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; replay_policy*: &lt;span class="o"&gt;(&lt;/span&gt;string enum:instant,original&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sample_freq: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; created*: &lt;span class="o"&gt;(&lt;/span&gt;string format:date-time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delivered*: &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; consumer_seq*: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; last_active: &lt;span class="o"&gt;(&lt;/span&gt;string nullable:true&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; stream_seq*: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; name*: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; num_ack_pending*: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; num_pending*: &lt;span class="o"&gt;(&lt;/span&gt;integer min:0 max:1.8446744073709552e+19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; num_redelivered*: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; num_waiting*: &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; push_bound: &lt;span class="o"&gt;(&lt;/span&gt;boolean&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; stream_name*: &lt;span class="o"&gt;(&lt;/span&gt;string&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ts*: &lt;span class="o"&gt;(&lt;/span&gt;string format:date-time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;## Responses 400/401/403/404&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# truncated...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To me this makes using API&amp;rsquo;s using a CLI &lt;strong&gt;actually&lt;/strong&gt; possible without consulting the docs.&lt;/p&gt;
&lt;h2 id="walkthrough"&gt;Walkthrough&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s walkthrough how to set all this up by using the &lt;code&gt;restish&lt;/code&gt; example API.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;restish api configure example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# next steps are interactive&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;restish api configure example https://api.rest.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; ? Select option Save and &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ve told &lt;code&gt;restish&lt;/code&gt; to associate &lt;a href="https://api.rest.sh"&gt;https://api.rest.sh&lt;/a&gt; with the namespace of &lt;code&gt;example&lt;/code&gt;. This is how we can segresgate many different
API&amp;rsquo;s from one another.&lt;/p&gt;
&lt;p&gt;Now we can auto detect what endpoints exist.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;restish example&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Which returns,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Usage:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; restish example &lt;span class="o"&gt;[&lt;/span&gt;flags&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; restish example &lt;span class="o"&gt;[&lt;/span&gt;command&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Echo Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-echo 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-echo 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; patch-echo 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; post-echo 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; put-echo 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Books Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; delete-book 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-book 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-books 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; patch-book Patch book
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; put-book 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Caching Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-cached 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Example Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-example 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Images Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-image 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; list-images 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Status Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-status 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Types Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; get-types-example 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; put-types-example 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I want to &lt;code&gt;put-book&lt;/code&gt; but don&amp;rsquo;t know what I need to provide in the request body.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# restish example put-book -h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# truncating some output for brevity
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;## Argument Schema:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; book-id: (string)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;## Option Schema:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --if-match: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (string)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --if-none-match: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (string)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --if-modified-since: (string format:date-time-http) Succeeds if the server&amp;#39;s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;resource date is more recent than the passed date.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --if-unmodified-since: (string format:date-time-http) Succeeds if the server&amp;#39;s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;resource date is older or the same as the passed date.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;## Input Example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;author&amp;#34;: &amp;#34;string&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;published&amp;#34;: &amp;#34;2020-05-14T23:44:51-07:00&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;rating_average&amp;#34;: 1,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ratings&amp;#34;: 1,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;recent_ratings&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;date&amp;#34;: &amp;#34;2020-05-14T23:44:51-07:00&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;rating&amp;#34;: 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;title&amp;#34;: &amp;#34;string&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that we know what the request requires we can put a book into the collection&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;author&amp;#34;: &amp;#34;Someone&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;published&amp;#34;: &amp;#34;1900-05-14T00:00:00-12:00&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;rating_average&amp;#34;: 4,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;ratings&amp;#34;: 30,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;title&amp;#34;: &amp;#34;My book is good&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; restish example put-book good-book
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# returns HTTP/2.0 204 No Content&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And &lt;code&gt;restish example list-books&lt;/code&gt; shows it in the collection&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;modified:&lt;/span&gt; &lt;span class="err"&gt;2025-09-29T03:36:11.74478507Z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;url:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;/books/the-demon-haunted-world&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;yWWo/Pq0gMgjMNgWw+K+Wg==&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;modified:&lt;/span&gt; &lt;span class="err"&gt;2025-09-29T03:36:11.74478507Z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;url:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;/books/the-fabric-of-the-cosmos&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;qhTFCaoJg92NnpRTMJzsEA==&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;modified:&lt;/span&gt; &lt;span class="err"&gt;2025-09-29T03:42:34.560280084Z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;url:&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;/books/good-book&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3Bvek8xG1BB3i+g4kvDzFg==&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;p&gt;Of course you can also just use it like you would &lt;code&gt;curl&lt;/code&gt;, here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; restish api.github.com/users/rs &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;{name, company, location, login, bio}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;Olivier Poitrey&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;company&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;Netflix&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;location&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;Silicon Valley, California, USA&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;login&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;rs&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="s2"&gt;&amp;#34;bio&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;Director of Engineering at Netflix\r\nCo-Founder &amp;amp; ex-CTO of Dailymotion\r\nCo-Founder of NextDNS&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is a lot more to cover but thats some of the parts I really like.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;restish&lt;/code&gt; is a tool that makes it much easier to stay in the terminal whilst interacting with a unfamiliar or complex API &lt;em&gt;without&lt;/em&gt; needing the docs.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#restish #api
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>TIL about Go's json.Indent and when to use it</title><link>https://danielms.site/zet/2025/til-about-gos-json.indent-and-when-to-use-it/</link><pubDate>Fri, 19 Sep 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/til-about-gos-json.indent-and-when-to-use-it/</guid><description>&lt;h1 id="til-about-gos-jsonindent-and-when-to-use-it"&gt;TIL about Go&amp;rsquo;s json.Indent and when to use it&lt;/h1&gt;
&lt;p&gt;I needed to &lt;code&gt;json.Unmarshal&lt;/code&gt; then &lt;code&gt;json.MarshalIndent&lt;/code&gt; a large amount of JSON. In this was a set of very large numbers.&lt;/p&gt;
&lt;p&gt;I learnt that this causes precision issues because unmarshalling to an &lt;code&gt;interface{}&lt;/code&gt; will use &lt;code&gt;float64&lt;/code&gt; for numbers.&lt;/p&gt;
&lt;p&gt;This caused a lot of head scratching, and all I can say is thank god for the debugger! Those who &amp;ldquo;don&amp;rsquo;t use debuggers&amp;rdquo;, well, I tip my hat to you because &lt;code&gt;print&lt;/code&gt; debugging would not have go me through this pickle.&lt;/p&gt;
&lt;p&gt;Example JSON&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;account_details&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reserved_memory&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18446744073709551615&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;reserved_storage&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18446744073709551615&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Was raising:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;decode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0001.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cannot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unmarshal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18446744073709552000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reserved_memory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To achieve my aims and keep the numbers exactly the same, &lt;code&gt;json.Indent&lt;/code&gt; can be used.&lt;/p&gt;
&lt;p&gt;My takeaway:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;json.Indent&lt;/code&gt; is for pure text manipulation&lt;/li&gt;
&lt;li&gt;&lt;code&gt;json.MarshalIndent&lt;/code&gt; is for parsing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I made a quick &lt;a href="https://go.dev/play/p/qFKb23mUYuz"&gt;https://go.dev/play/p/qFKb23mUYuz&lt;/a&gt; link showing it in action.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #json
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>ROG PG278Q Won't start - fix</title><link>https://danielms.site/zet/2025/rog-pg278q-wont-start---fix/</link><pubDate>Wed, 17 Sep 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/rog-pg278q-wont-start---fix/</guid><description>&lt;h1 id="rog-pg278q-wont-start---fix"&gt;ROG PG278Q Won&amp;rsquo;t start - fix&lt;/h1&gt;
&lt;p&gt;This monitor is a POS. Whenever we get a power outage this thing has issues starting back up.&lt;/p&gt;
&lt;p&gt;I think the capacitors are faulty and the only way I&amp;rsquo;ve been able to &lt;em&gt;semi&lt;/em&gt;-reliably get it back up and going is to turn it off for 30+ seconds. This seems to discharge the capacitors and then it will start.&lt;/p&gt;
&lt;p&gt;It happens infrequently enough that I forget what to do to fix it.&lt;/p&gt;
&lt;p&gt;Still, it doesn&amp;rsquo;t fix the issue where this monitor loses connectivity with my Dell dock requiring me to pull the cable out and switch display ports. That happens 3-6x a day.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll never buy another ASUS monitor again.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#asus #rog #rant
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Remarkable 3.22 SSH</title><link>https://danielms.site/zet/2025/remarkable-3.22-ssh/</link><pubDate>Tue, 16 Sep 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/remarkable-3.22-ssh/</guid><description>&lt;h1 id="remarkable-322-ssh"&gt;Remarkable 3.22 SSH&lt;/h1&gt;
&lt;p&gt;I finally got access to 3.22 which supports handwriting search! This is great because I only use it for hand writing.&lt;/p&gt;
&lt;p&gt;It also allows the (for lack of proper wording) side panel to be snapped to the top bar instead. This unlocks a little extra writing room
on the edges as I like to have the panel always visible.&lt;/p&gt;
&lt;p&gt;Annoyingly, 3.22 brings a new security feature; SSH disabled over Wifi by default.&lt;/p&gt;
&lt;p&gt;To enable it you have to plug it into a internet capable device (e.g. laptop), wait for it to get a new IP address and the SSH into it.
Once in, run &lt;code&gt;rm-ssh-over-wlan on&lt;/code&gt;. SSH over Wifi will now be enabled.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#remarkable
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Disable Firefox AI summary</title><link>https://danielms.site/zet/2025/disable-firefox-ai-summary/</link><pubDate>Thu, 04 Sep 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/disable-firefox-ai-summary/</guid><description>&lt;h1 id="disable-firefox-ai-summary"&gt;Disable Firefox AI summary&lt;/h1&gt;
&lt;p&gt;I don&amp;rsquo;t want your summary or crappy AI force fed into my browser.&lt;/p&gt;
&lt;p&gt;To disable, navigate to &lt;code&gt;about:config&lt;/code&gt; and paste in &lt;code&gt;browser.ml.chat.enabled&lt;/code&gt;. Set it to &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Fixed.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m getting AI fatigue.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ai #firefox
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Working across timezones</title><link>https://danielms.site/zet/2025/working-across-timezones/</link><pubDate>Wed, 03 Sep 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/working-across-timezones/</guid><description>&lt;h1 id="working-across-timezones"&gt;Working across timezones&lt;/h1&gt;
&lt;p&gt;This week I&amp;rsquo;ve had meetings across several timezones, delivered a demo at 0200 (my time) and still got heaps of work done.&lt;/p&gt;
&lt;p&gt;In the lead up to the demo I&amp;rsquo;ve been chatting with colleagues in Europe and the US. Being part of a distributed global team
means some times &lt;em&gt;out of hours&lt;/em&gt; calls are required.&lt;/p&gt;
&lt;p&gt;When I explain that I&amp;rsquo;m talking to someone at 2100 or delivering a demo at 0200 they think two things; I&amp;rsquo;m crazy and that I have no &amp;ldquo;work life balance&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I think they&amp;rsquo;re wrong.&lt;/p&gt;
&lt;p&gt;For me, it works. I can hand off to others, or wake up to find &lt;em&gt;x&lt;/em&gt; project is no longer blocked freeing me up to start working on it immediately.&lt;/p&gt;
&lt;p&gt;As for the late night calls, they aren&amp;rsquo;t so bad. I have &lt;em&gt;a lot&lt;/em&gt; of experience working &lt;strong&gt;long&lt;/strong&gt; hours at odd times often cold, wet and hungry.&lt;/p&gt;
&lt;p&gt;Going to bed at 2200, waking up at 0145 to deliver a demo from the comfort of my warm house, then going back to my comfy bed.. that is a privilege!&lt;/p&gt;
&lt;p&gt;That is life on easy mode. Some times, perspective is all you need.&lt;/p&gt;
&lt;p&gt;Being woken for picket at 0200 after only getting to bed at 0000, doing an hour on watch, then waking again at 0500 before first light - only then to do another full day&amp;rsquo;s patrolling. That&amp;rsquo;s hard! Software development is easy in comparison.&lt;/p&gt;
&lt;p&gt;An easy, enjoyable trade-off. I love this career!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Uses</title><link>https://danielms.site/uses/</link><pubDate>Sun, 17 Aug 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/uses/</guid><description>&lt;p&gt;This is a list of software, tools, and stuff that I use on a regular basis.&lt;/p&gt;
&lt;h2 id="development"&gt;Development&lt;/h2&gt;
&lt;h3 id="editors"&gt;Editors&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GoLand&lt;/strong&gt; - I write a lot of Go&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PyCharm&lt;/strong&gt; - I used to write a lot of Python&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webstorm&lt;/strong&gt; - I write typescript because I have to&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zed.dev"&gt;Zed&lt;/a&gt; for light scripting/reading code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NeoVim&lt;/strong&gt; or just Vim for everything else&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="terminal--shell"&gt;Terminal &amp;amp; Shell&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ghostty.org"&gt;Ghostty&lt;/a&gt; - For my Mac&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wezterm.org/index.html"&gt;WezTerm&lt;/a&gt; - On Linux&lt;/li&gt;
&lt;li&gt;&lt;a href="https://extensions.gnome.org/extension/3780/ddterm"&gt;ddterm&lt;/a&gt; - Dropdown terminal for linux&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ohmyz.sh"&gt;Zsh&lt;/a&gt; with Oh My Zsh - Shell with useful plugins&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ohmyz.sh"&gt;Starship&lt;/a&gt; cross-shell prompt&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zellij.dev"&gt;zellij&lt;/a&gt; a better TMUX experience&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="development-tools"&gt;Development Tools&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://task.dev"&gt;task&lt;/a&gt; a better &lt;code&gt;makefile&lt;/code&gt;. Use it in every project.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chezmoi.io"&gt;chezmoi&lt;/a&gt; for managing dotfiles, scripts and more across machines backed by git&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/alexellis/arkade"&gt;arkade&lt;/a&gt; easily install kubernetes tools (useful more so on Linux as &lt;code&gt;brew&lt;/code&gt; is pretty good)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rs/curlie"&gt;curlie&lt;/a&gt; I alias this to &lt;code&gt;curl&lt;/code&gt; as its a dropline replacement with better defaults&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jesseduffield/lazygit"&gt;lazygit&lt;/a&gt; for when I&amp;rsquo;m not using JetBrains in built git tools&lt;/li&gt;
&lt;li&gt;&lt;a href="https://k9scli.io"&gt;k9s&lt;/a&gt;; I can&amp;rsquo;t use &lt;code&gt;kubectl&lt;/code&gt; anymore&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="system--productivity"&gt;System &amp;amp; Productivity&lt;/h2&gt;
&lt;h3 id="operating-system"&gt;Operating System&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MacOS&lt;/strong&gt; - Macbook M4 Pro
&lt;ul&gt;
&lt;li&gt;Only recently started using this. Mac&amp;rsquo;s are a lot better since my last one in around 2015-16 ish.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ubuntu&lt;/strong&gt; - Linux desktop
&lt;ul&gt;
&lt;li&gt;Simple, just works. Containers as a native construct is far superior to all MacOS workarounds.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="productivity-and-general-apps"&gt;Productivity and General Apps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://obsidian.md"&gt;Obsidian&lt;/a&gt; - Note-taking and knowledge management. Pay for cloud sync.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://remarkable.com/products/remarkable-2"&gt;Remarkable2&lt;/a&gt; - All my rough work, notes, sketches start in here before
transcribing to Obsidian. Pay for cloud.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danielmichaels/zet-cmd"&gt;Zet&lt;/a&gt; - My personal zettlekasten CLI which gets published to this site&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.raycast.com"&gt;raycast&lt;/a&gt; if you aren&amp;rsquo;t using this on Mac what is wrong with you! Its amazing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bitwarden.com"&gt;bitwarden&lt;/a&gt; Password Vault. I pay for premium at $10 USD a year; it&amp;rsquo;s a steal&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hosting--services"&gt;Hosting &amp;amp; Services&lt;/h2&gt;
&lt;h3 id="hosting"&gt;Hosting&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.hetzner.com"&gt;Hetzner&lt;/a&gt; Any time I need a VPS that doesn&amp;rsquo;t need to be in my geo&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/coollabsio/coolify"&gt;Coolify&lt;/a&gt; Self-hosted application platform&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.proxmox.com/en"&gt;Proxmox&lt;/a&gt; - VM hypervisor for my home lab&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="networking"&gt;Networking&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pfsense.com"&gt;pfSense&lt;/a&gt; firewall&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ui.com"&gt;unifi&lt;/a&gt; I use their switches and access points. They&amp;rsquo;re the Apple of networking&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Last updated: August 2025&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Ghostty quick-terminal makes Mac terminals useful again</title><link>https://danielms.site/zet/2025/ghostty-quick-terminal-makes-mac-terminals-useful-again/</link><pubDate>Sat, 26 Jul 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/ghostty-quick-terminal-makes-mac-terminals-useful-again/</guid><description>&lt;h1 id="ghostty-quick-terminal-makes-mac-terminals-useful-again"&gt;Ghostty quick-terminal makes Mac terminals useful again&lt;/h1&gt;
&lt;p&gt;A while back I whinged hard about terminals on Mac. At the time I was unaware, or it didn&amp;rsquo;t exist - not sure - of Ghostty&amp;rsquo;s &amp;ldquo;quick terminal&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;This for my interests works exactly the same as how I&amp;rsquo;ve setup iTerm to dropdown using F12. Which is/was the only way to achieve what &lt;em&gt;many&lt;/em&gt; linux terminals offer out of the box. Yakuke, Guake and ddterm being examples of this.&lt;/p&gt;
&lt;p&gt;I think now I can safely transition from iTerm and WezTerm (still love the lua configs..) and make Ghostty my daily.&lt;/p&gt;
&lt;p&gt;Some references:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dbushell.com/2025/04/11/ghostty-macos-quick-terminal/"&gt;https://dbushell.com/2025/04/11/ghostty-macos-quick-terminal/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ghostty.org/docs/features#macos"&gt;https://ghostty.org/docs/features#macos&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note, I&amp;rsquo;ve not tested quick term on linux. I have read conflicting reports; x11 is okay but wayland might not be supported. Honestly, I think it&amp;rsquo;ll eventually get support there but it would be a real shame if it doesn&amp;rsquo;t get implemented. Still, linux has many great offerings and I largely use Zellij inside Ghostty anyway which works in Guake/Yakuke/ddterm as-is&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ghostty #terminal
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Claude Code findings</title><link>https://danielms.site/zet/2025/claude-code-findings/</link><pubDate>Wed, 09 Jul 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/claude-code-findings/</guid><description>&lt;h1 id="claude-code-findings"&gt;Claude Code findings&lt;/h1&gt;
&lt;p&gt;After using it for a few days, some initial thoughts and observations.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It uses &lt;code&gt;*.bak&lt;/code&gt; files despite having git. Obviously it&amp;rsquo;s learnt this practice from us, failed humans. Often it forgets to clean them up.&lt;/li&gt;
&lt;li&gt;Sometimes you really have to fight it to get it to do what you want no matter how much &amp;ldquo;planning&amp;rdquo; or &amp;ldquo;context engineering&amp;rdquo; you do.
&lt;ul&gt;
&lt;li&gt;E.g. using NATS JetStream legacy over the new standard. Several times it reverted from &amp;ldquo;new&amp;rdquo; to &amp;ldquo;legacy&amp;rdquo;. In the end I took over and fixed it after much wasted tokens.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AI is really a &amp;ldquo;shovels and picks&amp;rdquo; moment IMO - the model vendors are absolutely killing it with things like claude code. Forget MAX they are chasing enterprise pay-as-you-go. I, rather cynically, believe it&amp;rsquo;s possible they don&amp;rsquo;t want models that are too smart else they&amp;rsquo;ll lose money on credits; akin to why we&amp;rsquo;ll never get a cure for cancer - no money in it.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s insanely good for scaffolding projects but mediocre at filling in the complex centre of most big projects.&lt;/li&gt;
&lt;li&gt;Very easy to lose your way if you let it auto-edit - I can&amp;rsquo;t use it for work and hand on heart I fully understand it; actually better off writing the code myself and asking AI questions about my intent rather than letting it take the wheel (so no change from status quo for me).&lt;/li&gt;
&lt;li&gt;Reviewing code is boring and you&amp;rsquo;ll do A LOT of that using these tools.&lt;/li&gt;
&lt;li&gt;Asking questions, clarifying how things work and talking through your thought process are amazing quality of life improvements from these tools.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s pretty average at logging what it&amp;rsquo;s done in any markdown file; often does the work then I have to request it updates documents.&lt;/li&gt;
&lt;li&gt;Super easy to get lazy.&lt;/li&gt;
&lt;li&gt;Voice prompting is really cool because I can ramble on and it&amp;rsquo;ll understand me - sometimes when I start talking I realise my question/prompt is silly or I refine it more than I would just typing alone. Rubber ducky, if you will.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, could I do better at &amp;ldquo;context engineering&amp;rdquo;? Most likely. Do I think throwing shade on anyone who has anything negative to say via a &amp;ldquo;probably not prompting it right&amp;rdquo; is a very bad pattern, yes I do. IMO, it&amp;rsquo;s another form of character assassination we&amp;rsquo;ve seen time and time again in the last few years; &amp;ldquo;trust the science&amp;rdquo;, &amp;ldquo;right winger&amp;rdquo;, &amp;ldquo;MAGA&amp;rdquo; and so on as a quick way to dismiss someone who is not &amp;ldquo;on your side&amp;rdquo;. So yeah, I think we do ourselves an injustice here by immediately dismissing valuable feedback by labeling someone by virtue of &amp;ldquo;not prompting correctly&amp;rdquo;! Bit of a philosophical rant but I can&amp;rsquo;t help but feel it in this current era of AI.&lt;/p&gt;
&lt;p&gt;Regardless, I can absolutely see how these tools are a real boon for developers. Do I think running 6 agents at the same time on a single codebase is going to yield amazing results - no I don&amp;rsquo;t, at least not long term.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ai #claude #rant
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>NATS, ADS-B and my blog</title><link>https://danielms.site/zet/2025/nats-ads-b-and-my-blog/</link><pubDate>Sun, 06 Jul 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/nats-ads-b-and-my-blog/</guid><description>&lt;h1 id="nats-ads-b-and-my-blog"&gt;NATS, ADS-B and my blog&lt;/h1&gt;
&lt;p&gt;With the help of Claude Code, I (mostly) &amp;ldquo;vibed&amp;rdquo; a new page on my static Hugo blog.&lt;/p&gt;
&lt;p&gt;The page connects to a server over websockets and that server is connected to Synadia Cloud over NATS.
At my house I have a Raspberry Pi 2 with an &lt;a href="https://www.rtl-sdr.com/"&gt;RTL-SDR&lt;/a&gt; configured to listen for &lt;a href="https://en.wikipedia.org/wiki/Automatic_Dependent_Surveillance%E2%80%93Broadcast"&gt;ADS-B&lt;/a&gt; beacons 1090Mhz.&lt;/p&gt;
&lt;p&gt;The program running on the Pi is called &lt;a href="https://github.com/antirez/dump1090"&gt;dump1090&lt;/a&gt; (created by the Antirez who wrote Redis btw). It outputs raw AVR data onto port 30003 which is what I collect.&lt;/p&gt;
&lt;p&gt;So I wrote Go monolith with two entrypoints; agent and server. The agent is installed/ran on a sensor (rpi2) and its configured to listen on TCP 30003 for the
raw data. It also connects to a NATS server and then publishes the data to NATS. The agent uses core NATS so its fire and forget - no persistence. I set it up to reconnect
using a backoff mechanism incase my local network or its wifi dongle goes down. It&amp;rsquo;s connected to my IOT VLAN which is firewalled from the rest of the network but because it can make outbound requests, such as a NATS server, we get around all the complexities of firewalls, network address translation etc. Go NATS!&lt;/p&gt;
&lt;p&gt;The server is also connected to NATS - though it can be configured to run as an embedded NATS server. It&amp;rsquo;s subscribing to the subjects that contain the 1090 raw data. It then does some parsing and munging to turn this into useful info. ADS-B packets are transmit frequently and have a structure but not every packet needs to fill all the &amp;ldquo;fields&amp;rdquo; - you can get packets with a mix match of these fields and we have to construct them in order to build out useful data structures. Once each &amp;ldquo;packet&amp;rdquo; is rolled up into a valid go struct we save that into a &lt;a href="https://docs.nats.io/nats-concepts/jetstream"&gt;NATS JetStream&lt;/a&gt; &lt;a href="https://docs.nats.io/nats-concepts/jetstream/key-value-store"&gt;Key Value&lt;/a&gt; store. The aircrafts &lt;a href="https://en.wikipedia.org/wiki/International_Civil_Aviation_Organization#:~:text=ICAO%20is%20also%20responsible%20for%20issuing%20two,type%20designators%20B741%2C%20B742%20and%20B743%20respectively."&gt;ICAO&lt;/a&gt; is the key and it has a TTL of 30 minutes.&lt;/p&gt;
&lt;p&gt;A websocket is made available on the server which serves as a proxy for its NATS connection. Messages can be sent up the socket and as long as they match the whitelist they&amp;rsquo;ll be sent to NATS and the reply returned down the socket to the caller. I found this to be the safest way to do it in a static site which cannot hide secrets.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also created a couple of NATS &lt;a href="https://docs.nats.io/using-nats/nex/getting-started/building-service"&gt;micro&lt;/a&gt; endpoints which allow users to lookup aircraft via their callsign or ICAO number. Another great feature of NATS.&lt;/p&gt;
&lt;p&gt;Access to this is being exposed (and here&amp;rsquo;s where Claude came in clutch) on my Hugo blog at &lt;a href="https://danielms.site/nats-adsb"&gt;https://danielms.site/nats-adsb&lt;/a&gt;. It uses a Hugo shortcode which then contains
a lot of plain javascript to connect to the servers websocket and render the responses on the page.&lt;/p&gt;
&lt;p&gt;On the site you can &amp;ldquo;watch&amp;rdquo; all aircraft in real-time (as long as their are planes fly near my house). And lookup their callsign/ICAO number in the table, or do a search of any planes data via a form field. I&amp;rsquo;m leveraging a free ADSB API in the server to do these requests over NATS micro - the API sometimes has issues but, hey, its free.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#nats #websockets #hugo
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>NATS ADS-B</title><link>https://danielms.site/nats-adsb/</link><pubDate>Thu, 03 Jul 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/nats-adsb/</guid><description>&lt;p&gt;What you are seeing here is live &lt;a href="https://en.wikipedia.org/wiki/Automatic_Dependent_Surveillance%E2%80%93Broadcast"&gt;ADS-B&lt;/a&gt; data from a sensor located at my house. It is connected to a server
which then connects to &lt;a href="https://synadia.com"&gt;Synadia&lt;/a&gt; &lt;a href="https://www.synadia.com/cloud"&gt;Cloud&lt;/a&gt;. The server proxies data to and from NATS over a websocket.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="usage"&gt;Usage&lt;/h2&gt;
&lt;p&gt;Clicking on &lt;em&gt;Watch Aircraft&lt;/em&gt; will start a live tail of aircraft seen in the last 30 minutes.
There may be times when no aircraft are flying due to noise, typically 0000-0500 AEST.&lt;/p&gt;
&lt;p&gt;There are two function that can be made to look up aircraft by their Callsign or &lt;a href="https://skybrary.aero/articles/mode-s"&gt;mode s&lt;/a&gt; number.
These make use of NATS &lt;a href="https://docs.nats.io/using-nats/nex/getting-started/building-service"&gt;services/micro&lt;/a&gt; framework.&lt;/p&gt;
&lt;div id="nats-aircraft-container"&gt;
 &lt;div class="controls"&gt;
 &lt;button id="watchAircraftBtn" disabled&gt;Watch Aircraft&lt;/button&gt;
 &lt;button id="unwatchAircraftBtn" disabled&gt;Stop Watching Aircraft&lt;/button&gt;
 &lt;/div&gt;

 &lt;div class="controls"&gt;
 &lt;select id="requestType"&gt;
 &lt;option value="$SVC.adsb.callsign"&gt;Callsign Lookup&lt;/option&gt;
 &lt;option value="$SVC.adsb.aircraft"&gt;Aircraft Lookup&lt;/option&gt;
 &lt;/select&gt;
 &lt;input type="text" id="requestData" placeholder="Enter callsign or aircraft ID" value="VOZ511"&gt;
 &lt;button id="requestBtn" disabled&gt;Send Request&lt;/button&gt;
 &lt;/div&gt;

 &lt;div id="status" class="status disconnected"&gt;Disconnected&lt;/div&gt;

 &lt;div class="stats"&gt;
 &lt;div class="stat"&gt;
 &lt;div class="stat-value" id="messageCount"&gt;0&lt;/div&gt;
 &lt;div class="stat-label"&gt;Messages Received&lt;/div&gt;
 &lt;/div&gt;
 &lt;div class="stat"&gt;
 &lt;div class="stat-value" id="aircraftCount"&gt;0&lt;/div&gt;
 &lt;div class="stat-label"&gt;Active Aircraft&lt;/div&gt;
 &lt;/div&gt;
 &lt;div class="stat"&gt;
 &lt;div class="stat-value" id="errorCount"&gt;0&lt;/div&gt;
 &lt;div class="stat-label"&gt;Errors&lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div id="requestResultsContainer" style="display: none;"&gt;
 &lt;h3&gt;Request Results&lt;/h3&gt;
 &lt;div id="requestResultsContent"&gt;&lt;/div&gt;
 &lt;div class="controls"&gt;
 &lt;button id="clearResultsBtn"&gt;Clear Results&lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div id="aircraftContainer" style="display: none;"&gt;
 &lt;h3&gt;Live Aircraft&lt;/h3&gt;
 &lt;table id="aircraftTable"&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;ICAO&lt;/th&gt;
 &lt;th&gt;Callsign&lt;/th&gt;
 &lt;th&gt;Altitude&lt;/th&gt;
 &lt;th&gt;Speed&lt;/th&gt;
 &lt;th&gt;Heading&lt;/th&gt;
 &lt;th&gt;Last Seen&lt;/th&gt;
 &lt;th&gt;Status&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody id="aircraftTableBody"&gt;
 &lt;/tbody&gt;
 &lt;/table&gt;
 &lt;/div&gt;

 &lt;div class="messages" id="messages"&gt;&lt;/div&gt;

 &lt;div class="controls"&gt;
 &lt;button id="clearBtn"&gt;Clear Messages&lt;/button&gt;
 &lt;button id="pauseBtn"&gt;Pause&lt;/button&gt;
 &lt;/div&gt;
&lt;/div&gt;

&lt;script&gt;
(function() {
 'use strict';
 
 let ws = null;
 let messageCount = 0;
 let errorCount = 0;
 let paused = false;
 let requestId = 0;
 let watchingAircraft = false;
 let aircraftMap = new Map();
 let isConnected = false;
 let pendingRequests = new Map();
 let animationTimeouts = new Map();
 let lastSeenUpdateInterval = null;

 const elements = {
 watchAircraftBtn: document.getElementById('watchAircraftBtn'),
 unwatchAircraftBtn: document.getElementById('unwatchAircraftBtn'),
 requestType: document.getElementById('requestType'),
 requestData: document.getElementById('requestData'),
 requestBtn: document.getElementById('requestBtn'),
 status: document.getElementById('status'),
 messages: document.getElementById('messages'),
 messageCount: document.getElementById('messageCount'),
 aircraftCount: document.getElementById('aircraftCount'),
 errorCount: document.getElementById('errorCount'),
 clearBtn: document.getElementById('clearBtn'),
 pauseBtn: document.getElementById('pauseBtn'),
 aircraftContainer: document.getElementById('aircraftContainer'),
 aircraftTableBody: document.getElementById('aircraftTableBody'),
 requestResultsContainer: document.getElementById('requestResultsContainer'),
 requestResultsContent: document.getElementById('requestResultsContent'),
 clearResultsBtn: document.getElementById('clearResultsBtn')
 };

 function escapeHtml(unsafe) {
 return String(unsafe)
 .replace(/&amp;/g, "&amp;amp;")
 .replace(/&lt;/g, "&amp;lt;")
 .replace(/&gt;/g, "&amp;gt;")
 .replace(/"/g, "&amp;quot;")
 .replace(/'/g, "&amp;#039;");
 }

 function getWebSocketUrl() {
 const isDevelopment = window.location.hostname === 'localhost' || 
 window.location.hostname === '127.0.0.1' ||
 window.location.hostname === '';
 
 if (isDevelopment) {
 return 'ws://localhost:7070/ws';
 } else {
 return 'wss://nats-adsb.danielms.site/ws';
 }
 }

 function updateStatus(connected) {
 elements.status.textContent = connected ? 'Connected' : 'Disconnected';
 elements.status.className = connected ? 'status connected' : 'status disconnected';
 
 elements.requestBtn.disabled = !connected;
 elements.watchAircraftBtn.disabled = !connected || watchingAircraft;
 elements.unwatchAircraftBtn.disabled = !connected || !watchingAircraft;
 }

 function addMessage(type, data) {
 if (paused) return;
 
 const messageDiv = document.createElement('div');
 messageDiv.className = `message ${type}`;
 
 const timestamp = new Date().toLocaleTimeString();
 const content = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
 const escapedContent = escapeHtml(content);
 
 messageDiv.innerHTML = `
 &lt;div class="timestamp"&gt;${timestamp}&lt;/div&gt;
 &lt;div&gt;${escapedContent}&lt;/div&gt;
 `;
 
 elements.messages.appendChild(messageDiv);
 elements.messages.scrollTop = elements.messages.scrollHeight;
 
 if (type === 'received') {
 messageCount++;
 elements.messageCount.textContent = messageCount;
 } else if (type === 'error') {
 errorCount++;
 elements.errorCount.textContent = errorCount;
 }
 }

 function sendMessage(message) {
 if (ws &amp;&amp; ws.readyState === WebSocket.OPEN) {
 ws.send(JSON.stringify(message));
 addMessage('sent', message);
 }
 }

 function connect() {
 const url = getWebSocketUrl();
 
 try {
 ws = new WebSocket(url);
 
 ws.onopen = function() {
 isConnected = true;
 updateStatus(true);
 addMessage('received', { type: 'connection', message: 'Connected to WebSocket' });
 };
 
 ws.onmessage = function(event) {
 try {
 const data = JSON.parse(event.data);
 
 
 if (data.type === 'aircraft_list') {
 handleAircraftList(data);
 } else if (data.type === 'aircraft_update') {
 handleAircraftUpdate(data);
 } else if (data.type === 'response') {
 handleRequestResponse(data);
 } else if (data.type === 'error') {
 
 if (data.id &amp;&amp; pendingRequests.has(data.id)) {
 handleRequestResponse(data);
 } else {
 addMessage('received', data);
 }
 errorCount++;
 elements.errorCount.textContent = errorCount;
 } else {
 addMessage('received', data);
 }
 } catch (e) {
 addMessage('error', { error: 'Failed to parse message', raw: event.data });
 }
 };
 
 ws.onclose = function() {
 isConnected = false;
 updateStatus(false);
 addMessage('received', { type: 'connection', message: 'Disconnected from WebSocket' });
 watchingAircraft = false;
 elements.aircraftContainer.style.display = 'none';
 aircraftMap.clear();
 elements.aircraftTableBody.innerHTML = '';
 elements.aircraftCount.textContent = 0;
 stopLastSeenUpdates();
 
 
 
 
 
 
 setTimeout(connect, 3000);
 };
 
 ws.onerror = function(error) {
 addMessage('error', { error: 'WebSocket error', details: error });
 };
 
 } catch (e) {
 addMessage('error', { error: 'Failed to connect', details: e.message });
 
 setTimeout(connect, 3000);
 }
 }

 function watchAircraft() {
 sendMessage({
 type: 'watch_aircraft'
 });
 watchingAircraft = true;
 elements.watchAircraftBtn.disabled = true;
 elements.unwatchAircraftBtn.disabled = false;
 elements.aircraftContainer.style.display = 'block';
 startLastSeenUpdates();
 }

 function unwatchAircraft() {
 sendMessage({
 type: 'unwatch_aircraft'
 });
 watchingAircraft = false;
 elements.watchAircraftBtn.disabled = false;
 elements.unwatchAircraftBtn.disabled = true;
 elements.aircraftContainer.style.display = 'none';
 aircraftMap.clear();
 elements.aircraftTableBody.innerHTML = '';
 elements.aircraftCount.textContent = 0;
 stopLastSeenUpdates();
 }

 function sendRequest() {
 const subject = elements.requestType.value;
 const data = elements.requestData.value;
 
 if (!subject || !data) return;
 
 
 clearRequestResults();
 
 const reqId = `req-${++requestId}`;
 const requestInfo = {
 id: reqId,
 subject: subject,
 searchTerm: data,
 timestamp: new Date().toLocaleTimeString(),
 type: subject.includes('callsign') ? 'Callsign Lookup' : 'Aircraft Lookup'
 };
 
 pendingRequests.set(reqId, requestInfo);
 
 sendMessage({
 type: 'request',
 id: reqId,
 subject: subject,
 data: data
 });
 }

 function handleAircraftList(data) {
 if (!data.aircraft) return;
 
 const aircraftList = Array.isArray(data.aircraft) ? data.aircraft : JSON.parse(data.aircraft);
 aircraftMap.clear();
 elements.aircraftTableBody.innerHTML = '';
 
 aircraftList.forEach(aircraft =&gt; {
 aircraftMap.set(aircraft.icao, aircraft);
 addAircraftToTable(aircraft, 'existing');
 });
 
 elements.aircraftCount.textContent = aircraftList.length;
 addMessage('received', { 
 type: 'aircraft_list', 
 message: `Loaded ${aircraftList.length} aircraft` 
 });
 }

 function handleAircraftUpdate(data) {
 const icao = data.icao;
 
 if (data.operation === 'delete') {
 const row = document.getElementById(`aircraft-${icao}`);
 if (row) {
 row.classList.add('aircraft-removed');
 setTimeout(() =&gt; {
 row.remove();
 aircraftMap.delete(icao);
 elements.aircraftCount.textContent = aircraftMap.size;
 }, 1000);
 }
 } else if (data.operation === 'put') {
 const aircraft = data.data;
 const isNew = !aircraftMap.has(icao);
 aircraftMap.set(icao, aircraft);
 
 if (isNew) {
 addAircraftToTable(aircraft, 'new');
 } else {
 updateAircraftInTable(aircraft);
 }
 
 elements.aircraftCount.textContent = aircraftMap.size;
 }
 }

 function addAircraftToTable(aircraft, status) {
 const row = document.createElement('tr');
 row.id = `aircraft-${aircraft.icao}`;
 
 const icaoClickable = aircraft.icao ? `&lt;a href="#" class="aircraft-lookup" data-type="aircraft" data-value="${escapeHtml(aircraft.icao)}"&gt;${escapeHtml(aircraft.icao)}&lt;/a&gt;` : '-';
 const callsignClickable = aircraft.callsign ? `&lt;a href="#" class="callsign-lookup" data-type="callsign" data-value="${escapeHtml(aircraft.callsign)}"&gt;${escapeHtml(aircraft.callsign)}&lt;/a&gt;` : '-';
 
 row.innerHTML = `
 &lt;td&gt;${icaoClickable}&lt;/td&gt;
 &lt;td&gt;${callsignClickable}&lt;/td&gt;
 &lt;td&gt;${aircraft.altitude ? aircraft.altitude.toFixed(0) + ' ft' : '-'}&lt;/td&gt;
 &lt;td&gt;${aircraft.speed ? aircraft.speed.toFixed(0) + ' kts' : '-'}&lt;/td&gt;
 &lt;td&gt;${aircraft.heading ? aircraft.heading.toFixed(0) + '°' : '-'}&lt;/td&gt;
 &lt;td&gt;${formatLastSeen(aircraft.last_seen)}&lt;/td&gt;
 &lt;td&gt;${escapeHtml(status)}&lt;/td&gt;
 `;
 
 if (status === 'new') {
 row.classList.add('aircraft-new');
 }
 
 elements.aircraftTableBody.appendChild(row);
 }

 function updateAircraftInTable(aircraft) {
 const row = document.getElementById(`aircraft-${aircraft.icao}`);
 if (!row) {
 addAircraftToTable(aircraft, 'new');
 return;
 }
 
 const icaoClickable = aircraft.icao ? `&lt;a href="#" class="aircraft-lookup" data-type="aircraft" data-value="${escapeHtml(aircraft.icao)}"&gt;${escapeHtml(aircraft.icao)}&lt;/a&gt;` : '-';
 const callsignClickable = aircraft.callsign ? `&lt;a href="#" class="callsign-lookup" data-type="callsign" data-value="${escapeHtml(aircraft.callsign)}"&gt;${escapeHtml(aircraft.callsign)}&lt;/a&gt;` : '-';
 
 row.innerHTML = `
 &lt;td&gt;${icaoClickable}&lt;/td&gt;
 &lt;td&gt;${callsignClickable}&lt;/td&gt;
 &lt;td&gt;${aircraft.altitude ? aircraft.altitude.toFixed(0) + ' ft' : '-'}&lt;/td&gt;
 &lt;td&gt;${aircraft.speed ? aircraft.speed.toFixed(0) + ' kts' : '-'}&lt;/td&gt;
 &lt;td&gt;${aircraft.heading ? aircraft.heading.toFixed(0) + '°' : '-'}&lt;/td&gt;
 &lt;td&gt;${formatLastSeen(aircraft.last_seen)}&lt;/td&gt;
 &lt;td&gt;updated&lt;/td&gt;
 `;
 
 debouncedAnimateRow(aircraft.icao, 'aircraft-updated');
 }

 function debouncedAnimateRow(icao, animationClass) {
 const row = document.getElementById(`aircraft-${icao}`);
 if (!row) return;
 
 
 if (animationTimeouts.has(icao)) {
 clearTimeout(animationTimeouts.get(icao));
 
 row.classList.remove('aircraft-updated');
 }
 
 
 row.classList.add(animationClass);
 
 
 const timeout = setTimeout(() =&gt; {
 row.classList.remove(animationClass);
 animationTimeouts.delete(icao);
 }, 2000);
 
 animationTimeouts.set(icao, timeout);
 }

 function formatLastSeen(lastSeen) {
 if (!lastSeen) return '-';
 const date = new Date(lastSeen);
 const now = new Date();
 const diffSeconds = Math.floor((now - date) / 1000);
 
 if (diffSeconds &lt; 60) {
 return `${diffSeconds}s ago`;
 } else if (diffSeconds &lt; 3600) {
 return `${Math.floor(diffSeconds / 60)}m ago`;
 } else {
 return date.toLocaleTimeString();
 }
 }

 function updateAllLastSeenTimes() {
 if (!watchingAircraft) return;
 
 
 aircraftMap.forEach((aircraft, icao) =&gt; {
 const row = document.getElementById(`aircraft-${icao}`);
 if (row) {
 const lastSeenCell = row.cells[5]; 
 if (lastSeenCell) {
 lastSeenCell.textContent = formatLastSeen(aircraft.last_seen);
 }
 }
 });
 }

 function startLastSeenUpdates() {
 stopLastSeenUpdates(); 
 lastSeenUpdateInterval = setInterval(updateAllLastSeenTimes, 1000); 
 }

 function stopLastSeenUpdates() {
 if (lastSeenUpdateInterval) {
 clearInterval(lastSeenUpdateInterval);
 lastSeenUpdateInterval = null;
 }
 }

 function updateRequestPlaceholder() {
 const requestType = elements.requestType.value;
 if (requestType === '$SVC.adsb.callsign') {
 elements.requestData.placeholder = 'Enter callsign (e.g. VOZ511)';
 elements.requestData.value = 'VOZ511';
 } else if (requestType === '$SVC.adsb.aircraft') {
 elements.requestData.placeholder = 'Enter aircraft ID (e.g. 7C6AF3)';
 elements.requestData.value = '7C6AF3';
 }
 }

 function clearMessages() {
 elements.messages.innerHTML = '';
 messageCount = 0;
 errorCount = 0;
 elements.messageCount.textContent = 0;
 elements.errorCount.textContent = 0;
 }

 function togglePause() {
 paused = !paused;
 elements.pauseBtn.textContent = paused ? 'Resume' : 'Pause';
 elements.pauseBtn.style.backgroundColor = paused ? '#28a745' : '#007bff';
 }

 function handleRequestResponse(data) {
 const requestInfo = pendingRequests.get(data.id);
 if (!requestInfo) {
 
 addMessage('received', data);
 return;
 }

 
 pendingRequests.delete(data.id);

 
 elements.requestResultsContainer.style.display = 'block';

 
 let responseData;
 try {
 responseData = typeof data.data === 'string' ? JSON.parse(data.data) : data.data;
 } catch (e) {
 responseData = data.data;
 }

 
 const resultDiv = document.createElement('div');
 resultDiv.className = 'request-result';
 
 let resultContent = '';
 
 
 if (data.error || (responseData &amp;&amp; responseData.error)) {
 const errorMessage = data.error || responseData.error || 'Unknown error occurred';
 resultContent = `
 &lt;div class="result-header"&gt;
 &lt;h4&gt;Error: ${escapeHtml(requestInfo.type)}: ${escapeHtml(requestInfo.searchTerm)}&lt;/h4&gt;
 &lt;span class="result-timestamp"&gt;${requestInfo.timestamp}&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="error-message"&gt;
 &lt;p style="color: var(--error-color, #d32f2f);"&gt;${escapeHtml(errorMessage)}&lt;/p&gt;
 ${requestInfo.type === 'Aircraft Lookup' ? '&lt;p&gt;&lt;small&gt;This may indicate the aircraft is not in the database or the external API is unavailable.&lt;/small&gt;&lt;/p&gt;' : ''}
 ${requestInfo.type === 'Callsign Lookup' ? '&lt;p&gt;&lt;small&gt;This may indicate the callsign is not found or the external API is unavailable.&lt;/small&gt;&lt;/p&gt;' : ''}
 &lt;/div&gt;
 `;
 }
 
 else if (responseData &amp;&amp; responseData.response &amp;&amp; responseData.response.flightroute) {
 const flightRoute = responseData.response.flightroute;
 
 
 function createAirportLink(airport) {
 if (!airport || !airport.name) return '-';
 
 let searchQuery = airport.name;
 if (airport.iata_code) {
 searchQuery += ` ${airport.iata_code}`;
 }
 if (airport.municipality) {
 searchQuery += ` ${airport.municipality}`;
 }
 
 const mapsUrl = `https://www.google.com/maps/search/${encodeURIComponent(searchQuery + ' airport')}`;
 const displayText = escapeHtml(airport.name) + (airport.iata_code ? ' (' + escapeHtml(airport.iata_code) + ')' : '');
 
 return `&lt;a href="${mapsUrl}" target="_blank" rel="noopener noreferrer" class="airport-link"&gt;${displayText}&lt;/a&gt;`;
 }
 
 resultContent = `
 &lt;div class="result-header"&gt;
 &lt;h4&gt;${escapeHtml(requestInfo.type)}: ${escapeHtml(requestInfo.searchTerm)}&lt;/h4&gt;
 &lt;span class="result-timestamp"&gt;${requestInfo.timestamp}&lt;/span&gt;
 &lt;/div&gt;
 &lt;table class="result-table"&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Callsign:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(flightRoute.callsign || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;ICAO:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(flightRoute.callsign_icao || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;IATA:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(flightRoute.callsign_iata || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Airline:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml((flightRoute.airline &amp;&amp; flightRoute.airline.name) || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Origin:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${createAirportLink(flightRoute.origin)}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Destination:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${createAirportLink(flightRoute.destination)}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Country:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml((flightRoute.airline &amp;&amp; flightRoute.airline.country) || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;/table&gt;
 `;
 } else if (responseData &amp;&amp; responseData.response &amp;&amp; responseData.response.aircraft) {
 const aircraft = responseData.response.aircraft;
 const thumbnailHtml = aircraft.url_photo_thumbnail ? 
 `&lt;div class="aircraft-thumbnail"&gt;
 &lt;a href="${escapeHtml(aircraft.url_photo || '')}" target="_blank" rel="noopener noreferrer"&gt;
 &lt;img src="${escapeHtml(aircraft.url_photo_thumbnail)}" alt="Aircraft ${escapeHtml(aircraft.registration || aircraft.mode_s || 'photo')}" /&gt;
 &lt;/a&gt;
 &lt;/div&gt;` : '';
 
 resultContent = `
 &lt;div class="result-header"&gt;
 &lt;h4&gt;${escapeHtml(requestInfo.type)}: ${escapeHtml(requestInfo.searchTerm)}&lt;/h4&gt;
 &lt;span class="result-timestamp"&gt;${requestInfo.timestamp}&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="result-content-wrapper"&gt;
 ${thumbnailHtml}
 &lt;table class="result-table"&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Registration:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(aircraft.registration || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Mode S:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(aircraft.mode_s || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Aircraft Type:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(aircraft.type || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;ICAO Type:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(aircraft.icao_type || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Manufacturer:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(aircraft.manufacturer || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Owner:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(aircraft.registered_owner || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Country:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(aircraft.registered_owner_country_name || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;/table&gt;
 &lt;/div&gt;
 `;
 } else if (responseData &amp;&amp; typeof responseData === 'object' &amp;&amp; !Array.isArray(responseData)) {
 
 resultContent = `
 &lt;div class="result-header"&gt;
 &lt;h4&gt;${escapeHtml(requestInfo.type)}: ${escapeHtml(requestInfo.searchTerm)}&lt;/h4&gt;
 &lt;span class="result-timestamp"&gt;${requestInfo.timestamp}&lt;/span&gt;
 &lt;/div&gt;
 &lt;table class="result-table"&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;ICAO:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(responseData.icao || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Callsign:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${escapeHtml(responseData.callsign || '-')}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Altitude:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${responseData.altitude ? responseData.altitude.toFixed(0) + ' ft' : '-'}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Speed:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${responseData.speed ? responseData.speed.toFixed(0) + ' kts' : '-'}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Heading:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${responseData.heading ? responseData.heading.toFixed(0) + '°' : '-'}&lt;/td&gt;&lt;/tr&gt;
 &lt;tr&gt;&lt;td&gt;&lt;strong&gt;Last Seen:&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;${formatLastSeen(responseData.last_seen)}&lt;/td&gt;&lt;/tr&gt;
 &lt;/table&gt;
 `;
 } else if (Array.isArray(responseData)) {
 
 resultContent = `
 &lt;div class="result-header"&gt;
 &lt;h4&gt;${escapeHtml(requestInfo.type)}: ${escapeHtml(requestInfo.searchTerm)}&lt;/h4&gt;
 &lt;span class="result-timestamp"&gt;${requestInfo.timestamp}&lt;/span&gt;
 &lt;/div&gt;
 &lt;p&gt;Found ${responseData.length} result(s)&lt;/p&gt;
 &lt;table class="result-table"&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;ICAO&lt;/th&gt;
 &lt;th&gt;Callsign&lt;/th&gt;
 &lt;th&gt;Altitude&lt;/th&gt;
 &lt;th&gt;Speed&lt;/th&gt;
 &lt;th&gt;Heading&lt;/th&gt;
 &lt;th&gt;Last Seen&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 ${responseData.map(aircraft =&gt; `
 &lt;tr&gt;
 &lt;td&gt;${escapeHtml(aircraft.icao || '-')}&lt;/td&gt;
 &lt;td&gt;${escapeHtml(aircraft.callsign || '-')}&lt;/td&gt;
 &lt;td&gt;${aircraft.altitude ? aircraft.altitude.toFixed(0) + ' ft' : '-'}&lt;/td&gt;
 &lt;td&gt;${aircraft.speed ? aircraft.speed.toFixed(0) + ' kts' : '-'}&lt;/td&gt;
 &lt;td&gt;${aircraft.heading ? aircraft.heading.toFixed(0) + '°' : '-'}&lt;/td&gt;
 &lt;td&gt;${formatLastSeen(aircraft.last_seen)}&lt;/td&gt;
 &lt;/tr&gt;
 `).join('')}
 &lt;/tbody&gt;
 &lt;/table&gt;
 `;
 } else {
 
 resultContent = `
 &lt;div class="result-header"&gt;
 &lt;h4&gt;${escapeHtml(requestInfo.type)}: ${escapeHtml(requestInfo.searchTerm)}&lt;/h4&gt;
 &lt;span class="result-timestamp"&gt;${requestInfo.timestamp}&lt;/span&gt;
 &lt;/div&gt;
 &lt;p&gt;${responseData ? 'Raw response:' : 'No results found'}&lt;/p&gt;
 ${responseData ? `&lt;pre&gt;${escapeHtml(JSON.stringify(responseData, null, 2))}&lt;/pre&gt;` : ''}
 `;
 }

 resultDiv.innerHTML = resultContent;
 
 
 elements.requestResultsContent.insertBefore(resultDiv, elements.requestResultsContent.firstChild);

 
 addMessage('received', data);
 }

 function clearRequestResults() {
 elements.requestResultsContent.innerHTML = '';
 elements.requestResultsContainer.style.display = 'none';
 }

 
 elements.watchAircraftBtn.addEventListener('click', watchAircraft);
 elements.unwatchAircraftBtn.addEventListener('click', unwatchAircraft);
 elements.requestBtn.addEventListener('click', sendRequest);
 elements.clearBtn.addEventListener('click', clearMessages);
 elements.pauseBtn.addEventListener('click', togglePause);
 elements.requestType.addEventListener('change', updateRequestPlaceholder);
 elements.clearResultsBtn.addEventListener('click', clearRequestResults);

 elements.requestData.addEventListener('keypress', function(e) {
 if (e.key === 'Enter') sendRequest();
 });

 
 elements.aircraftContainer.addEventListener('click', function(e) {
 if (e.target.classList.contains('aircraft-lookup') || e.target.classList.contains('callsign-lookup')) {
 e.preventDefault();
 
 const lookupType = e.target.getAttribute('data-type');
 const lookupValue = e.target.getAttribute('data-value');
 
 if (lookupType &amp;&amp; lookupValue) {
 
 if (lookupType === 'aircraft') {
 elements.requestType.value = '$SVC.adsb.aircraft';
 } else if (lookupType === 'callsign') {
 elements.requestType.value = '$SVC.adsb.callsign';
 }
 
 
 elements.requestData.value = lookupValue;
 
 
 updateRequestPlaceholder();
 
 
 clearRequestResults();
 
 const reqId = `req-${++requestId}`;
 const requestInfo = {
 id: reqId,
 subject: elements.requestType.value,
 searchTerm: lookupValue,
 timestamp: new Date().toLocaleTimeString(),
 type: lookupType === 'callsign' ? 'Callsign Lookup' : 'Aircraft Lookup'
 };
 
 pendingRequests.set(reqId, requestInfo);
 
 sendMessage({
 type: 'request',
 id: reqId,
 subject: elements.requestType.value,
 data: lookupValue
 });
 }
 }
 });

 
 updateStatus(false);
 updateRequestPlaceholder();
 
 
 connect();
})();
&lt;/script&gt;

&lt;style&gt;
#nats-aircraft-container {
 font-family: 'Verdana', sans-serif;
 max-width: 1200px;
 margin: 0 auto;
 padding: 20px;
 color: var(--body-color);
}

#nats-aircraft-container .controls {
 display: flex;
 gap: 10px;
 margin-bottom: 20px;
 flex-wrap: wrap;
}

#nats-aircraft-container button {
 display: inline-block;
 position: relative;
 outline: 0;
 color: var(--heading-color);
 background: transparent;
 font-size: 14px;
 text-align: center;
 text-decoration: none;
 cursor: pointer;
 border: 2px solid var(--heading-color);
 white-space: nowrap;
 font-weight: 600;
 font-style: normal;
 border-radius: 999em;
 padding: 0.5em 1.25em;
 line-height: 1.666em;
 transition: all 0.2s ease;
}

#nats-aircraft-container button:hover:not(:disabled) {
 color: #2660ab;
 border-color: #2660ab;
 background-color: var(--secondary-bg-color);
 transform: translateY(-1px);
 box-shadow: 0 2px 4px rgba(38, 166, 171, 0.2);
}

#nats-aircraft-container button:disabled {
 opacity: 0.6;
 cursor: not-allowed;
 color: var(--post-color);
 border-color: var(--post-color);
}

#nats-aircraft-container input[type="text"],
#nats-aircraft-container select {
 box-sizing: border-box;
 font-size: 14px;
 padding: 8px;
 outline: none;
 background-color: var(--bg-color);
 border: 1px solid var(--form-border-color);
 color: var(--body-color);
 border-radius: 4px;
 min-width: 200px;
}

#nats-aircraft-container select {
 min-width: 150px;
}

#nats-aircraft-container input[type="text"]:focus,
#nats-aircraft-container select:focus {
 box-shadow: 0 0 5px;
 border: 1px solid var(--form-button-hover-border-color);
}

#nats-aircraft-container .status {
 padding: 10px;
 margin: 10px 0;
 border-radius: 4px;
 font-weight: bold;
 border: 1px solid var(--border-color);
}

#nats-aircraft-container .status.connected {
 background-color: var(--secondary-bg-color);
 color: #1e4a73;
 border-color: #2660ab;
}

html[data-theme='dark'] #nats-aircraft-container .status.connected {
 color: #4a9eff;
}

#nats-aircraft-container .status.disconnected {
 background-color: var(--secondary-bg-color);
 color: var(--heading-color);
 border-color: var(--border-color);
}

#nats-aircraft-container .messages {
 height: 300px;
 overflow-y: auto;
 border: 1px solid var(--border-color);
 padding: 10px;
 background-color: var(--pre-bg-color);
 font-family: monospace;
 font-size: 12px;
 margin-bottom: 20px;
 border-radius: 4px;
}

#nats-aircraft-container .message {
 margin: 5px 0;
 padding: 5px;
 border-radius: 3px;
 color: var(--body-color);
}

#nats-aircraft-container .message.sent {
 background-color: var(--secondary-bg-color);
 border-left: 3px solid #2660ab;
}

#nats-aircraft-container .message.received {
 background-color: var(--secondary-bg-color);
 border-left: 3px solid var(--tag-color);
}

#nats-aircraft-container .message.error {
 background-color: var(--secondary-bg-color);
 border-left: 3px solid #d9534f;
}

#nats-aircraft-container .timestamp {
 color: var(--post-color);
 font-size: 10px;
}

#nats-aircraft-container .stats {
 display: flex;
 gap: 20px;
 margin: 20px 0;
 justify-content: center;
}

#nats-aircraft-container .stat {
 text-align: center;
 padding: 15px;
 background-color: var(--secondary-bg-color);
 border: 1px solid var(--border-color);
 border-radius: 8px;
 min-width: 120px;
}

#nats-aircraft-container .stat-value {
 font-size: 24px;
 font-weight: bold;
 color: #2660ab;
}

#nats-aircraft-container .stat-label {
 font-size: 12px;
 color: var(--heading-color);
 margin-top: 5px;
 font-weight: 500;
}

#nats-aircraft-container #aircraftContainer {
 margin: 20px 0;
 padding: 20px;
 background-color: var(--secondary-bg-color);
 border: 1px solid var(--border-color);
 border-radius: 8px;
}

#nats-aircraft-container #aircraftContainer h3 {
 color: var(--heading-color);
 margin-top: 0;
}

#nats-aircraft-container #aircraftTable {
 width: 100%;
 border-collapse: collapse;
 font-size: 14px;
 color: var(--body-color);
}

#nats-aircraft-container #aircraftTable th {
 padding: 8px;
 text-align: left;
 border-bottom: 2px solid var(--border-color);
 background-color: var(--pre-bg-color);
 color: var(--heading-color);
 font-weight: 600;
}

#nats-aircraft-container #aircraftTable td {
 padding: 8px;
 border-bottom: 1px solid var(--border-color);
}

#nats-aircraft-container #aircraftTable tr:hover {
 background-color: var(--pre-bg-color);
}

#nats-aircraft-container .aircraft-new {
 animation: highlight-green 2s ease;
}

#nats-aircraft-container .aircraft-updated {
 animation: highlight-yellow 2s ease;
}

#nats-aircraft-container .aircraft-removed {
 animation: fade-out 1s ease;
 opacity: 0.5;
}

@keyframes highlight-green {
 0% { 
 background-color: rgba(38, 96, 171, 0.3);
 color: var(--body-color);
 }
 50% {
 background-color: rgba(38, 96, 171, 0.15);
 color: var(--body-color);
 }
 100% { 
 background-color: transparent; 
 color: var(--body-color);
 }
}

@keyframes highlight-yellow {
 0% { 
 background-color: rgba(38, 96, 171, 0.2);
 border-left: 3px solid rgba(38, 96, 171, 0.6);
 }
 50% {
 background-color: rgba(38, 96, 171, 0.1);
 border-left: 3px solid rgba(38, 96, 171, 0.3);
 }
 100% { 
 background-color: transparent; 
 border-left: 3px solid transparent;
 }
}

@keyframes fade-out {
 0% { 
 opacity: 1; 
 background-color: rgba(217, 83, 79, 0.2);
 }
 50% {
 opacity: 0.7;
 background-color: rgba(217, 83, 79, 0.1);
 }
 100% { 
 opacity: 0.3; 
 background-color: transparent; 
 }
}

#nats-aircraft-container #requestResultsContainer {
 margin: 20px 0;
 padding: 20px;
 background-color: var(--secondary-bg-color);
 border: 1px solid var(--border-color);
 border-radius: 8px;
}

#nats-aircraft-container #requestResultsContainer h3 {
 color: var(--heading-color);
 margin-top: 0;
}

#nats-aircraft-container .request-result {
 margin-bottom: 20px;
 padding: 15px;
 background-color: var(--bg-color);
 border: 1px solid var(--border-color);
 border-radius: 6px;
 border-left: 4px solid #2660ab;
}

#nats-aircraft-container .result-header {
 display: flex;
 justify-content: space-between;
 align-items: center;
 margin-bottom: 10px;
 padding-bottom: 8px;
 border-bottom: 1px solid var(--border-color);
}

#nats-aircraft-container .result-header h4 {
 margin: 0;
 color: var(--heading-color);
 font-size: 16px;
 font-weight: 600;
}

#nats-aircraft-container .result-timestamp {
 color: var(--post-color);
 font-size: 12px;
 font-weight: normal;
}

#nats-aircraft-container .result-table {
 width: 100%;
 border-collapse: collapse;
 font-size: 14px;
 color: var(--body-color);
 margin-top: 10px;
}

#nats-aircraft-container .result-table th {
 padding: 8px;
 text-align: left;
 border-bottom: 2px solid var(--border-color);
 background-color: var(--pre-bg-color);
 color: var(--heading-color);
 font-weight: 600;
}

#nats-aircraft-container .result-table td {
 padding: 8px;
 border-bottom: 1px solid var(--border-color);
 vertical-align: top;
}

#nats-aircraft-container .result-table tr:hover {
 background-color: var(--pre-bg-color);
}

#nats-aircraft-container .request-result pre {
 background-color: var(--pre-bg-color);
 padding: 10px;
 border-radius: 4px;
 border: 1px solid var(--border-color);
 overflow-x: auto;
 font-size: 12px;
 color: var(--body-color);
 margin: 10px 0;
}

#nats-aircraft-container .request-result p {
 margin: 10px 0;
 color: var(--body-color);
}

 
#nats-aircraft-container .aircraft-lookup,
#nats-aircraft-container .callsign-lookup,
#nats-aircraft-container .airport-link {
 color: #2660ab;
 text-decoration: underline;
 cursor: pointer;
 transition: color 0.2s ease;
}

#nats-aircraft-container .aircraft-lookup:hover,
#nats-aircraft-container .callsign-lookup:hover,
#nats-aircraft-container .airport-link:hover {
 color: #1e4a73;
 text-decoration: none;
}

 
#nats-aircraft-container .result-content-wrapper {
 display: flex;
 gap: 20px;
 align-items: flex-start;
}

#nats-aircraft-container .aircraft-thumbnail {
 flex-shrink: 0;
 border-radius: 8px;
 overflow: hidden;
 border: 1px solid var(--border-color);
 background-color: var(--pre-bg-color);
}

#nats-aircraft-container .aircraft-thumbnail img {
 display: block;
 max-width: 200px;
 height: auto;
 transition: transform 0.2s ease;
}

#nats-aircraft-container .aircraft-thumbnail:hover img {
 transform: scale(1.05);
}

#nats-aircraft-container .result-content-wrapper .result-table {
 flex-grow: 1;
}

 
@media screen and (max-width: 960px) {
 #nats-aircraft-container {
 padding: 10px;
 }
 
 #nats-aircraft-container .controls {
 flex-direction: column;
 align-items: stretch;
 }
 
 #nats-aircraft-container input[type="text"],
 #nats-aircraft-container select,
 #nats-aircraft-container button {
 width: 100%;
 min-width: auto;
 margin-bottom: 10px;
 }
 
 #nats-aircraft-container .stats {
 flex-direction: column;
 gap: 10px;
 }
 
 #nats-aircraft-container .stat {
 min-width: auto;
 }
 
 #nats-aircraft-container #aircraftTable {
 font-size: 12px;
 }
 
 #nats-aircraft-container #aircraftTable th,
 #nats-aircraft-container #aircraftTable td {
 padding: 4px;
 }
 
 #nats-aircraft-container .result-header {
 flex-direction: column;
 align-items: flex-start;
 gap: 5px;
 }
 
 #nats-aircraft-container .result-table {
 font-size: 12px;
 }
 
 #nats-aircraft-container .result-table th,
 #nats-aircraft-container .result-table td {
 padding: 4px;
 }
 
 
 #nats-aircraft-container .result-content-wrapper {
 flex-direction: column;
 gap: 10px;
 }
 
 #nats-aircraft-container .aircraft-thumbnail {
 align-self: center;
 }
 
 #nats-aircraft-container .aircraft-thumbnail img {
 max-width: 150px;
 }
}
&lt;/style&gt;</description></item><item><title>Ollama on the plane</title><link>https://danielms.site/zet/2025/ollama-on-the-plane/</link><pubDate>Wed, 02 Jul 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/ollama-on-the-plane/</guid><description>&lt;h1 id="ollama-on-the-plane"&gt;Ollama on the plane&lt;/h1&gt;
&lt;p&gt;I was doing some work offline on a flight today and (sadly) reached for AI only to realise my usual wasn&amp;rsquo;t available whilst offline.&lt;/p&gt;
&lt;p&gt;Then I remembered I setup &lt;code&gt;ollama&lt;/code&gt; with &lt;code&gt;qwen3&lt;/code&gt; the other day. Well it worked really well in helping me figure out how to use a library.
I haven&amp;rsquo;t used &lt;code&gt;ollama&lt;/code&gt; in a long time and found the local models quite inferior to online but &lt;code&gt;qwen3&lt;/code&gt; was really quite good.&lt;/p&gt;
&lt;p&gt;I think having access to an offline model is a real boon for productivity, especially when you just want to consult docs and ask questions about
simple stuff like that.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ai #ollama
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>NATS and Fly Gateway proxy</title><link>https://danielms.site/zet/2025/nats-and-fly-gateway-proxy/</link><pubDate>Tue, 24 Jun 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/nats-and-fly-gateway-proxy/</guid><description>&lt;h1 id="nats-and-fly-gateway-proxy"&gt;NATS and Fly Gateway proxy&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://badlyenginee.red/posts/2025-06-24-woodpecker-proxy/"&gt;Woodpecker CI, NATS and Fly.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was posted in my work chat today. It&amp;rsquo;s a simple but easy to understand and briefly touches on some of the power of NATS.&lt;/p&gt;
&lt;p&gt;NATS small footprint, and ability to use leaf nodes really blows up the possibilities for the tool. The fact that John is leveraging Synadia Cloud
with his home leaf node is really powerful. I love reading about how people are using these technologies and platforms to build what they &lt;em&gt;want&lt;/em&gt;
rather than shoehorning ideas into the requirements and limitations of other tools.&lt;/p&gt;
&lt;p&gt;Also, echo&amp;rsquo;s my distain for GitHub Actions. I think Dagger is the future of CI these days but Woodpecker/Drone is nice too.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#nats #synadia #cicd
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go vendor for debugging dependencies</title><link>https://danielms.site/zet/2025/go-vendor-for-debugging-dependencies/</link><pubDate>Sun, 22 Jun 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/go-vendor-for-debugging-dependencies/</guid><description>&lt;h1 id="go-vendor-for-debugging-dependencies"&gt;Go vendor for debugging dependencies&lt;/h1&gt;
&lt;p&gt;TIL how to debug dependencies in Go easily.&lt;/p&gt;
&lt;p&gt;One of my deps have a client timeout of &lt;code&gt;2 * time.Second&lt;/code&gt; which meant debugging the connections was nearly impossible.&lt;/p&gt;
&lt;p&gt;I ran &lt;code&gt;go mod vendor&lt;/code&gt; and I was then able to make changes to the vendored code. I increased the timeout to something reasonable and was able to find the issue.&lt;/p&gt;
&lt;p&gt;I could use the &lt;code&gt;replace&lt;/code&gt; directive in my &lt;code&gt;go.mod&lt;/code&gt; and clone down the repo but that seemed like a lot of work. If vendoring didn&amp;rsquo;t work it would of been my next step but &lt;code&gt;vendor&lt;/code&gt; is perfect for simple changes like this.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Github UI needs vimium extension to be useful</title><link>https://danielms.site/zet/2025/github-ui-needs-vimium-extension-to-be-useful/</link><pubDate>Thu, 19 Jun 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/github-ui-needs-vimium-extension-to-be-useful/</guid><description>&lt;h1 id="github-ui-needs-vimium-extension-to-be-useful"&gt;Github UI needs vimium extension to be useful&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;rant&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Long issues/PR&amp;rsquo;s etc in Github UI are a real PITA to navigate around. For instance, if I want to see the status of the branches actions I have to go all the way to the bottom of the page.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t have vimium installed I don&amp;rsquo;t know what you do here except scroll like a maniac (I&amp;rsquo;m sure there&amp;rsquo;s other hotkeys). &lt;code&gt;&amp;lt;Shift&amp;gt;-G&lt;/code&gt; is a saviour here.&lt;/p&gt;
&lt;p&gt;I think GitLab&amp;rsquo;s experience is far superior here. Firstly, the important stuff is at the top of the fold; Merge and pipeline statuses. Secondly, all comments can
be nested in GitLab; something thats not possible in Github. Having to create another entry on the page just to reply to a question (which could be several comments back)
is really bad UX.&lt;/p&gt;
&lt;p&gt;I feel like Github exists this way solely to be different to GitLab and because its the incumbent and they have zero incentive to change. Possibly, a lot of
developers don&amp;rsquo;t know there&amp;rsquo;s a better experience waiting for them as they&amp;rsquo;ve never used GitLab properly.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;/rant&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #github #gitlab
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Went to my local SecTalks today</title><link>https://danielms.site/zet/2025/went-to-my-local-sectalks-today/</link><pubDate>Tue, 03 Jun 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/went-to-my-local-sectalks-today/</guid><description>&lt;h1 id="went-to-my-local-sectalks-today"&gt;Went to my local SecTalks today&lt;/h1&gt;
&lt;p&gt;In an effort to be more involved in the tech community I ventured off the local SecTalks chapter for a talk on application security.&lt;/p&gt;
&lt;p&gt;The talk was pretty good, covered a lot of ground from basic defensive programming to supply chain management. The target audience was not software developers but security folk and I think Lochie did a good job from that regard.&lt;/p&gt;
&lt;p&gt;In my opinion, security engineers sometimes forget software engineering is just as vast and difficult as their trade. For red-team types, security failures only need to happen once to achieve their intent whereas application defence is a constant and ongoing process. This can lead to a misalignment of realities between the two professions and animosity from my experience. Throwing shade is always so easy!&lt;/p&gt;
&lt;p&gt;The speakers intent was to help remind everyone that we, application/software developers, do try our best, for the most part, to create secure products. I think he did that.&lt;/p&gt;
&lt;p&gt;I should probably get up and give a software related talk one day though I&amp;rsquo;d have to find a topic that&amp;rsquo;s suitable for the SecTalk crowd. Maybe security scanning at scale with NATS as the backbone! I was scheming up some ideas in the drive home; probably a network scanner that hits bug bounty targets, collects results stores in KV then aggregates data into an ObjectStore for historical analysis or something like that.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#sectalks #meetup
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go: errors.As versus type assertions</title><link>https://danielms.site/zet/2025/go-errors.as-versus-type-assertions/</link><pubDate>Mon, 26 May 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/go-errors.as-versus-type-assertions/</guid><description>&lt;h1 id="go-errorsas-versus-type-assertions"&gt;Go: errors.As versus type assertions&lt;/h1&gt;
&lt;p&gt;I prefer to use error unwrapping via &lt;code&gt;errors.As&lt;/code&gt; and here is my reasoning.&lt;/p&gt;
&lt;h2 id="examples"&gt;Examples&lt;/h2&gt;
&lt;p&gt;Type assertion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// fails if error is wrapped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newErr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// only works for direct new.Error instances&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Error unwrapping&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// works with wrapped errors like fmt.Errorf(&amp;#34;foo: %w&amp;#34;, newErr)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;newErr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// finds new.Error even if wrapped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="reasoning"&gt;Reasoning&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Introduced in &lt;code&gt;1.13&lt;/code&gt; alongside &lt;code&gt;errors.Is&lt;/code&gt; and &lt;code&gt;fmt.Errorf(&amp;quot;%w&amp;quot;,err)&lt;/code&gt; to fix type assertion issues.&lt;/li&gt;
&lt;li&gt;Type assertion is brittle (I personally try to avoid it when possible, sometimes unavoidable).&lt;/li&gt;
&lt;li&gt;Recommended by Go team, &lt;a href="https://go.dev/blog/go1.13-errors"&gt;https://go.dev/blog/go1.13-errors&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;If, in future, we decided to wrap this error, a type assertion will break; &lt;code&gt;errors.As&lt;/code&gt; will still work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="realworld-example"&gt;Realworld Example&lt;/h2&gt;
&lt;p&gt;I use this (sometimes) in my projects to enforce some sane error handling when processing JSON. Standard json errors can be hard to decern the true issue.&lt;/p&gt;
&lt;p&gt;This block uses several error handling techniques where they make sense with heavy use of &lt;code&gt;errors.As&lt;/code&gt; throughout. Much of this was taken from &lt;a href="https://lets-go-further.alexedwards.net/"&gt;Alex Edwards&lt;/a&gt;
who provides many great resources and tools for Gophers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;readJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dst&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Set a max body length. Without this it will accept unlimited size requests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;maxBytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1_048_576&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 1MB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MaxBytesReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxBytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Init a Decoder and call DisallowUnknownFields() on it before decoding.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// This means that JSON from the client will be rejected if it contains keys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// which do not match the target destination struct. If not implemented,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// the decoder will silently drop unknown fields - this will raise an error instead.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisallowUnknownFields&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// decode the request body into the target struct/destination&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// start triaging the various JSON related errors&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;syntaxError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SyntaxError&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unmarshallTypeError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UnmarshalTypeError&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;invalidUnmarshallError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InvalidUnmarshalError&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// Use the errors.As() function to check whether the error has the&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// *json.SyntaxError. If it does, then return a user-readable error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// message including the location of the problem&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;syntaxError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body contains badly-formed JSON (at character %d)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;syntaxError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// Decode() can also return an io.ErrUnexpectedEOF for JSON syntax errors. This is&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// checked for with errors.Is() and returns a generic error message to the client.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrUnexpectedEOF&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body contains badly-formed JSON&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// Wrong JSON types will return an error when they do not match the target destination&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// struct.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;unmarshallTypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unmarshallTypeError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body contains incorrect JSON type for field %q&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;unmarshallTypeError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body contains incorrect JSON type (at character %d)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;unmarshallTypeError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// An EOF error will be returned by Decode() if the request body is empty. Use errors.Is()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// to check for this and return a human-readable error message&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body must not be empty&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// If JSON contains a field which cannot be mapped to the target destination&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// then Decode will return an error message in the format &amp;#34;json: unknown field &amp;#34;&amp;lt;name&amp;gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// We check for this, extract the field name and interpolate it into an error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// which is returned to the client&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;json: unknown field &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;json: unknown field &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body contains unknown key %s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fieldName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// If the request body exceeds maxBytes the decode will fail with a&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// &amp;#34;http: request body too large&amp;#34;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http: request body too large&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body must not be larger than %d bytes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;maxBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// A json.InvalidUnmarshallError will be returned if we pass a non-nil pointer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// to Decode(). We catch and panic, rather than return an error.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;invalidUnmarshallError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// All else fails, return an error as-is&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Call Decode() again, using a pointer to anonymous empty struct as the&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// destination. If the body only has one JSON value then an io.EOF error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// will be returned. If there is anything else, extra data has been sent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// and we craft a custom error message back to the client&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;body must only contain a single JSON value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>NATS Micro example</title><link>https://danielms.site/zet/2025/nats-micro-example/</link><pubDate>Tue, 20 May 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/nats-micro-example/</guid><description>&lt;h1 id="nats-micro-example"&gt;NATS Micro example&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/nats-io/nats.go/blob/main/micro/README.md"&gt;NATS Micro&lt;/a&gt; is a super simple way to create microservices using NATS as the communication layer.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re using them a lot at Synadia internally but I&amp;rsquo;d never used them before.&lt;/p&gt;
&lt;p&gt;It only takes about 5 minutes to go from an empty file to running service that is handling load. It&amp;rsquo;s really impressive.&lt;/p&gt;
&lt;p&gt;Over the weekend I wrote a small (imperfect) &lt;a href="https://github.com/danielmichaels/nats-micro-hackernews"&gt;example application&lt;/a&gt; to learn enough to get started. It fetches the top posts
from HackerNews over the last 24 hours, saves them to NATS KV, sorts by &lt;em&gt;points&lt;/em&gt;, saves that days sorted posts to Object Store and
exposes a service that prints the top &lt;em&gt;n&lt;/em&gt; posts.&lt;/p&gt;
&lt;p&gt;Once the service is running you can inspect most things with just three commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;nats micro ls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nats micro info HackerNews&lt;/code&gt; - HackerNews being the service I&amp;rsquo;m running&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nats micro stats HackerNews&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One thing I did come across was mixing of &lt;code&gt;publish&lt;/code&gt; and &lt;code&gt;request&lt;/code&gt; semantics. In one of my endpoints I am publishing items as they come in. This allows
the consumer to execute on each item instead of a large list - typical work queue behaviour. But, if I &lt;code&gt;nc.Publish&lt;/code&gt; inside of a &lt;code&gt;req&lt;/code&gt; and don&amp;rsquo;t respond
it will cause the service to report an error. This is more about how I structured the code, and it&amp;rsquo;s easy to fix. I just created an two endpoints; one
for publishing/subscribing and one that will respond. The endpoint which only exists to pub/sub could probably be excluded from the services framework
but I included it because it gives great discoverability and statistics out of the box.&lt;/p&gt;
&lt;p&gt;Going forward I see myself using &lt;code&gt;nats micro&lt;/code&gt;/&lt;code&gt;nats service&lt;/code&gt; a lot. It&amp;rsquo;s such a simple, yet powerful construct around NATS.&lt;/p&gt;
&lt;p&gt;One thing I thought of was how this could be a OpenFaaS-like replacement, which also uses NATS, where you could bootstrap &amp;ldquo;functions&amp;rdquo; and do it
all in &lt;code&gt;micro&lt;/code&gt;. It would be easy to expose a single HTTP gateway like OpenFaaS&amp;rsquo;s which proxies/transforms the data into NATS protocol. These things
kind of already exist but not quite the same.&lt;/p&gt;
&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=AiUazlrtgyU"&gt;https://www.youtube.com/watch?v=AiUazlrtgyU&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=byHGNUqIONw"&gt;https://www.youtube.com/watch?v=byHGNUqIONw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=s2seyKyQ_Zw"&gt;https://www.youtube.com/watch?v=s2seyKyQ_Zw&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#nats #micro #services #openfaas
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Projects</title><link>https://danielms.site/projects/</link><pubDate>Mon, 12 May 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/projects/</guid><description>&lt;h2 id="check-redirectscom"&gt;&lt;a href="https://check-redirects.com?utm_medium=blog&amp;amp;utm_source=danielms.site&amp;amp;utm_campaign=projects"&gt;check-redirects.com&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A web application to help find out where your short links are taking you.
Entering a URL will return each hop&amp;rsquo;s status code, response headers, and the
entire redirect chain.&lt;/p&gt;
&lt;p&gt;Written in Go, with react and sqlite embedded using PocketBase.&lt;/p&gt;
&lt;h2 id="tarsrun"&gt;&lt;a href="https://tars.run?utm_medium=blog&amp;amp;utm_source=danielms.site&amp;amp;utm_campaign=projects"&gt;tars.run&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A dead simple, hobbyist, no guarantees URL shortener. Written in Go, rendered with Next.js and
persisted with SQLite through the magic of &lt;a href="https://litestream.io"&gt;Litestream&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="rfcpy"&gt;&lt;a href="https://github.com/danielmichaels/rfc.py/"&gt;RFC.py&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A simple python client that offers users the ability to search for, read and
bookmark RFC&amp;rsquo;s from the Internet Engineering Task Force whilst offline.&lt;/p&gt;
&lt;h2 id="http-tracer"&gt;&lt;a href="https://github.com/danielmichaels/http-tracer/"&gt;http-tracer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A python CLI tool for ascertaining the redirection path taken when accessing a
given URL.&lt;/p&gt;
&lt;h2 id="mudmapio"&gt;Mudmap.io&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Archived&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cloud monitoring and configuration for pfSense firewalls. Secure, safe and easy
to configure and use. Take the hassle out of multisite firewall maintenance.&lt;/p&gt;</description></item><item><title>DBeaver wordwrap and autoformat by default</title><link>https://danielms.site/zet/2025/dbeaver-wordwrap-and-autoformat-by-default/</link><pubDate>Thu, 08 May 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/dbeaver-wordwrap-and-autoformat-by-default/</guid><description>&lt;h1 id="dbeaver-wordwrap-and-autoformat-by-default"&gt;DBeaver wordwrap and autoformat by default&lt;/h1&gt;
&lt;p&gt;Make the &lt;code&gt;values&lt;/code&gt; panel auto-format and wordwrap by default. This requires changes to the config files in DBeaver&amp;rsquo;s workdirectory.&lt;/p&gt;
&lt;p&gt;Make sure DBeaver is closed before doing this.&lt;/p&gt;
&lt;p&gt;Edit these files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dbeaver/dbeaver/wiki/Workspace-Location#default-location-of-the-workspace"&gt;workdirectory&lt;/a&gt;/workspace6/.metadata/.plugins/org.eclipse.core.runtime/.settings/org.eclipse.ui.workbench.prefs&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# add these to bottom of file 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lineNumberRuler=true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wordwrap.enabled=true
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dbeaver/dbeaver/wiki/Workspace-Location#default-location-of-the-workspace"&gt;workdirectory&lt;/a&gt;/workspace6/.metadata/.plugins/org.jkiss.dbeaver.ui.editors.data/dialog_settings.xml&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Workbench&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;AbstractTextPanelEditor&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # these two lines
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;content.text.editor.auto-format&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;item&lt;/span&gt; &lt;span class="na"&gt;key=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;content.text.editor.word-wrap&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ref: &lt;a href="https://superuser.com/a/1827163/862055"&gt;https://superuser.com/a/1827163/862055&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#sql
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>iterm2 dropdown tips</title><link>https://danielms.site/zet/2025/iterm2-dropdown-tips/</link><pubDate>Mon, 05 May 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/iterm2-dropdown-tips/</guid><description>&lt;h1 id="iterm2-dropdown-tips"&gt;iterm2 dropdown tips&lt;/h1&gt;
&lt;p&gt;Setting up dropdown like &lt;code&gt;ddterm&lt;/code&gt;, &lt;code&gt;guake&lt;/code&gt; or &lt;code&gt;yakuke&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This explains it well with pictures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/vikbert/drop-down-iterm2-in-macos-2od"&gt;https://dev.to/vikbert/drop-down-iterm2-in-macos-2od&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To allow it to dropdown into the &amp;ldquo;desktop&amp;rdquo; you currently have focused:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Profiles&lt;/code&gt; -&amp;gt; &lt;code&gt;$profile_name&lt;/code&gt; -&amp;gt; &lt;code&gt;Keys&lt;/code&gt; -&amp;gt; &lt;code&gt;Configure Hotkey WindowFloating Window&lt;/code&gt; -&amp;gt; &lt;code&gt;Floating Window&lt;/code&gt; -&amp;gt; restart iTerm.&lt;/li&gt;
&lt;li&gt;ref: &lt;a href="https://gitlab.com/gnachman/iterm2/-/issues/10695#note_1267078045"&gt;https://gitlab.com/gnachman/iterm2/-/issues/10695#note_1267078045&lt;/a&gt;,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Prevent it from disappearing when you change focus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://superuser.com/questions/1100918/how-can-i-prevent-the-iterm2-hotkey-window-from-losing-focus"&gt;https://superuser.com/questions/1100918/how-can-i-prevent-the-iterm2-hotkey-window-from-losing-focus&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#macos #iterm
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>OpenFaaS golang-middleware go.work fix</title><link>https://danielms.site/zet/2025/openfaas-golang-middleware-go.work-fix/</link><pubDate>Mon, 21 Apr 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/openfaas-golang-middleware-go.work-fix/</guid><description>&lt;h1 id="openfaas-golang-middleware-gowork-fix"&gt;OpenFaaS golang-middleware go.work fix&lt;/h1&gt;
&lt;p&gt;This is an annoying problem where golang-middleware has a go.work referencing go
1.23 but I&amp;rsquo;m using 1.24 and it want versioning like 1.23.0&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; 2025/04/21 12:11:59 stdout: go: module &lt;span class="k"&gt;function&lt;/span&gt; listed in go.work file requires go &amp;gt;&lt;span class="o"&gt;=&lt;/span&gt; 1.23.0, but go.work lists go 1.23&lt;span class="p"&gt;;&lt;/span&gt; to update it:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; 2025/04/21 12:11:59 stdout: go work use
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; 2025/04/21 12:11:59 stdout: failed to build, error: &lt;span class="nb"&gt;exit&lt;/span&gt; status &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To fix this I had to go into the &lt;code&gt;template/golang-middleware/go.work&lt;/code&gt; file and
change &lt;code&gt;1.23&lt;/code&gt; to &lt;code&gt;1.23.0&lt;/code&gt; or it won&amp;rsquo;t build locally.&lt;/p&gt;
&lt;p&gt;This is janky but it works for now.&lt;/p&gt;
&lt;p&gt;Note: I use docker compose for local development with openfaas but even using
&lt;code&gt;faas-cli local-run&lt;/code&gt; this issue occurs. In fact, my fix didn&amp;rsquo;t fix this issue
using &lt;code&gt;local-run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#openfaas
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Homeassistant MQTT (and NATS)</title><link>https://danielms.site/zet/2025/homeassistant-mqtt-and-nats/</link><pubDate>Thu, 17 Apr 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/homeassistant-mqtt-and-nats/</guid><description>&lt;h1 id="homeassistant-mqtt-and-nats"&gt;Homeassistant MQTT (and NATS)&lt;/h1&gt;
&lt;p&gt;I have a couple of automations (pool pump being most important) that run over Zigbee2MQTT.&lt;/p&gt;
&lt;p&gt;I was using the Homeassistant built in MQTT broker but decided to test out NATS MQTT implementation and see if it was compatible.
It works a treat!&lt;/p&gt;
&lt;p&gt;However, when doing this to make it work I didn&amp;rsquo;t realise that I need to update the Z2M configuration and point it at the NATS cluster &lt;strong&gt;and&lt;/strong&gt;
reconfigure HA&amp;rsquo;s MQTT service to use it as well.&lt;/p&gt;
&lt;p&gt;When I triggered things from the HA Z2M panel everything worked as expected and I could see messages being routed but my automations wouldn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;The fix is &lt;code&gt;Settings -&amp;gt; Devices &amp;amp; services -&amp;gt; MQTT -&amp;gt; Reconfigure&lt;/code&gt; then change the broker address. Once I did that my automations started working again.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#mqtt #nats #Homeassistant
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Coolify basic auth</title><link>https://danielms.site/zet/2025/coolify-basic-auth/</link><pubDate>Sun, 13 Apr 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/coolify-basic-auth/</guid><description>&lt;h1 id="coolify-basic-auth"&gt;Coolify basic auth&lt;/h1&gt;
&lt;p&gt;Setting basic auth on Coolify requires editing the traefik labels.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve only needed to do it in docker compose files. Heres my snippet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; labels:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &amp;#39;traefik.http.middlewares.$UNIQUE_TO_PROJECT.basicauth.users=$USERNAME:$PASSWORD_GENERATED_BY_HTPASSWD&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Link to &lt;a href="https://coolify.io/docs/knowledge-base/proxy/traefik/basic-auth#docker-compose-and-services"&gt;docs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#coolify #traefik
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>My Thinkpad is dead, back on the (old) macbook</title><link>https://danielms.site/zet/2025/my-thinkpad-is-dead-back-on-the-old-macbook/</link><pubDate>Mon, 07 Apr 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/my-thinkpad-is-dead-back-on-the-old-macbook/</guid><description>&lt;h1 id="my-thinkpad-is-dead-back-on-the-old-macbook"&gt;My Thinkpad is dead, back on the (old) macbook&lt;/h1&gt;
&lt;p&gt;My trusty Thinkpad is finally so slow that I&amp;rsquo;ve calling it dead.&lt;/p&gt;
&lt;p&gt;This is bringing great shame to me as I know am resorting to using my partners old Mac.&lt;/p&gt;
&lt;p&gt;I think I&amp;rsquo;ve been a linux master race user since leaving mac around 2017-2018. Despite mac
being based on linux it still sucks to use.&lt;/p&gt;
&lt;p&gt;No docker (docker destop/orbstack/colima are all workarounds), inexplicable usage of the &amp;ldquo;Command&amp;rdquo; key in the &amp;ldquo;Alt&amp;rdquo; key position, weird security prompts requiring clicky clicky.&lt;/p&gt;
&lt;p&gt;But, I do get it, people enjoy this because some things &amp;ldquo;just work&amp;rdquo;. I mean they work because millions and millions are poured into its development.&lt;/p&gt;
&lt;p&gt;Now I need to figure out how to manage Chezmoi with two different operating systems. Should I keep using mise, or just leverage brew. I feel like the answers to these questions are only a footgun away!&lt;/p&gt;
&lt;p&gt;On a good note, rebuilding this machine showed me that my &lt;code&gt;zet&lt;/code&gt; tool no longer builds as an underlying package has been deleted upstream. I&amp;rsquo;m guessing its cached on my other machines so I didn&amp;rsquo;t notice - some learnings in there. I rewrote the tool using &lt;a href="https://github.com/alecthomas/kong"&gt;https://github.com/alecthomas/kong&lt;/a&gt; over the weekend to get it back online.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/danielmichaels/zet-cmd"&gt;https://github.com/danielmichaels/zet-cmd&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Edit: adding apps that have similar ergonomics on Mac as they do linux:&lt;/p&gt;
&lt;h2 id="terminals"&gt;Terminals&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Dropdown&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;iterm2&lt;/code&gt; is (so far) the only terminal I can configure to have dropdown like functionality similar to &lt;code&gt;guake&lt;/code&gt; or &lt;code&gt;ddterm&lt;/code&gt; on Linux. This really
breaks my flow. I have f12 mapped to dropdown a terminal meaning I can keep my IDE/Browser whatever in view whilst I have terminal access. Iterm2 supports this
BUT if you use mac&amp;rsquo;s desktop&amp;rsquo;s it&amp;rsquo;ll yank you from a fullscreen in desktop 3 back to desktop 1 if thats where you &amp;ldquo;main&amp;rdquo; desktop is. Iterm won&amp;rsquo;t dropdown into
the current desktop - only the &amp;ldquo;main&amp;rdquo;, typically desktop 1. I&amp;rsquo;m sure theres a mac way of explaining this but its a massively negative experience for my flow.&lt;/p&gt;
&lt;p&gt;Edit: I think I&amp;rsquo;ve fixed this by doing &lt;a href="https://gitlab.com/gnachman/iterm2/-/issues/10695#note_1267078045"&gt;this&lt;/a&gt;, which is to set &lt;code&gt;Profiles -&amp;gt; &amp;lt;profile&amp;gt; -&amp;gt; Keys -&amp;gt; Configure Hotkey WindowFloating Window -&amp;gt; Floating Window&lt;/code&gt; then restart iTerm. Works well so far..&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Terminal&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wezterm&lt;/code&gt; and &lt;code&gt;Iterm2&lt;/code&gt; so far are the only terminals that support the Zellij &lt;code&gt;option&lt;/code&gt; key (&lt;code&gt;alt&lt;/code&gt; rebinding).&lt;/p&gt;
&lt;p&gt;Terminals that do not support my workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ghostty&lt;/code&gt;: doesn&amp;rsquo;t work well with Zellij, no alt key alternative&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alacrity&lt;/code&gt;: as above&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kitty&lt;/code&gt;: as above&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#mac #linux
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Dagger CI is best CI</title><link>https://danielms.site/zet/2025/dagger-ci-is-best-ci/</link><pubDate>Sun, 23 Mar 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/dagger-ci-is-best-ci/</guid><description>&lt;h1 id="dagger-ci-is-best-ci"&gt;Dagger CI is best CI&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ve switched to using &lt;a href="dagger.io"&gt;dagger&lt;/a&gt; for CI and couldn&amp;rsquo;t be happier.&lt;/p&gt;
&lt;p&gt;Without pushing code to the origin I can run my entire suite of CI tools
locally. This means I know everything will run when I push and CI will pass
should I want to know.&lt;/p&gt;
&lt;p&gt;More importantly, I can debug/fix broken CI without needless commits. This saves
me a lot of time and if I was in a team, money wasted on compute/CI run minutes.&lt;/p&gt;
&lt;p&gt;Whats more, you can &amp;ldquo;checkout&amp;rdquo; someones branch using dagger without git
pulling/git switching. Dagger isn&amp;rsquo;t just limited to running CI - it can run
entire apps including supporting services. This means you can run the entire
project in the PR and check it works as it should with all the expected
services. Obviously, this is only as good as the effort that goes into writing
the Dagger files.&lt;/p&gt;
&lt;p&gt;Doing it this way makes running complicated applications easier, for instance,
if you needed NATS, Postgres and Redis to run E2E/Integration tests. You can now
do that locally or in your CI, e.g. GH Actions or Gitlab Runners.&lt;/p&gt;
&lt;p&gt;All that your CI runner does now is start your Dagger process and begin
everything inside Dagger. It&amp;rsquo;s a far better user experience.&lt;/p&gt;
&lt;p&gt;I need to sit down and write more about this amazing technology.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#dagger #ci
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>xclip helper</title><link>https://danielms.site/zet/2025/xclip-helper/</link><pubDate>Mon, 03 Mar 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/xclip-helper/</guid><description>&lt;h1 id="xclip-helper"&gt;xclip helper&lt;/h1&gt;
&lt;p&gt;I do a lot of json manipulation/reading in my day job. Often I need to copy
contents into the clipboard for putting into MR/Issues or documentation.&lt;/p&gt;
&lt;p&gt;Today I finally automated that after years of typing &lt;code&gt;xclip -sel clipboard&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; -gt &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# read a file and pipe to xclip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xclip -sel clipboard
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# read from stdin; &amp;#34;text&amp;#34; | clip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; xclip -sel clipboard
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;copied to clipboard!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#scripts #linux
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Taskfile dynamic command variables</title><link>https://danielms.site/zet/2025/taskfile-dynamic-command-variables/</link><pubDate>Wed, 29 Jan 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/taskfile-dynamic-command-variables/</guid><description>&lt;h1 id="taskfile-dynamic-command-variables"&gt;Taskfile dynamic command variables&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://taskfile.dev"&gt;Taskfile&lt;/a&gt; is amazing and I use it as the task runner for
all my projects.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no .env pollution between operations; define envs per task&lt;/li&gt;
&lt;li&gt;concatenate many commands together, for instance, regenerate tailwind when
files change and restart server&lt;/li&gt;
&lt;li&gt;simplify my life by reducing the amount of typing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There more reasons but those are the highlights.&lt;/p&gt;
&lt;p&gt;One thing that I only learned a couple months ago is that you can supply dynamic
variables to tasks.&lt;/p&gt;
&lt;p&gt;As example, I have a bunch of python files that need various environment
variables - each file needing a different one to the next. Thats really annoying
to configure manually. Here&amp;rsquo;s a simplistic version using &lt;code&gt;Taskfile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I&amp;rsquo;d do that in a &lt;code&gt;Taskfile&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PG_PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5672&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;benchmark:*&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PG_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;foo.bar&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TRACEMALLOC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ARG_1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{index .MATCH 0}}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;python -m {{.ARG_1}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;production:*&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;PG_HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;prod.foo.bar&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TRACEMALLOC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ARG_1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{index .MATCH 0}}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;python -m {{.ARG_1}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I can run my benchmark or production code for any python module by passing
the name of the module like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;task benchmark:pg_connection
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# or&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;task production:pg_connection
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thats a really silly example, heres one I actually use.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nats:cname:*&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Publish a message to NATS to trigger a CNAME lookup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ARG_1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{index .MATCH 0}}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;nats pub scanners.dangling_cname.{{.ARG_1}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I can publish messages to NATS easily in development to the queue I want
with any domain name I want to target.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;task nats:cname:google.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;task nats:cname:tesla
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This has been a massive quality of life improvement. Its possible to use more
than one ARG here too but for simplicity I&amp;rsquo;ve left it at just one.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#taskfile
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Mermaid.js for Hugo blogs</title><link>https://danielms.site/zet/2025/mermaid.js-for-hugo-blogs/</link><pubDate>Fri, 17 Jan 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/mermaid.js-for-hugo-blogs/</guid><description>&lt;h1 id="mermaidjs-for-hugo-blogs"&gt;Mermaid.js for Hugo blogs&lt;/h1&gt;
&lt;p&gt;Just implemented a mermaid.js shortcode in my Hugo blog.&lt;/p&gt;
&lt;p&gt;All credit to:
&lt;a href="https://navendu.me/posts/adding-diagrams-to-your-hugo-blog-with-mermaid/"&gt;https://navendu.me/posts/adding-diagrams-to-your-hugo-blog-with-mermaid/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This will only render on my Hugo blog and serves as a reference only.&lt;/p&gt;
&lt;p&gt;ChatGPT generated this - I don&amp;rsquo;t care if it makes sense its just a demo&lt;/p&gt;
&lt;div class="mermaid"&gt;

graph TD; A[Start] --&gt; B{Is it raining?}; B -- Yes --&gt; C[Take an umbrella]; B --
No --&gt; D[Go outside]; C --&gt; E[Proceed with your day]; D --&gt; E;

&lt;/div&gt;

&lt;p&gt;Note: this isn&amp;rsquo;t perfect and sometimes complex mermaid diagrams fail to render
when uploaded as a &lt;code&gt;zet&lt;/code&gt;. WIP.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#mermaid #hugo&lt;/p&gt;</description></item><item><title>PocketBase used in Beszel server monitoring</title><link>https://danielms.site/zet/2025/pocketbase-used-in-beszel-server-monitoring/</link><pubDate>Fri, 17 Jan 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/pocketbase-used-in-beszel-server-monitoring/</guid><description>&lt;h1 id="pocketbase-used-in-beszel-server-monitoring"&gt;PocketBase used in Beszel server monitoring&lt;/h1&gt;
&lt;p&gt;TIL: &lt;a href="https://github.com/henrygd/beszel"&gt;beszel&lt;/a&gt; uses PocketBase as its server backend!&lt;/p&gt;
&lt;p&gt;Lately, I&amp;rsquo;ve been semi obsessing over PB due to its rich featureset. For a Go
dev, it&amp;rsquo;s a game changer in terms of speed and ease of use.&lt;/p&gt;
&lt;p&gt;My side projects will never amount to anything needing more than a single
instance. Instead of burning a lot of my limited (2 kids) free time with
boilerplate before even getting to the core logic of whatever I&amp;rsquo;m trying to
build, I usually run out of steam.&lt;/p&gt;
&lt;p&gt;PocketBase goes a long way to fixing that. It gives so much out-of-the-box. IMO
its the Django of Go. Of course, unlike Laravel, Django, Ruby on Rails, it only
supports SQLite but.. do you really need PG or MySql? Probably not. Just get a
bigger box and vertically scale that bad boy!&lt;/p&gt;
&lt;p&gt;So today when I found out &lt;a href="https://github.com/henrygd/beszel"&gt;beszel&lt;/a&gt; uses PB, I was kinda over the moon. This
isn&amp;rsquo;t a &lt;em&gt;toy&lt;/em&gt; its a legit useful project. Even TailScale
&lt;a href="https://tailscale.com/blog/video-beszel"&gt;dev rel&lt;/a&gt; have made a video on it.&lt;/p&gt;
&lt;p&gt;If nothing else its made me even more adamant that PB is a underutilized tool in
the Go dev&amp;rsquo;s toolkit.&lt;/p&gt;
&lt;p&gt;I think the big reason for this is its kinda marketed as a JS dev&amp;rsquo;s tool.
Effectively just a FireBase alternative - and it can be. But, its written in Go
and can be extended as little or as much as you like.&lt;/p&gt;
&lt;p&gt;Want a message queue hanging off it - Done! Want a custom API - Done! The sky is
the limit. Only limitation is your imagination.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#til #go #pocketbase&lt;/p&gt;</description></item><item><title>PocketBase learned me a browser caching</title><link>https://danielms.site/zet/2025/pocketbase-learned-me-a-browser-caching/</link><pubDate>Wed, 15 Jan 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/pocketbase-learned-me-a-browser-caching/</guid><description>&lt;h1 id="pocketbase-learned-me-a-browser-caching"&gt;PocketBase learned me a browser caching&lt;/h1&gt;
&lt;p&gt;Im writing a file sharing tool to explore pocketbases abilities. The files are
stored in PB and retrieved by creating their unique url. This is easy using the
JS SDK and works well.&lt;/p&gt;
&lt;p&gt;I needed to extend this im go by using PBs hooks. Particularly the
OnRequestFileDownload hook. Everytime a file is downloaded we can create custom
logic before returning the file object&lt;/p&gt;
&lt;p&gt;In this case, im creating a entry in the analytics table to track each download.
I also have options like download limits, expiry and password protection. So I
need to use the hook.&lt;/p&gt;
&lt;p&gt;This is where the problem began. I could trigger the download by not the hook.&lt;/p&gt;
&lt;p&gt;It took me a number of hours to figure it out. Browsers cache things. After the
first download that url is cached and therefore it won&amp;rsquo;t fetch the contents from
the server again. Which is perplexing to me, but it wouldn&amp;rsquo;t even show up in my
dev tools tab. This means no hook called, no analytics. Not helpful&lt;/p&gt;
&lt;p&gt;The workaround is to cache bust by adding a querystrinh to the url. I used Unix
time which means every second the cache is busted and contents pulled freshly
from server. Perfect.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #browsers #pocketbase
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Using Github Actions to publish my zettelkasten notes</title><link>https://danielms.site/blog/github-actions-auto-publish-zettelkasten-notes/</link><pubDate>Tue, 14 Jan 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/github-actions-auto-publish-zettelkasten-notes/</guid><description>&lt;p&gt;I publish my zettelkasten notes to this website using GitHub Actions.&lt;/p&gt;
&lt;h2 id="what-is-a-zettelkasten"&gt;What is a zettelkasten?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A Zettelkasten is a personal tool for thinking and writing that creates an interconnected web of thought&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From the zettelkasten website, a true &lt;em&gt;zet&lt;/em&gt; should have the following properties:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Atomic - Each note contains one main idea&lt;/li&gt;
&lt;li&gt;Unique identifiers - Every note has a unique ID for referencing&lt;/li&gt;
&lt;li&gt;Linking - Notes are connected to other related notes via links&lt;/li&gt;
&lt;li&gt;Emergence - Knowledge and insights emerge from the network of connected notes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;rsquo;s meant to ensure that over time, the notes become more interconnected and the network of knowledge grows.&lt;/p&gt;
&lt;p&gt;My zettelkasten isn&amp;rsquo;t completely true to this model but its good enough for my use case.&lt;/p&gt;
&lt;h2 id="why-do-i-use-a-zettelkasten"&gt;Why do I use a zettelkasten?&lt;/h2&gt;
&lt;p&gt;When I learn something, figure out a new way or approach, ill try put it in my zet. Keeping them short and to the point
makes this more likely to happen. I found with a blog I finessed it too much which built up a resistance.&lt;/p&gt;
&lt;p&gt;My zets can be as polished or sloppy as I choose. I am the only person I write for.&lt;/p&gt;
&lt;p&gt;Unlike obsidian or other tools my approach is just text. Linking beyond a simple search, is on me to do at the time
of search. This works better than an automated tool because it&amp;rsquo;s the act of re-reading notes even if unrelated that
builds your mental model of your own knowledge base (spaced repetition)&lt;/p&gt;
&lt;h2 id="my-zet-cmd-tool"&gt;My zet-cmd tool&lt;/h2&gt;
&lt;p&gt;To write, read and edit my notes I have a custom CLI written in Go,
called &lt;a href="https://github.com/danielmichaels/zet-cmd"&gt;zet-cmd&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It uses GitHub as the backend to store the zets and your &lt;code&gt;$EDITOR&lt;/code&gt; to write them. It&amp;rsquo;s a simple tool that does one thing
and does it well enough for me.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the output of &lt;code&gt;zet&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;NAME&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zettelkasten&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commander&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;SYNOPSIS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;COMMAND&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;COMMANDS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;similar&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;man&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;conf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;manage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;conf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;danielmichaels&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;home&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;danielmichaels&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isosec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;screen&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;edit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;edit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Retrieve&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;editing&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;searchable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;variables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Find&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;zet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;over&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;DESCRIPTION&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Zettelkasten&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Bonzai&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;branch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;small&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slips&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;knowledge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Those&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slips&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uploaded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Github&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;ability&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ease&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;CONTACT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;danielms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;site&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;danielmichaels&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Issues&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;danielmichaels&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;LEGAL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;zet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.5.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Copyright&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2022&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Daniel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Michaels&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;License&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Apache&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The tool is written in Go and uses &lt;a href="https://github.com/rwxrob/bonzai"&gt;Bonzai&lt;/a&gt; for the CLI. Though, it&amp;rsquo;s using an older
version and will need updating to the latest version - which has several breaking changes. Could equally be rewritten in
&lt;a href="https://github.com/alecthomas/kong"&gt;kong&lt;/a&gt; another favourite of mine.&lt;/p&gt;
&lt;h2 id="how-i-use-it"&gt;How I use it&lt;/h2&gt;
&lt;p&gt;When I want to create a new zet I run &lt;code&gt;zet new&lt;/code&gt;. This will create a new entry in my zet repo using a timestamp as a
folder name, and inside that folder a new README.md. The &lt;code&gt;zet new &amp;quot;my new zet&amp;quot;&lt;/code&gt; will generate the README.md with
&lt;code&gt;my new zet&lt;/code&gt; as the &lt;code&gt;#&lt;/code&gt; (h1) heading and drop into the editor, in my case, &lt;code&gt;(n)vim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Saving and exiting will then prompt you to commit the changes to the repo, this will then push the changes to GitHub.&lt;/p&gt;
&lt;p&gt;Editing a zet is as simple as running &lt;code&gt;zet edit&lt;/code&gt;. This accepts &lt;code&gt;last&lt;/code&gt; for the most recent zet, or a search term.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;zet edit kong&lt;/code&gt; returns:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# zet edit kong&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="m"&gt;20230107005542&lt;/span&gt; Kong is an amazing CLI &lt;span class="k"&gt;for&lt;/span&gt; Go apps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="m"&gt;20240801073758&lt;/span&gt; How I write Golang CLI tools today &lt;span class="o"&gt;(&lt;/span&gt;using Kong&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Entering one of the numbers will open the zet in your editor.&lt;/p&gt;
&lt;p&gt;Viewing a zet is as simple as running &lt;code&gt;zet view&lt;/code&gt;. This accepts &lt;code&gt;last&lt;/code&gt; for the most recent zet, or a search term. Same
functionality as &lt;code&gt;zet edit&lt;/code&gt;. &lt;code&gt;zet view all&lt;/code&gt; will list all zets in the repo.&lt;/p&gt;
&lt;p&gt;Zets are rendered in the terminal using &lt;a href="https://github.com/charmbracelet/glow"&gt;glow&lt;/a&gt; to make them visually appealing.&lt;/p&gt;
&lt;h2 id="adding-zets-to-a-hugo-site"&gt;Adding zet&amp;rsquo;s to a hugo site&lt;/h2&gt;
&lt;p&gt;Sometimes I am not at my computer but want to reference something I&amp;rsquo;ve written about before. All my zets are public,
so I figured why not add them to my site. The added bonus is others can also view them, and it takes the &lt;em&gt;pressure&lt;/em&gt; off
of me to write a blog post.&lt;/p&gt;
&lt;p&gt;To do that I use a GitHub action to fetch the zets from my repo and add them to my site. The action runs every hour and
fetches the latest zets only triggering a rebuild if a new zet is added. This is done via the commit functionality of
&lt;a href="https://github.com/stefanzweifel/git-auto-commit-action"&gt;stefanzweifel/git-auto-commit-action&lt;/a&gt; which just commits
directly
to &lt;code&gt;master&lt;/code&gt;. It&amp;rsquo;s my blog, so I don&amp;rsquo;t care if it commits straight to master.&lt;/p&gt;
&lt;p&gt;All the tools and actions are available on &lt;a href="https://github.com/danielmichaels/danielms"&gt;GitHub&lt;/a&gt; in the &lt;code&gt;scripts&lt;/code&gt; and
&lt;code&gt;.github&lt;/code&gt;
directories. &lt;code&gt;./scripts/fetch-zet.go&lt;/code&gt; is the script this action uses to fetch the zets.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;A picture paints a thousand words. This graph shows the &lt;em&gt;broad&lt;/em&gt; flow of events. Refer to &lt;code&gt;./scripts/fetch-zet.go&lt;/code&gt; for
the full flow.&lt;/p&gt;
&lt;div class="mermaid"&gt;
graph TD
subgraph "Github Actions hourly schedule"
A[Start fetch-zet.go] --&gt; E[Fetch Contents from GitHub API]
E --&gt; F{Check if New Zets}
F --&gt;|Yes| G[Write JSON to assets/zet.json]
G --&gt; H[Create zet entry if needed]
F --&gt;|No| L[Exit - No Updates Needed]
H --&gt; M[Commit changes to repo]
end
M --&gt; N[Netlify rebuild on commit to Master]
&lt;/div&gt;

&lt;p&gt;Each hour a GitHub action will run the &lt;code&gt;fetch-zet&lt;/code&gt; binary. This will fetch the contents of the zet repo from the GitHub
API. The API returns a list of all the files in the repo.&lt;/p&gt;
&lt;p&gt;Once a new zet is identified, the script will write the new zet to a JSON file in the &lt;code&gt;assets&lt;/code&gt; directory. This is then
compared against the existing zet entries, if a new one is found the go program will create a new zet markdown file.&lt;/p&gt;
&lt;p&gt;The markdown file&amp;rsquo;s top level h1 tag is used to create the slug and title for the hugo page. The GitHub API provides the
title, but we don&amp;rsquo;t use that because if the h1 changes after the original commit, the API will still return the original
commits title.&lt;/p&gt;
&lt;p&gt;Go templating is used to create the markdown file with hugo&amp;rsquo;s frontmatter.&lt;/p&gt;
&lt;p&gt;Once done, the GitHub action will commit any new files to the repo. Netlify will then detect that a commit has been made
to &lt;code&gt;master&lt;/code&gt; and trigger a redeployment of the site.&lt;/p&gt;
&lt;h2 id="the-result"&gt;The result&lt;/h2&gt;
&lt;p&gt;So far, this simple process has allowed me to continue to write zets using my CLI tools but be able
to refer to them from anywhere. Before this I was locked out of viewing them without setting up my &lt;code&gt;zet&lt;/code&gt; CLI locally.&lt;/p&gt;
&lt;p&gt;As you can see, it works well enough.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/zet.png" alt="zet.png"&gt;&lt;/p&gt;</description></item><item><title>Migrating from CapRover to Coolify</title><link>https://danielms.site/zet/2025/migrating-from-caprover-to-coolify/</link><pubDate>Fri, 03 Jan 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/migrating-from-caprover-to-coolify/</guid><description>&lt;h1 id="migrating-from-caprover-to-coolify"&gt;Migrating from CapRover to Coolify&lt;/h1&gt;
&lt;p&gt;I think I&amp;rsquo;ve been running a CapRover instance since 2019 or 2020. So long that
my instance is still running Ubuntu 18.04. And, thats why I&amp;rsquo;m migrating.&lt;/p&gt;
&lt;p&gt;Recently, I blew away all my machines and installed Ubuntu 24.04. In doing this
I accidentally deleted my old CapRover SSH keys. The machine is SSH access only
so I don&amp;rsquo;t have anyway to manage it, or upgrade CapRover.&lt;/p&gt;
&lt;p&gt;I decided to spin up a new instance on Hetzner and try the backup/restore
process that CapRover recommends. The process is quite clunky IMO and ultimately
didn&amp;rsquo;t work. I have a number of services running on the CapRover and knowing I
can&amp;rsquo;t migrate them over gave me the impetus to move completely over to Coolify.&lt;/p&gt;
&lt;p&gt;Side note, one thing that annoyed me about CapRover is when services went down
due to node issue, such as running out of RAM (underprovisioning), all the
services would not automatically come back up (I miss kuberenetes!). This meant
I had to go into each app and manually restart them - theres no API or CLI for
this. Coolify has an API and I&amp;rsquo;m hopeful I could write scripts to do this.&lt;/p&gt;
&lt;p&gt;So far I&amp;rsquo;m really enjoying Coolify. I like the approach to deploying services
from GitHub. CapRover used webhooks but each service needed to manually sign in
and then copy/paste the webhook address into the repo. Coolify instead creates a
GitHub App so you can add whichever repo&amp;rsquo;s you want and it automatically has
access to them. Much better DX.&lt;/p&gt;
&lt;p&gt;I use basic auth a lot on my CapRover services (e.g. NTFY and
ChangeDetection.io). Coolify doesn&amp;rsquo;t have a button/automation for this. You have
to manually add a label to the service file (compose file) which Traefik then
enforces. Its not too hard but its also not as easy as a button and form with
username/ password fields for the auth like CapRover does. Also I&amp;rsquo;m not sure how
it handles websockets which, again, CapRover sets up with a single click. I
suspect this might another Traefik config I&amp;rsquo;ll have to set.&lt;/p&gt;
&lt;p&gt;So far, so good. Glad I made the switch.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#caprover #coolify #paas
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>PocketBase and Litestream - very unreliable</title><link>https://danielms.site/zet/2025/pocketbase-and-litestream---very-unreliable/</link><pubDate>Wed, 01 Jan 2025 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2025/pocketbase-and-litestream---very-unreliable/</guid><description>&lt;h1 id="pocketbase-and-litestream---very-unreliable"&gt;PocketBase and Litestream - very unreliable&lt;/h1&gt;
&lt;p&gt;After most of a day I&amp;rsquo;ve given up on a pure PocketBase Litestream setup.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s really flaky and I experienced several instances where the restored database was subseqently
overritten by PocketBase. I couldn&amp;rsquo;t ascertain why and don&amp;rsquo;t have the time to understand the problem.&lt;/p&gt;
&lt;p&gt;Frankly, I&amp;rsquo;m disappointed by it. I think PocketBase will be my new MVP/PoC stack with Go. Its an amazing
tool and offers so much out of the box.&lt;/p&gt;
&lt;p&gt;I created &amp;lt;pocketshare.infra.ptco.rocks&amp;gt; with it in about a week. Its a file sharing app - which I felt
pushed all the PocketBase buttons. It was an excellent thing to build for learning PB.&lt;/p&gt;
&lt;p&gt;For now, I&amp;rsquo;m resorting to volume mounting the DB but running it in a container. I&amp;rsquo;ll rely on
automated backups and manual intervention in case of emergency rather than automation via Litestream.&lt;/p&gt;
&lt;p&gt;This will work great but I&amp;rsquo;m still down about not figuring out Litestream!&lt;/p&gt;
&lt;p&gt;ref:
- &lt;a href="https://pocketbase.io"&gt;https://pocketbase.io&lt;/a&gt;
- &lt;a href="https://github.com/pocketbase/pocketbase/discussions/3080"&gt;https://github.com/pocketbase/pocketbase/discussions/3080&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #pocketbase
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>pycharm-goland broke my GPG verified commits</title><link>https://danielms.site/zet/2024/pycharm-goland-broke-my-gpg-verified-commits/</link><pubDate>Mon, 16 Dec 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/pycharm-goland-broke-my-gpg-verified-commits/</guid><description>&lt;h1 id="pycharm-goland-broke-my-gpg-verified-commits"&gt;pycharm-goland broke my GPG verified commits&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;~/.gnupg/gpg-agent.conf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Comment the line that uses &lt;code&gt;pinentry-program &amp;lt;path&amp;gt;/pinentry-ide.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Reload gpg agent with &lt;code&gt;gpgconf --reload gpg-agent&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hopefully they fix this in the next release (they usually are very responsive to
these issues) because each time you open the IDE it&amp;rsquo;s going to prompt you to
install this script again.&lt;/p&gt;
&lt;p&gt;ref:
&lt;a href="https://youtrack.jetbrains.com/issue/IJPL-173525/Git-GPG-signing-fails-with-errors-like-Bad-CA-certificate-or-failed-to-write-commit-object"&gt;https://youtrack.jetbrains.com/issue/IJPL-173525/Git-GPG-signing-fails-with-errors-like-Bad-CA-certificate-or-failed-to-write-commit-object&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ide #gpg #bug
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>structured logging; convincing a team to use it</title><link>https://danielms.site/zet/2024/structured-logging-convincing-a-team-to-use-it/</link><pubDate>Tue, 10 Dec 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/structured-logging-convincing-a-team-to-use-it/</guid><description>&lt;h1 id="structured-logging-convincing-a-team-to-use-it"&gt;structured logging; convincing a team to use it&lt;/h1&gt;
&lt;p&gt;My $DAYJOB deals with millions of messages through the message bus, each
messsage kicking off many more IO bound operations.&lt;/p&gt;
&lt;p&gt;We use Loki and Grafana to monitor the logs.&lt;/p&gt;
&lt;p&gt;Our python code relies heavily on threading and its hard to know whats happening
in each thread.&lt;/p&gt;
&lt;p&gt;And we don&amp;rsquo;t use structured logging. Which ever since I started left a sour
taste in my mouth as I&amp;rsquo;ve seen it used very well in other environment which 10x
less load and events.&lt;/p&gt;
&lt;p&gt;Today, after someone made a big change which included what I consider bad
changes to the plain loggers I decided to make the effort to convince them that
structured logging is worth the effort.&lt;/p&gt;
&lt;p&gt;My core driver is the time wasted finding issues in our threaded code which we
could easily have detected/debugged with a trace ID spanning the events.
OpenTelemetry in this team would be a very very hard sell. But a trace ID? Thats
doable and has a lot of upside.&lt;/p&gt;
&lt;p&gt;I know I will get a lot of pushback. Pretty much all the 12 factor type
improvements I&amp;rsquo;ve made have received little interest.&lt;/p&gt;
&lt;p&gt;So this time I made a &lt;strong&gt;huge&lt;/strong&gt; effort to push this. Incredibly detailed
reasoning, issues, example PoC&amp;rsquo;s in the core libraries. Tomorrow, I&amp;rsquo;ll start
making the merge requests to get the initial work done. I think if I can get our
core rabbit library cut over and then our two &amp;ldquo;hot path&amp;rdquo; projects 80% of the way
there I&amp;rsquo;ll have the majority interested.&lt;/p&gt;
&lt;p&gt;And, if not. Well, I&amp;rsquo;ll just document how I did it and keep all those learnings
for future $DAYJOB if needed.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#logging #software #team
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>DNS Enumeration summary</title><link>https://danielms.site/zet/2024/dns-enumeration-summary/</link><pubDate>Wed, 20 Nov 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/dns-enumeration-summary/</guid><description>&lt;h1 id="dns-enumeration-summary"&gt;DNS Enumeration summary&lt;/h1&gt;
&lt;p&gt;Testing out fabric summary of youtube clips&lt;/p&gt;
&lt;p&gt;From &lt;a href="https://www.youtube.com/watch?v=GRv-O-Hq9Io"&gt;https://www.youtube.com/watch?v=GRv-O-Hq9Io&lt;/a&gt; which is about DNS
enumeration and other techniques.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;fabric -sp extract_wisdom -y=$URL&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="summary"&gt;SUMMARY&lt;/h2&gt;
&lt;p&gt;Stoke Frederick hosts Bounty Thursdays, discussing tools, techniques, and
insights about DNS in cybersecurity.&lt;/p&gt;
&lt;h2 id="ideas"&gt;IDEAS:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DNS is a foundational element in cybersecurity, often blamed when issues
arise.&lt;/li&gt;
&lt;li&gt;The evolution of DNS tools reflects ongoing advancements in cybersecurity
techniques.&lt;/li&gt;
&lt;li&gt;Subdomain enumeration tools like Amass and Subfinder are vital for passive
discovery.&lt;/li&gt;
&lt;li&gt;Active DNS enumeration requires robust tools for effective brute forcing and
validation.&lt;/li&gt;
&lt;li&gt;Historical tools continue to influence current methodologies in DNS
penetration testing.&lt;/li&gt;
&lt;li&gt;Community knowledge sharing enhances understanding of DNS vulnerabilities and
tools.&lt;/li&gt;
&lt;li&gt;Subdomain takeovers represent a lucrative opportunity in bug bounty hunting.&lt;/li&gt;
&lt;li&gt;Utilizing various DNS resolvers is crucial to avoid false positives during
testing.&lt;/li&gt;
&lt;li&gt;Understanding DNS records can reveal security weaknesses in an organization’s
infrastructure.&lt;/li&gt;
&lt;li&gt;Effective DNS enumeration involves both passive and active techniques for
comprehensive testing.&lt;/li&gt;
&lt;li&gt;Wildcard DNS responses can obscure vulnerabilities during penetration testing.&lt;/li&gt;
&lt;li&gt;Tools like DNS Validator help maintain clean resolver lists for accurate
testing.&lt;/li&gt;
&lt;li&gt;Leveraging historical DNS data can provide insights into vulnerabilities and
attack surfaces.&lt;/li&gt;
&lt;li&gt;The importance of community collaboration is highlighted in the development of
DNS tools.&lt;/li&gt;
&lt;li&gt;Anomalies in DNS responses can indicate potential security issues or
misconfigurations.&lt;/li&gt;
&lt;li&gt;Continuous improvement and adaptation of tools are essential in the evolving
cybersecurity landscape.&lt;/li&gt;
&lt;li&gt;Properly configured DNS records can prevent unauthorized access and reduce
attack vectors.&lt;/li&gt;
&lt;li&gt;Rebinding attacks demonstrate the complexity of DNS vulnerabilities and their
exploitation.&lt;/li&gt;
&lt;li&gt;The DNS space requires ongoing education and exploration to uncover new
techniques.&lt;/li&gt;
&lt;li&gt;Experimenting with custom tools can lead to innovation in DNS testing
methodologies.&lt;/li&gt;
&lt;li&gt;Effective communication among cybersecurity professionals enhances the
collective knowledge base.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="insights"&gt;INSIGHTS:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DNS remains central to cybersecurity; understanding its nuances is essential
for effective testing.&lt;/li&gt;
&lt;li&gt;The evolution of tools and techniques reflects the dynamic nature of the
cybersecurity landscape.&lt;/li&gt;
&lt;li&gt;Community-driven development leads to innovative solutions addressing complex
cybersecurity challenges.&lt;/li&gt;
&lt;li&gt;Subdomain enumeration is a key aspect of reconnaissance, influencing overall
penetration testing success.&lt;/li&gt;
&lt;li&gt;Active and passive techniques must be combined for a comprehensive approach to
DNS security.&lt;/li&gt;
&lt;li&gt;The ability to adapt and refine tools is crucial for maintaining effectiveness
in testing.&lt;/li&gt;
&lt;li&gt;Historical data can provide valuable insights into vulnerabilities and attack
patterns in DNS.&lt;/li&gt;
&lt;li&gt;Collaboration within the cybersecurity community fosters knowledge sharing and
tool improvement.&lt;/li&gt;
&lt;li&gt;Understanding DNS vulnerabilities can lead to more effective defense
strategies in organizations.&lt;/li&gt;
&lt;li&gt;Continuous learning and exploration are vital for staying ahead in the
cybersecurity field.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="quotes"&gt;QUOTES:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;DNS is the staple of everything and when things break we can always blame it
on DNS.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;There&amp;rsquo;s just so many tools out there.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;The essence of why we write a tool is because a lot of people starting out
need good resolvers.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;The first two core spaces are passive and active.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Community is so important; the more you got in community, the more you
learn.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;You can learn to code in one language and better translate yourself to
another.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;It&amp;rsquo;s a classic catch-all; you will send an email to a server and it will
respond.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;You don&amp;rsquo;t always have to get a result; you just want to know.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;If you see a few people suddenly starring a project, it&amp;rsquo;s a good indication
they&amp;rsquo;re onto something.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Speed, reliability, and the ability to pipe into other tools are key metrics
for evaluating tools.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;You can get more insight by running it yourself.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;The DNS space is probably the best example of why community is important.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;The essence of DNS vulnerabilities is that they&amp;rsquo;re often overlooked.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Rebinding attacks can reveal internal resources previously thought secure.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Subdomain takeovers have historically been the most profitable bug in bug
bounty hunting.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Every person who goes out there and writes a tool deserves recognition.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;You need to introduce other resolvers if you&amp;rsquo;re scaling.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;DNS is a foundational element in cybersecurity.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Effective DNS enumeration involves both passive and active techniques.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Historical DNS data provides insights into vulnerabilities and attack
patterns.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="habits"&gt;HABITS:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Regularly engage with the cybersecurity community to share knowledge and learn
new techniques.&lt;/li&gt;
&lt;li&gt;Experiment with different DNS tools to determine which best fits your
workflow.&lt;/li&gt;
&lt;li&gt;Maintain a clean resolver list for accurate DNS enumeration during testing.&lt;/li&gt;
&lt;li&gt;Continuously update and refine personal coding skills to adapt to evolving
tools.&lt;/li&gt;
&lt;li&gt;Deploy personal domains for hands-on practice with DNS configurations and
vulnerabilities.&lt;/li&gt;
&lt;li&gt;Use a VPN when conducting DNS tests from home to avoid ISP issues.&lt;/li&gt;
&lt;li&gt;Schedule regular practice sessions with new tools to familiarize yourself with
their functionalities.&lt;/li&gt;
&lt;li&gt;Engage in collaborative projects to enhance tool development and understanding
of DNS.&lt;/li&gt;
&lt;li&gt;Stay informed about the latest trends and updates in cybersecurity tools and
techniques.&lt;/li&gt;
&lt;li&gt;Document and share personal experiences with tools to contribute to community
knowledge.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="facts"&gt;FACTS:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;DNS is often blamed for network issues, reflecting its foundational role in
internet infrastructure.&lt;/li&gt;
&lt;li&gt;Historical tools for DNS enumeration have shaped current methodologies and
practices.&lt;/li&gt;
&lt;li&gt;Subdomain takeovers can yield significant financial rewards in bug bounty
programs.&lt;/li&gt;
&lt;li&gt;Many DNS tools emphasize passive discovery to minimize impact on target
systems.&lt;/li&gt;
&lt;li&gt;Active DNS enumeration requires a robust understanding of brute forcing
techniques.&lt;/li&gt;
&lt;li&gt;Community collaboration leads to the development of innovative cybersecurity
tools.&lt;/li&gt;
&lt;li&gt;Proper DNS configuration can significantly reduce an organization&amp;rsquo;s attack
surface.&lt;/li&gt;
&lt;li&gt;Wildcard DNS responses can obscure potential vulnerabilities during
penetration testing.&lt;/li&gt;
&lt;li&gt;Continuous learning is crucial in adapting to the evolving landscape of
cybersecurity.&lt;/li&gt;
&lt;li&gt;The DNS space is rich with opportunities for discovering vulnerabilities and
attack vectors.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="references"&gt;REFERENCES:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Amass&lt;/li&gt;
&lt;li&gt;Subfinder&lt;/li&gt;
&lt;li&gt;DNS Validator&lt;/li&gt;
&lt;li&gt;DNS Dumpster&lt;/li&gt;
&lt;li&gt;ctfr&lt;/li&gt;
&lt;li&gt;Pure DNS&lt;/li&gt;
&lt;li&gt;DNSX&lt;/li&gt;
&lt;li&gt;Nuclei&lt;/li&gt;
&lt;li&gt;Plunder&lt;/li&gt;
&lt;li&gt;Security Trails&lt;/li&gt;
&lt;li&gt;ByNine&lt;/li&gt;
&lt;li&gt;TryHackMe&lt;/li&gt;
&lt;li&gt;Singularity Framework&lt;/li&gt;
&lt;li&gt;Hack Luke&amp;rsquo;s Tools&lt;/li&gt;
&lt;li&gt;Project Discovery Tools&lt;/li&gt;
&lt;li&gt;DNS Cool&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="one-sentence-takeaway"&gt;ONE-SENTENCE TAKEAWAY&lt;/h2&gt;
&lt;p&gt;Understanding DNS vulnerabilities and employing effective tools are essential
for successful penetration testing and bug bounty hunting.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;RECOMMENDATIONS:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Engage in community forums to share insights and learn from experienced
cybersecurity professionals.&lt;/li&gt;
&lt;li&gt;Regularly practice with various DNS tools to enhance your skills in
enumeration and testing.&lt;/li&gt;
&lt;li&gt;Experiment with setting up your own domains to gain practical experience with
DNS configurations.&lt;/li&gt;
&lt;li&gt;Stay updated on the latest trends and tools in the cybersecurity landscape for
effective testing.&lt;/li&gt;
&lt;li&gt;Combine passive and active DNS enumeration techniques for comprehensive
vulnerability assessments.&lt;/li&gt;
&lt;li&gt;Leverage historical DNS data to identify potential vulnerabilities and attack
vectors.&lt;/li&gt;
&lt;li&gt;Document your experiences with tools to contribute to the broader
cybersecurity community.&lt;/li&gt;
&lt;li&gt;Collaborate with others on projects to foster innovation in DNS testing
methodologies.&lt;/li&gt;
&lt;li&gt;Develop a habit of evaluating new tools based on speed, reliability, and
integration capabilities.&lt;/li&gt;
&lt;li&gt;Participate in workshops or training sessions focused on advanced DNS
techniques and tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#fabric #dns
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Deploy to Netlify using Github Actions</title><link>https://danielms.site/zet/2024/deploy-to-netlify-using-github-actions/</link><pubDate>Tue, 22 Oct 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/deploy-to-netlify-using-github-actions/</guid><description>&lt;h1 id="deploy-to-netlify-using-github-actions"&gt;Deploy to Netlify using Github Actions&lt;/h1&gt;
&lt;p&gt;After switching some projects from my personal account into a new GitHub
organisation my Netlify automated deploys started failing.&lt;/p&gt;
&lt;p&gt;To use a private organisation you have to pay $19/USD per month. Considering its
the 22nd and I&amp;rsquo;ve used 5 of my 300 free build minutes they can get stuffed.&lt;/p&gt;
&lt;p&gt;So I had to switch from automated to triggering the deployment using their CLI
in GitHub actions.&lt;/p&gt;
&lt;p&gt;Honestly I just ask CodyAI (sourcegraph&amp;rsquo;s AI tool) to convert my &lt;code&gt;hugo.toml&lt;/code&gt;
file into a GitHub actions template and it got me 90% done. I had to tweak a
couple things (switch to pnpm for example)&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the file,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Netlify Deploy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;branches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build-and-deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Checkout Repository&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Node.js&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-node@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;node-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install pnpm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pnpm/action-setup@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Get pnpm store directory&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;STORE_PATH=$(pnpm store path --silent)&amp;#34; &amp;gt;&amp;gt; $GITHUB_ENV&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup pnpm cache&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/cache@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ env.STORE_PATH }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ runner.os }}-pnpm-store-${{ hashFiles(&amp;#39;**/pnpm-lock.yaml&amp;#39;) }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restore-keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ${{ runner.os }}-pnpm-store-&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Hugo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;peaceiris/actions-hugo@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hugo-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.134.3&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;extended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Setup Go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-go@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;go-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1.23&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Dependencies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pnpm install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Project Setup and Build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; pnpm project-setup
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; pnpm build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deploy to Netlify&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nwtgck/actions-netlify@v2.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;publish-dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./public&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;production-branch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;github-token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.NETLIFY_GITHUB_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deploy-message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Deploy from GitHub Actions&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enable-pull-request-comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enable-commit-comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;overwrites-pull-request-comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NETLIFY_AUTH_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.NETLIFY_AUTH_TOKEN }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ secrets.NETLIFY_SITE_ID }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout-minutes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And its working perfectly!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#netlify #github #cicd
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>git log to PR message</title><link>https://danielms.site/zet/2024/git-log-to-pr-message/</link><pubDate>Sun, 20 Oct 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/git-log-to-pr-message/</guid><description>&lt;h1 id="git-log-to-pr-message"&gt;git log to PR message&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;git log &amp;lt;sha&amp;gt;..HEAD | mods &amp;quot;write a concise pull request for these changes&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;And it produced a really decent summary of all the changes. I think the key is
create meaningful commit messages.&lt;/p&gt;
&lt;p&gt;I like to add a lot of context to my commit bodies and it pays off here.&lt;/p&gt;
&lt;p&gt;Example&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This pull request introduces multiple updates and refactors across the dashboard
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;templates, email account management, and Slack integration. These changes aim to
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;enhance performance, usability, and maintainability.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## Key Changes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;1.&lt;/span&gt; Dashboard Enhancements:\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; • Simplified, restructured, and cleaned up dashboard templates, notably\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; accounts.html and connections.html ([Commit 5bd7454]).\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; • Added accounts listing with filtering, sorting, and pagination ([Commit\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; a2b906d]).\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; • Implemented detailed connection display and profile updates ([Commit\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; d7cdda8]).
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;2.&lt;/span&gt; Account and Connection Management:\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; • Introduced delete functionality with confirmation for accounts and\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; connections ([Commit 72818d1]).\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; • Improved URL routing and messaging for account actions in views.py\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ([Commit d968d7e]).\
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ..truncated
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ai #git
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>docker compose up --watch</title><link>https://danielms.site/zet/2024/docker-compose-up---watch/</link><pubDate>Fri, 27 Sep 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/docker-compose-up---watch/</guid><description>&lt;h1 id="docker-compose-up-watch"&gt;docker compose up &amp;ndash;watch&lt;/h1&gt;
&lt;p&gt;I used to volume mount to get &amp;ldquo;hot-reload&amp;rdquo; behaviour. Now I&amp;rsquo;ve switched to
&lt;code&gt;docker compose watch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Its much simpler to understand. Here&amp;rsquo;s an example of syncing code and restart a
celery worker container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;host&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network_mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;host&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;celery&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;.env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;depends_on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;service_healthy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rabbit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;service_healthy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;worker_data:/usr/src/app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;develop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sync+restart&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;./&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;.venv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;worker_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A simplified &lt;code&gt;compose.yml&lt;/code&gt; file but the core part I want to showcase is the
&lt;code&gt;develop&lt;/code&gt; key.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;develop.watch&lt;/code&gt; lets you define the path to &amp;ldquo;watch&amp;rdquo;, target to sync the
changes to and the action to take.&lt;/p&gt;
&lt;p&gt;For this container I have to restart it but its very robust and simple to
follow. You can also just &lt;code&gt;sync&lt;/code&gt; which would work well for some applications.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible to achieve similar things with other tools (&lt;code&gt;watchfiles&lt;/code&gt; is a good
one) and methods but I prefer to lean on a &lt;code&gt;docker&lt;/code&gt; builtin these days.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#docker
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Slack Bolt Python oauth install fix</title><link>https://danielms.site/zet/2024/slack-bolt-python-oauth-install-fix/</link><pubDate>Thu, 26 Sep 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/slack-bolt-python-oauth-install-fix/</guid><description>&lt;h1 id="slack-bolt-python-oauth-install-fix"&gt;Slack Bolt Python oauth install fix&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/slackapi/bolt-python/issues/492"&gt;https://github.com/slackapi/bolt-python/issues/492&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It was not apparent to me that the &lt;code&gt;slack-bolt&lt;/code&gt; python app requires you to start
the oauth flow from &lt;!-- raw HTML omitted --&gt;/slack/install&lt;/p&gt;
&lt;p&gt;At that address it will load a page with a &amp;ldquo;Install&amp;rdquo; button (not your own) and
only clicking that to start the flow will work.&lt;/p&gt;
&lt;p&gt;Otherwise it don&amp;rsquo;t work! Super confusing and wasted a number of precious hours
on this.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#slack #python
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>NF20MESH bridge mode</title><link>https://danielms.site/zet/2024/nf20mesh-bridge-mode/</link><pubDate>Mon, 23 Sep 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/nf20mesh-bridge-mode/</guid><description>&lt;h1 id="nf20mesh-bridge-mode"&gt;NF20MESH bridge mode&lt;/h1&gt;
&lt;p&gt;This thing is a POS.&lt;/p&gt;
&lt;p&gt;To set bridge mode do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;disable wifi 2.4/5g and stop SSID being broadcast&lt;/li&gt;
&lt;li&gt;remove all connections&lt;/li&gt;
&lt;li&gt;disable DHCP on v4 and v6&lt;/li&gt;
&lt;li&gt;add ptm and eth connections back as bridge mode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now kick the connection on ABB and power off for a couple of minutes&lt;/p&gt;</description></item><item><title>Goa websocket with subprotocols (auth)</title><link>https://danielms.site/zet/2024/goa-websocket-with-subprotocols-auth/</link><pubDate>Sat, 07 Sep 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/goa-websocket-with-subprotocols-auth/</guid><description>&lt;h1 id="goa-websocket-with-subprotocols-auth"&gt;Goa websocket with subprotocols (auth)&lt;/h1&gt;
&lt;p&gt;How I figured out to pass an API key to my websocket endpoint using &lt;code&gt;goa&lt;/code&gt; and
&lt;code&gt;React&lt;/code&gt;. It wasn&amp;rsquo;t straight forward!&lt;/p&gt;
&lt;p&gt;Firstly, you have to setup an &lt;code&gt;websocket.Upgrader{}&lt;/code&gt; and assign &lt;code&gt;CheckOrigin&lt;/code&gt;
and &lt;code&gt;Subprotocols&lt;/code&gt; values.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;upgrader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Upgrader&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;upgrader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CheckOrigin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// setup authorised origins; this is a demo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// You have to tell it that Sec-Websocket-Protocol and your custom auth header are valid subprotocols&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// or it won&amp;#39;t work! and you will get an error like this:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Error during WebSocket handshake: Sent non-empty &amp;#39;Sec-WebSocket-Protocol&amp;#39; header but no response was received&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;upgrader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Subprotocols&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Sec-Websocket-Protocol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;x-api-key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then you have to add some headers to your request. But before we do that, in Goa
we need a custom middleware to read the subprotocol. Using
&lt;code&gt;Authorization: Bearer asdadad&lt;/code&gt;, or in my case &lt;code&gt;x-api-key: 123&lt;/code&gt; like a
traditional HTTP request doesn&amp;rsquo;t work.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# authorization (subprotocol) header on a websocket 101 Switching
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sec-WebSocket-Protocol: x-api-key, key_000000000000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because its &lt;code&gt;x-api-key, key_000000000000&lt;/code&gt;, you have to split the string and do
some manipulation. Things you don&amp;rsquo;t have to do in normal HTTP requests.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I do it using a Goa middleware&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// set custom context keys&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xApiKeyWSType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;apiKeyCtxKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;xApiKeyWSType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;x-api-key&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WebsocketConnection&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// if not a websocket connection, continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Connection&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;upgrade&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Upgrade&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="c1"&gt;// Not a websocket connection&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Get the header with the key&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Sec-WebSocket-Protocol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the header will return a string: x-api-key, key_000000000000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// we need to split, then trim white space&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TrimSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;apiKeyCtxKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ServeHTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then in your security scheme, you&amp;rsquo;ll need to get that context key we just set.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ApiKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;scheme&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIKeyScheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Queries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetXApiKeyWS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// truncated but do something with the key.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// a typical HTTP request will have the key available already but&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// we need to grab it via the GetXApiKeyWS function&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It took me a few hours of reading issues, code, websocket specs to figure out
why &lt;code&gt;wscat&lt;/code&gt; and &lt;code&gt;insomnia&lt;/code&gt; could connect but not React. Inspecting the headers
was a big clue - no response headers were set from the server. But, if I
connected to a globally available unauthenticated server such as
&lt;code&gt;wss://echo.websocket.events&lt;/code&gt; they were.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s all working now and apart from this I&amp;rsquo;m really happy with how easy it is to
get going with Goa and websockets. From a &lt;code&gt;design.go&lt;/code&gt;/DSL prespective its
simple. I just replaced &lt;code&gt;Result&lt;/code&gt; with &lt;code&gt;StreamingResult&lt;/code&gt; in my Services'
&lt;code&gt;Method&lt;/code&gt;. Had to plumb in the &lt;code&gt;upgrader&lt;/code&gt; but it was simple. The hard part was
making sure access was still secure behind my API keys (and soon to be JWTs).&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#goa #websockets #react #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Hire me</title><link>https://danielms.site/hire/</link><pubDate>Thu, 05 Sep 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/hire/</guid><description>&lt;p&gt;Hello! I&amp;rsquo;m a professional developer specializing in Go, Python, and JavaScript. I can take your ideas and designs and
turn them into web applications, ready for deployment.&lt;/p&gt;
&lt;h2 id="skills"&gt;Skills&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Languages:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go&lt;/li&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;JavaScript&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Areas of Expertise:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Transforming design into operational web applications&lt;/li&gt;
&lt;li&gt;Building with event systems and microservices&lt;/li&gt;
&lt;li&gt;API development using OpenAPI, specifically with a &lt;em&gt;design-first&lt;/em&gt; approach&lt;/li&gt;
&lt;li&gt;Deployment using Kubernetes, fly.io, Render, or a Virtual Private Server running containers&lt;/li&gt;
&lt;li&gt;IaC using terraform and ansible to keep your infrastructure consistent&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-can-i-do-for-you"&gt;What Can I do for You?&lt;/h2&gt;
&lt;p&gt;Whether you&amp;rsquo;re an individual with a great idea or a business looking to improve your web presence, I can help you. I am
not only a developer but also a consultant who can provide you with solutions tailored to your unique requirements. I go
beyond just coding to understand your goals and develop customized solutions that help you achieve them.&lt;/p&gt;
&lt;h3 id="consulting"&gt;Consulting:&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ll delve deep into understanding your idea or problem, offering tech-based, innovative solutions. I can help you
understand complex concepts and tech jargon and work with you to create a web application that meets all your
requirements.&lt;/p&gt;
&lt;h3 id="building"&gt;Building:&lt;/h3&gt;
&lt;p&gt;I love challenging projects that make me think and push boundaries. Whether it&amp;rsquo;s building a web
application from scratch or redesigning an existing one to make it better, I am up for the task. I&amp;rsquo;m proficient in Go,
Python, and JavaScript, and am comfortable building with containerisation, event systems, and microservices.&lt;/p&gt;
&lt;h2 id="why-hire-me"&gt;Why Hire Me?&lt;/h2&gt;
&lt;p&gt;In a world full of generic solutions, I offer personalized service. From deployment to designing strategies for growth,
I work closely with my clients to ensure they&amp;rsquo;re not just getting a product, but a roadmap to success.&lt;/p&gt;
&lt;p&gt;I know that technology is ever-evolving, and I strive to be at the forefront of it. My passion for continuous learning
ensures your project isn&amp;rsquo;t just relevant for now, but future-ready.&lt;/p&gt;
&lt;h2 id="lets-connect"&gt;Let&amp;rsquo;s Connect!&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re looking for a dedicated, passionate developer who is great at solving problems and loves taking on challenges,
reach out to me. I&amp;rsquo;d love to hear about your idea and explore how we can turn it into a reality.&lt;/p&gt;</description></item><item><title>How to invalidate tanstack query key in same component</title><link>https://danielms.site/zet/2024/how-to-invalidate-tanstack-query-key-in-same-component/</link><pubDate>Tue, 03 Sep 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/how-to-invalidate-tanstack-query-key-in-same-component/</guid><description>&lt;h1 id="how-to-invalidate-tanstack-query-key-in-same-component"&gt;How to invalidate tanstack query key in same component&lt;/h1&gt;
&lt;p&gt;I am new to TanStack query, router, form etc.&lt;/p&gt;
&lt;p&gt;I wanted to have a table and a form in the same component (or page containing
many components).&lt;/p&gt;
&lt;p&gt;Create a thing in the form, invalidate the cache and have the table re-fetch
table data rendering the table with the new data.&lt;/p&gt;
&lt;p&gt;At the same time I am using Orval to generate all my TanStack Query (TQ from now
on) methods.&lt;/p&gt;
&lt;p&gt;It took me a while to figure this out but AFAICT, you do not invalidate a cache
from Orval&amp;rsquo;s generated spec. You instead use TQ directly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-tsx" data-lang="tsx"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Foo() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useQueryClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useTQListMethod&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mutate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useMutateTQListMethod&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onSuccess&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// this is how I invalidated the cache
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;queryKey&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/formdata&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;// matches the queryKey from useTQListMethod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pending&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;: &lt;span class="kt"&gt;React.FormEvent&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;HTMLFormElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;currentTarget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// truncated logic
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// does the mutation (sending POST data) but does not invalidate the cache!
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;myData&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;}&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;This&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nx"&gt;truncated&lt;/span&gt; &lt;span class="nx"&gt;and&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;that&lt;/span&gt; &lt;span class="nx"&gt;important&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;MyTable&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Useing &lt;code&gt;queryClient.invalidateQueries&lt;/code&gt; let me force my cache query to refetch
and successfully re-render the table (&lt;code&gt;MyTable&lt;/code&gt;) with the new entry.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#react #tanstack
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>How I write Golang CLI tools today (using Kong)</title><link>https://danielms.site/zet/2024/how-i-write-golang-cli-tools-today-using-kong/</link><pubDate>Thu, 01 Aug 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/how-i-write-golang-cli-tools-today-using-kong/</guid><description>&lt;h1 id="how-i-write-golang-cli-tools-today-using-kong"&gt;How I write Golang CLI tools today (using Kong)&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ve ditched &lt;code&gt;cobra&lt;/code&gt; for &lt;code&gt;alecthomas/kong&lt;/code&gt; for good. Smaller, easier to grok,
great interface design.&lt;/p&gt;
&lt;p&gt;This is a short snippet on how I layout a basic CLI (which I do for every
project, nearly). CLI&amp;rsquo;s power most of my app&amp;rsquo;s, including web servers.&lt;/p&gt;
&lt;p&gt;Here is the most simple layout of a useless but instructive example CLI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── app
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── internal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── cmd.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── echo.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each &lt;code&gt;*.go&lt;/code&gt; file in detail.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// cmd/app/main.go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;me/my-cli/internal/cmd&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/alecthomas/kong&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;my-cli&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DecodeContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;IsBool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;BeforeApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EchoCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`cmd:&amp;#34;&amp;#34; help:&amp;#34;Example of an Echo command&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;` help:&amp;#34;Print version information and quit&amp;#34; short:&amp;#34;v&amp;#34; name:&amp;#34;version&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;development&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLI&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Display help if no args are provided instead of an error message&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;--help&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;My new CLI&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsageOnError&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureHelp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HelpOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Compact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DefaultEnvars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vars&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FatalIfErrorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// internal/cmd/cmd.go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Output format&amp;#34; default:&amp;#34;console&amp;#34; enum:&amp;#34;console,json&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// internal/cmd/cmd.go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;encoding/json&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EchoCommand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`arg:&amp;#34;&amp;#34; help:&amp;#34;text to echo&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Formatter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OutputFunc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OutputFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;o&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;echoMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;EchoCommand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ConsoleFormatted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;OutputFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JSONFormatted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;OutputFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;echoMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;JSONFormatted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;console&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;echoMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ConsoleFormatted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once these files exist, from the root level, run &lt;code&gt;go run cmd/app/main.go&lt;/code&gt; and it
will output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Usage: my-cli &amp;lt;command&amp;gt; &lt;span class="o"&gt;[&lt;/span&gt;flags&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;My new CLI
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Flags:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -h, --help Show context-sensitive help.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --format&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;console&amp;#34;&lt;/span&gt; Output format &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$MY&lt;/span&gt;-CLI_FORMAT&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -v, --version Print version information and quit &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$MY&lt;/span&gt;-CLI_VERSION&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; Example of an Echo &lt;span class="nb"&gt;command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Run &lt;span class="s2"&gt;&amp;#34;my-cli &amp;lt;command&amp;gt; --help&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information on a command.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;echo&lt;/code&gt; command can be called with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go run cmd/app/main.go &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;this is going to be written to the console&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# this is going to print to the console&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and to print JSON:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go run cmd/app/main.go &lt;span class="nb"&gt;echo&lt;/span&gt; --format&lt;span class="o"&gt;=&lt;/span&gt;json &lt;span class="s2"&gt;&amp;#34;this is going to print out some json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# {&amp;#34;message&amp;#34;:&amp;#34;this is going to written to the console&amp;#34;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #cli #kong
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>RabbitMQ missed heartbeat BlockingConsumer</title><link>https://danielms.site/zet/2024/rabbitmq-missed-heartbeat-blockingconsumer/</link><pubDate>Thu, 25 Jul 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/rabbitmq-missed-heartbeat-blockingconsumer/</guid><description>&lt;h1 id="rabbitmq-missed-heartbeat-blockingconsumer"&gt;RabbitMQ missed heartbeat BlockingConsumer&lt;/h1&gt;
&lt;p&gt;Fix missed heartbeats from client, add the following line of code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;connection.process_data_events()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is for when using a &lt;code&gt;basic_consume&lt;/code&gt;, &lt;code&gt;start_consuming&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;ref: &lt;a href="https://anands.me/blog/pika-missed-heartbeats-rabbitmq"&gt;https://anands.me/blog/pika-missed-heartbeats-rabbitmq&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Go's peterbourgon ff CLI package snippet</title><link>https://danielms.site/zet/2024/gos-peterbourgon-ff-cli-package-snippet/</link><pubDate>Thu, 11 Jul 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/gos-peterbourgon-ff-cli-package-snippet/</guid><description>&lt;h1 id="gos-peterbourgon-ff-cli-package-snippet"&gt;Go&amp;rsquo;s peterbourgon ff CLI package snippet&lt;/h1&gt;
&lt;p&gt;This snippet will create a simple CLI using
&lt;a href="https://github.com/peterbourgon/ff"&gt;https://github.com/peterbourgon/ff&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It will read CLI arguments in the following order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;flag&lt;/li&gt;
&lt;li&gt;environment variable&lt;/li&gt;
&lt;li&gt;config file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This isn&amp;rsquo;t perfect (its a POC) but is a good starting point.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/peterbourgon/ff/v4&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/peterbourgon/ff/v4/ffhelp&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/peterbourgon/ff/v4/ffyaml&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os/signal&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;path/filepath&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/template&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pvenotify&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotifyContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Interrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kill&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;noTLSVerify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;userConfigDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UserConfigDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;could not determine user config dir: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;pveConfigDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s/%s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userConfigDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pvconfig&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;CreateDirectoryIfNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pveConfigDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;could not create pveconfig dir: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileData&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;tfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;./config.yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tfile&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generateDefaultConfigFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pveConfigDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewFlagSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;u&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;username&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;un&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;username for authentication&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;p&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pw&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password for authentication&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://localhost:8006/api2/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;PVE api host address&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BoolVarDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;v&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;verbose&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;verbose logging&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BoolVarDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noTLSVerify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;t&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;no-tls-verify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;do not verify TLS connections&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s/config.yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pveConfigDir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;location of config file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BoolVarDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;h&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;help&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;show help information&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s [OPTIONS]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Subcommands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Exec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ffhelp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Config: %+v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseAndRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvVarPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;PVE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithEnvVars&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithConfigFileFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithConfigAllowMissingFile&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ff&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithConfigFileParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ffyaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;{}.&lt;/span&gt;&lt;span class="nx"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to parse config: %v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doesNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;CreateDirectoryIfNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doesNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generateDefaultConfigFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;defaultFileName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config.yaml&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;defaultFileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doesNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the config file would look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;no-tls-verify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;username_via_config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;password_via_config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://localhost_via_config:8006/api2/json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #cli
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>minio for local s3 testing</title><link>https://danielms.site/zet/2024/minio-for-local-s3-testing/</link><pubDate>Thu, 27 Jun 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/minio-for-local-s3-testing/</guid><description>&lt;h1 id="minio-for-local-s3-testing"&gt;minio for local s3 testing&lt;/h1&gt;
&lt;p&gt;How to setup minio for use in local development.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;minio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;quay.io/minio/minio&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;minio-local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;minio_data:/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;9090:9090&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;9000:9000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_ROOT_USER&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MINIO_ROOT_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;password&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;server /data --console-address :9090&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;minio_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;{}&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;minio&lt;/code&gt; profile will point any s3 data to the &lt;code&gt;s3-specific&lt;/code&gt; service which
uses our local &lt;code&gt;minio&lt;/code&gt; container as the endpoint_url.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll also need to &lt;code&gt;aws configure --profile minio&lt;/code&gt; and enter the API keys from
&lt;code&gt;minio&lt;/code&gt;. To create them in the UI go to &lt;code&gt;Access Keys&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[profile minio]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;services = s3-specific
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[services s3-specific]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;s3 =
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; endpoint_url = http://localhost:9000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the code we have to create and load some environment variables so that it
won&amp;rsquo;t use the defaults&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AWS_PROFILE=minio
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AWS_ENDPOINT_URL_S3=http://localhost:9000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And finally in we need to create a bucket in &lt;code&gt;minio&lt;/code&gt; called &lt;code&gt;my-bucket&lt;/code&gt; or
whatever you like. I just do that in the UI.&lt;/p&gt;
&lt;h3 id="awswrangler"&gt;AWSWrangler&lt;/h3&gt;
&lt;p&gt;Once those items have been established, its possible to use the &lt;code&gt;minio&lt;/code&gt; profile
for &lt;code&gt;awswrangler&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The best way to do this without making any code changes is to use environment
variables.&lt;/p&gt;
&lt;p&gt;In addition to the variables above also add
&lt;code&gt;AWS_ENDPOINT_URL=http://localhost:9000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;All together it will look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AWS_PROFILE=minio
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AWS_ENDPOINT_URL=http://localhost:9000
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AWS_ENDPOINT_URL_S3=http://localhost:9000
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will work on most s3 projects&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#s3 #aws #development
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>golang ssh client snippet</title><link>https://danielms.site/zet/2024/golang-ssh-client-snippet/</link><pubDate>Sat, 27 Apr 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/golang-ssh-client-snippet/</guid><description>&lt;h1 id="golang-ssh-client-snippet"&gt;golang ssh client snippet&lt;/h1&gt;
&lt;p&gt;Here&amp;rsquo;s an SSH snippet I&amp;rsquo;ve used successfully across projects.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/melbahja/goph&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;golang.org/x/crypto/ssh&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AuthMethod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;HostAddress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Client is an SSH client capable of sending and receiving through a tunnel.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;goph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewClientWithPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// NewClientWithPort returns a client containing an SSHConfig struct and allows&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// for a custom port to be set on the goph.Client.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewClientWithPort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;goph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HostAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefaultTimeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Callback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InsecureIgnoreHostKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewConn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// todo(dan) need to handle dead servers otherwise 60s + timeout before EHOSTUNREACH(113)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ReturnedError&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Reason&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Detail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #ssh
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>goa.design custom zerolog logger with HTTP Info, Warn and Error codes</title><link>https://danielms.site/zet/2024/goa.design-custom-zerolog-logger-with-http-info-warn-and-error-codes/</link><pubDate>Sun, 21 Apr 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/goa.design-custom-zerolog-logger-with-http-info-warn-and-error-codes/</guid><description>&lt;h1 id="goadesign-custom-zerolog-logger-with-http-info-warn-and-error-codes"&gt;goa.design custom zerolog logger with HTTP Info, Warn and Error codes&lt;/h1&gt;
&lt;p&gt;By default the goa logger isn&amp;rsquo;t great IMO. I like zerolog and have created
a custom logger.&lt;/p&gt;
&lt;p&gt;You could also use slog or zap, basically anything as long as it has these
methods to implement the Logger interface.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/rs/zerolog&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Logger is an adapted zerologger&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDebug&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isConsole&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;InfoLevel&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isDebug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DebugLevel&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetGlobalLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;svclogger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;With&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;isConsole&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;svclogger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConsoleWriter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;With&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;svclogger&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Log is called by the log middleware to log HTTP requests key values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;FormatFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If this isn&amp;#39;t here, all HTTP requests get a zerolog.Info() block&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// which is super confusing when a request is a 5xx so I&amp;#39;ve overridden it&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;].(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Warn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Msgf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HTTP Request&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Msgf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HTTP Request&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Msgf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HTTP Request&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// FormatFields formats input keyvals&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ref: https://github.com/goadesign/goa/blob/v1/logging/logrus/adapter.go#L64&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;FormatFields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;keyvals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%v&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#zerolog #go #goa&lt;/p&gt;</description></item><item><title>Client-go; kubernetes deployment,service and ingress</title><link>https://danielms.site/zet/2024/client-go-kubernetes-deploymentservice-and-ingress/</link><pubDate>Tue, 16 Apr 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/client-go-kubernetes-deploymentservice-and-ingress/</guid><description>&lt;h1 id="client-go-kubernetes-deploymentservice-and-ingress"&gt;Client-go; kubernetes deployment,service and ingress&lt;/h1&gt;
&lt;p&gt;How to create a simple deployment exposed with an ingress in Kubernetes using
the &lt;code&gt;client-go&lt;/code&gt; SDK.&lt;/p&gt;
&lt;h2 id="preconditions"&gt;Preconditions:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;k3s&lt;/li&gt;
&lt;li&gt;traefik&lt;/li&gt;
&lt;li&gt;&lt;code&gt;echo.k3s.lcl&lt;/code&gt; mapped to local IP in &lt;code&gt;/etc/hosts&lt;/code&gt; (if demo purposes only)&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log/slog&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s.io/apimachinery/pkg/util/intstr&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;appsv1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s.io/api/apps/v1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s.io/api/core/v1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s.io/api/networking/v1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s.io/apimachinery/pkg/apis/meta/v1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s.io/client-go/kubernetes&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;k8s.io/client-go/tools/clientcmd&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;k8sClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createDeployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ealen/echo-server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo-svc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createIngress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo-ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createDeployment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kubernetes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;deploymentName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;replicas&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;dc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppsV1&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Deployments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;dp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;appsv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Deployment&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appsv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DeploymentSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LabelSelector&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;MatchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PodTemplateSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;Labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PodSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;Containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="nx"&gt;Ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ContainerPort&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;									&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;									&lt;/span&gt;&lt;span class="nx"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ProtocolTCP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;									&lt;/span&gt;&lt;span class="nx"&gt;ContainerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8888&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;deployment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;deployment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;creating&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateOptions&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;deployment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;deployment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetObjectMeta&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;created&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kubernetes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;svc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServiceSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;corev1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServicePort&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;TargetPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intstr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IntOrString&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;intstr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;IntVal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;creating&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CoreV1&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;svc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateOptions&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetObjectMeta&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;created&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createIngress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kubernetes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingressName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ingress&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ObjectMeta&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingressName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IngressSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;IngressClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;traefik&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IngressRule&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo.k3s.lcl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;IngressRuleValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IngressRuleValue&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="nx"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPIngressRuleValue&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="nx"&gt;Paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTTPIngressPath&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;									&lt;/span&gt;&lt;span class="nx"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;									&lt;/span&gt;&lt;span class="nx"&gt;PathType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PathTypePrefix&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;									&lt;/span&gt;&lt;span class="nx"&gt;Backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IngressBackend&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;										&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;IngressServiceBackend&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;											&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo-svc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;											&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;networkingv1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServiceBackendPort&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;												&lt;/span&gt;&lt;span class="nx"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;											&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;										&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;									&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingressName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;creating&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NetworkingV1&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Ingresses&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateOptions&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetObjectMeta&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;created&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;k8sClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kubernetes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Clientset&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If you are using $HOME/.kube/config uncomment this and remove the&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// os.Getenv(&amp;#34;KUBECONFIG&amp;#34;) line&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;//userHomeDir, err := os.UserHomeDir()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;//if err != nil {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;//	fmt.Printf(&amp;#34;error getting user home dir: %v\n&amp;#34;, err)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;//	os.Exit(1)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;//}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;//kubeConfigPath := filepath.Join(userHomeDir, &amp;#34;.kube&amp;#34;, &amp;#34;config&amp;#34;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;kubeConfigPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;KUBECONFIG&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Using kubeconfig: %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubeConfigPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clientcmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubeConfigPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error getting kubernetes config: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubernetes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;error getting kubernetes config: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clientset&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run with &lt;code&gt;go run .&lt;/code&gt; and if you have a connection to the cluster, it should output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Using kubeconfig: /etc/rancher/k3s/k3s.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024/04/16 22:07:07 INFO deployment &lt;span class="nv"&gt;deployment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creating
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024/04/16 22:07:07 INFO deployment &lt;span class="nv"&gt;deployment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;created
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024/04/16 22:07:07 INFO service &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;echo-svc &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creating
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024/04/16 22:07:07 INFO service &lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;echo-svc &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;created
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024/04/16 22:07:07 INFO ingress &lt;span class="nv"&gt;ingress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;echo-ingress &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;creating
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024/04/16 22:07:07 INFO ingress &lt;span class="nv"&gt;ingress&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;echo-ingress &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;created
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If your &lt;code&gt;/etc/hosts&lt;/code&gt; file has an entry that has your local IP pointing
at &lt;code&gt;echo.k3s.lcl&lt;/code&gt; then you should be able to curl the pod.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# example of what /etc/hosts should look like on your host machine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.0.1 echo.k3s.lcl 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Output from curl&amp;rsquo;ing &lt;code&gt;echo.k3s.lcl&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# curl echo.k3s.lcl | jq .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;host&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;hostname&amp;#34;: &amp;#34;echo.k3s.lcl&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ip&amp;#34;: &amp;#34;::ffff:10.42.0.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ips&amp;#34;: []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;http&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;method&amp;#34;: &amp;#34;GET&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;baseUrl&amp;#34;: &amp;#34;&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;originalUrl&amp;#34;: &amp;#34;/&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;protocol&amp;#34;: &amp;#34;http&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;request&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;params&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;0&amp;#34;: &amp;#34;/&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;query&amp;#34;: {},
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;cookies&amp;#34;: {},
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;body&amp;#34;: {},
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;headers&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;host&amp;#34;: &amp;#34;echo.k3s.lcl&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;user-agent&amp;#34;: &amp;#34;curl/7.81.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;accept&amp;#34;: &amp;#34;*/*&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;x-forwarded-for&amp;#34;: &amp;#34;10.42.0.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;x-forwarded-host&amp;#34;: &amp;#34;echo.k3s.lcl&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;x-forwarded-port&amp;#34;: &amp;#34;80&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;x-forwarded-proto&amp;#34;: &amp;#34;http&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;x-forwarded-server&amp;#34;: &amp;#34;traefik-5df5cdc88d-b9tsn&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;x-real-ip&amp;#34;: &amp;#34;10.42.0.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;accept-encoding&amp;#34;: &amp;#34;gzip&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;environment&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;PATH&amp;#34;: &amp;#34;/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;HOSTNAME&amp;#34;: &amp;#34;echo-68cd9fb7bd-zxjmd&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;NODE_VERSION&amp;#34;: &amp;#34;20.11.0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;YARN_VERSION&amp;#34;: &amp;#34;1.22.19&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ECHO_SVC_PORT_3000_TCP&amp;#34;: &amp;#34;tcp://10.43.41.1:3000&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ECHO_SVC_PORT_3000_TCP_PORT&amp;#34;: &amp;#34;3000&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_PORT&amp;#34;: &amp;#34;tcp://10.43.0.1:443&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_SERVICE_PORT_HTTPS&amp;#34;: &amp;#34;443&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_PORT_443_TCP_PROTO&amp;#34;: &amp;#34;tcp&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_PORT_443_TCP_PORT&amp;#34;: &amp;#34;443&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_PORT_443_TCP_ADDR&amp;#34;: &amp;#34;10.43.0.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ECHO_SVC_PORT_3000_TCP_PROTO&amp;#34;: &amp;#34;tcp&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_SERVICE_HOST&amp;#34;: &amp;#34;10.43.0.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ECHO_SVC_SERVICE_HOST&amp;#34;: &amp;#34;10.43.41.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ECHO_SVC_PORT_3000_TCP_ADDR&amp;#34;: &amp;#34;10.43.41.1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_PORT_443_TCP&amp;#34;: &amp;#34;tcp://10.43.0.1:443&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ECHO_SVC_SERVICE_PORT&amp;#34;: &amp;#34;3000&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;ECHO_SVC_PORT&amp;#34;: &amp;#34;tcp://10.43.41.1:3000&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;KUBERNETES_SERVICE_PORT&amp;#34;: &amp;#34;443&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;HOME&amp;#34;: &amp;#34;/root&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Updated&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For resources which are not kubernetes primitives such as Traefik, you
can use a Dynamic Client to create &lt;code&gt;unstructured.Unstructired{}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this example we&amp;rsquo;re replacing the &lt;code&gt;networking.k8s.io&lt;/code&gt; &lt;code&gt;Ingress&lt;/code&gt; with a
&lt;code&gt;traefik.io/v1alpha1&lt;/code&gt; &lt;code&gt;IngressRoute&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;k8sDynClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DynamicClient&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;kubeConfigPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;KUBECONFIG&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Using kubeconfig: %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubeConfigPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clientcmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildConfigFromFlags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kubeConfigPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error getting kubernetes config: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Everything is the same as k8sClient except this line.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;clientset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewForConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kubeConfig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;error getting kubernetes config: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clientset&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createDynamicIngressRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dynamic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DynamicClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingressName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;unstructured&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unstructured&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;apiVersion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;traefik.io/v1alpha1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;IngressRoute&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingressName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;namespace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;spec&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;entryPoints&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;web&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;routes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Host(`echo.k3s.lcl`) &amp;amp;&amp;amp; PathPrefix(`/`)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Rule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;services&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;echo-svc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;namespace&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;								&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;							&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingressName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;creating&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GroupVersionResource&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;traefik.io&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;v1alpha1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingressroutes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;Namespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ns&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ingress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;metav1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateOptions&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ingress&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;created&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#kubernetes #go&lt;/p&gt;</description></item><item><title>Golang: Time.In panic missing Location</title><link>https://danielms.site/zet/2024/golang-time.in-panic-missing-location/</link><pubDate>Mon, 08 Apr 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/golang-time.in-panic-missing-location/</guid><description>&lt;h1 id="golang-timein-panic-missing-location"&gt;Golang: Time.In panic missing Location&lt;/h1&gt;
&lt;p&gt;This worked fine in development on my machine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;isToday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Australia/Brisbane&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;localTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;timezone&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;In&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localTZ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DayMonth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Day&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Day&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DayMonth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;date match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DayMonth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But in production produced:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-04-08T08:33:35Z 2024/04/08 08:33:35 ERROR timezone &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;unknown time zone Australia/Brisbane&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2024-04-08T08:33:35Z 2024/04/08 08:33:35 http: panic serving 127.0.0.1:46164: time: missing Location in call to Time.In
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Turns out, if Go can&amp;rsquo;t retrieve timezones from the machine it&amp;rsquo;ll panic. You can force Go
to embed timezones into the application though by importing &lt;code&gt;time/tzdata&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time/tzdata&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;isToday&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Australia/Brisbane&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;localTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LoadLocation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;timezone&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;In&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;localTZ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DayMonth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Day&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Day&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DayMonth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Month&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;slog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;date match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DayMonth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now it works!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#TIL #time #go&lt;/p&gt;</description></item><item><title>Caddy and CORS</title><link>https://danielms.site/zet/2024/caddy-and-cors/</link><pubDate>Sat, 30 Mar 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/caddy-and-cors/</guid><description>&lt;h1 id="caddy-and-cors"&gt;Caddy and CORS&lt;/h1&gt;
&lt;p&gt;A quick Caddy CORS snippet&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Caddyfile" data-lang="Caddyfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;(cors)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nd"&gt;@cors_preflight&lt;/span&gt; &lt;span class="k"&gt;method&lt;/span&gt; &lt;span class="s"&gt;OPTIONS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nd"&gt;@cors&lt;/span&gt; &lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="s"&gt;Origin&lt;/span&gt; &lt;span class="se"&gt;{args.0}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;handle&lt;/span&gt; &lt;span class="nd"&gt;@cors_preflight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;{args.0}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="s"&gt;Access-Control-Allow-Methods&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GET, POST, PUT, PATCH, DELETE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="s"&gt;Access-Control-Allow-Headers&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Content-Type, Authorization&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="s"&gt;Access-Control-Max-Age&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;3600&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;respond&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;handle&lt;/span&gt; &lt;span class="nd"&gt;@cors&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="s"&gt;Access-Control-Allow-Origin&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="se"&gt;{args.0}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;header&lt;/span&gt; &lt;span class="s"&gt;Access-Control-Expose-Headers&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Link&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gh"&gt;server1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;encode&lt;/span&gt; &lt;span class="s"&gt;gzip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;tls&lt;/span&gt; &lt;span class="no"&gt;internal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;import&lt;/span&gt; cors &lt;span class="s"&gt;https://server2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;reverse_proxy&lt;/span&gt; &lt;span class="n"&gt;https://192.168.20.10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="k"&gt;transport&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;			&lt;span class="k"&gt;tls_insecure_skip_verify&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#caddy #cors #til&lt;/p&gt;</description></item><item><title>nextdns rewrites</title><link>https://danielms.site/zet/2024/nextdns-rewrites/</link><pubDate>Sat, 30 Mar 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/nextdns-rewrites/</guid><description>&lt;h1 id="nextdns-rewrites"&gt;nextdns rewrites&lt;/h1&gt;
&lt;p&gt;I was humbled today; I thought nextdns rewrites meant you could point
all queries to a DNS server.&lt;/p&gt;
&lt;p&gt;For instance, *.foo.bar -&amp;gt; 192.168.20.20 (private LAN DNS server) and that
would then pass on the request to the DNS server.&lt;/p&gt;
&lt;p&gt;It doesn&amp;rsquo;t work like this. Instead you have to map each host to the LAN host
address/IP (or external).&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s no API for this either so for my 15 internal domains I have to
enter them in the GUI.&lt;/p&gt;
&lt;p&gt;I have several profiles and its a massive PITA. Now I have one profile for
all nextdns enabled devices so that I only have to change one place.&lt;/p&gt;
&lt;p&gt;A little disappointed about this not being exposed via an API (and thus scriptable).&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#TIL #dns #nextdns&lt;/p&gt;</description></item><item><title>Home server notes</title><link>https://danielms.site/zet/2024/home-server-notes/</link><pubDate>Fri, 29 Mar 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/home-server-notes/</guid><description>&lt;h1 id="home-server-notes"&gt;Home server notes&lt;/h1&gt;
&lt;p&gt;Collecting issues that I hit along the way to bootstrapping my new proxmox homelab.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Paperless-ngx&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For a reverse proxy to work you need to override the &lt;code&gt;PAPERLESS_URL&lt;/code&gt; variable. To update this,
open &lt;code&gt;/opt/paperless/paperless.conf&lt;/code&gt; in the LXC container.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Caddy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Caddy doesn&amp;rsquo;t come with a convenient LXC container. Instead I created a ubuntu 22.04
VM and installed Caddy as a systemd unit. This makes saving the Caddyfile
hard so I recommend saving the output once &lt;em&gt;sorted&lt;/em&gt; and then configuring
it to be API driven thereafter.&lt;/p&gt;
&lt;p&gt;To make a &lt;code&gt;server&lt;/code&gt; &lt;strong&gt;not&lt;/strong&gt; request a HTTPS certificate automatically then
use &lt;code&gt;http://&lt;/code&gt; in the server name block.&lt;/p&gt;
&lt;h2 id="proxmox-networking"&gt;Proxmox Networking&lt;/h2&gt;
&lt;p&gt;Make sure IPv6 is set to Static and empty. If set to DHCP and your router doesn&amp;rsquo;t provide it
boot times increase dramatically.&lt;/p&gt;
&lt;p&gt;Set static IPv4, especially for Cloudflared LXC or &lt;code&gt;/etc/resolv.conf&lt;/code&gt; will get the gateway
instead of DNS server. The server will fail to connect to Cloudflare and &lt;code&gt;/etc/resolv.conf&lt;/code&gt;
will always be overwritten by DHCP.&lt;/p&gt;
&lt;p&gt;Also make sure all services/VMs have a static IP so when the server reboots it doesn&amp;rsquo;t give
new IPs rendering the proxy useless. Do this in the GUI and not in the service as it&amp;rsquo;ll be
overwritten by cloud-init.&lt;/p&gt;
&lt;p&gt;Tags&lt;/p&gt;
&lt;p&gt;#proxmox #homelab #lxc&lt;/p&gt;</description></item><item><title>Goa openapi embedded openapi documentation</title><link>https://danielms.site/zet/2024/goa-openapi-embedded-openapi-documentation/</link><pubDate>Tue, 26 Mar 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/goa-openapi-embedded-openapi-documentation/</guid><description>&lt;h1 id="goa-openapi-embedded-openapi-documentation"&gt;Goa openapi embedded openapi documentation&lt;/h1&gt;
&lt;p&gt;A snippet for how I am storing an OpenAPI documentation page within a Goa
project docker image.&lt;/p&gt;
&lt;h3 id="designopenapigo"&gt;&lt;code&gt;design/openapi.go&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// design/openapi.go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;design&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;goa.design/goa/v3/dsl&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;openapi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;OpenAPI endpoints for debugging and demonstration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/openapi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Length is the downloaded content length in bytes.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;encoding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;encoding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;invalid_file_path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrorResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Could not locate file for download&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;internal_error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrorResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Fault while processing download.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/openapi3.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;SkipResponseBodyEncodeDecode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;length:Content-Length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;encoding:Content-Type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;invalid_file_path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;internal_error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;documentation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Int64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Length is the downloaded content length in bytes.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;encoding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;encoding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;invalid_file_path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrorResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Could not locate file for download&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;internal_error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrorResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Fault while processing download.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/docs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;SkipResponseBodyEncodeDecode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;length:Content-Length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nf"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;encoding:Content-Type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;invalid_file_path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;internal_error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="openapigo"&gt;&lt;code&gt;openapi.go&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;example&lt;/code&gt; generated from the above DSL&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;goagithub&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/danielmichaels/goa-github/gen/openapi&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;io&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;//go:embed gen/http/openapi3.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapijson&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;//go:embed assets/static/docs.html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// openapi service example implementation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// The example methods log the requests and return zero values.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapisrvc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// NewOpenapi returns the openapi service implementation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewOpenapi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;openapisrvc&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapisrvc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FileResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadCloser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapijson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;gen/http/openapi3.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeInvalidFilePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeInternalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FileResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapisrvc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Documentation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocumentationResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadCloser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;assets/static/docs.html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeInvalidFilePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MakeInternalError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DocumentationResult&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Size&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="assetsstaticdocshtml"&gt;&lt;code&gt;assets/static/docs.html&lt;/code&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;en&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;UTF-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Open API&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://cdn.jsdelivr.net/npm/swagger-ui@5.10.0/dist/swagger-ui.css&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sha256-IgNmYaATqY6z6AuD6hvz9XN0OyeAc94gsTa+lK8ka1Y=&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c"&gt;/* Fast dark mode https://github.com/swagger-api/swagger-ui/issues/5327 */&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;prefers-color-scheme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;dark&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;#1f1f1f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;swagger-ui&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;hue-rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="kt"&gt;deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;swagger-ui&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;microlight&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;invert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;hue-rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="kt"&gt;deg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;style&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;swagger-ui&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://cdn.jsdelivr.net/npm/swagger-ui@5.10.0/dist/swagger-ui-bundle.js&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sha256-i050FsZ0MSwm3mVMv7IhpfCdK90RKaXPS/EmiWxv8vc=&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://cdn.jsdelivr.net/npm/swagger-ui@5.10.0/dist/swagger-ui-standalone-preset.js&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;integrity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;sha256-IGoJVXW7MRyeZOsKceWVePAShfVpJhnYhDhOQp+Yi38=&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;crossorigin&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;anonymous&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Initialize Swagger UI
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;SwaggerUIBundle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/openapi/openapi3.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Provide the URL to your OpenAPI spec file
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;dom_id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;#swagger-ui&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;deepLinking&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;presets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;SwaggerUIBundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;SwaggerUIStandalonePreset&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;BaseLayout&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;html&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#goa #dsl #go&lt;/p&gt;</description></item><item><title>GH Actions versus GitlabCI</title><link>https://danielms.site/zet/2024/gh-actions-versus-gitlabci/</link><pubDate>Sun, 24 Mar 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/gh-actions-versus-gitlabci/</guid><description>&lt;h1 id="gh-actions-versus-gitlabci"&gt;GH Actions versus GitlabCI&lt;/h1&gt;
&lt;p&gt;I am convinced that people persist with GH actions because its simply
available on Github.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s objectively worse in everyway than GitlabCI. I&amp;rsquo;ve used Drone and Woodpecker
which are also ten times the product that GH Actions is.&lt;/p&gt;
&lt;p&gt;Can&amp;rsquo;t use custom container images! What a PITA.&lt;/p&gt;
&lt;p&gt;I make heavy use of &amp;ldquo;god&amp;rdquo; images for CI. These are images that have everything you
need for all your CI activities. Think terraform, packer, taskfile, python, golang and so on.&lt;/p&gt;
&lt;p&gt;It creates a huge image but it also means I only have to pull the image and then everything is
available.&lt;/p&gt;
&lt;p&gt;In Github, I have to run a step and then install that stuff (go and python, are available
though). This means my pipelines are spent installing things like &lt;code&gt;golines&lt;/code&gt;, &lt;code&gt;betteralign&lt;/code&gt; and
&lt;code&gt;go-task&lt;/code&gt; - for every single invocation.&lt;/p&gt;
&lt;p&gt;In Gitlab I pull the image once, its cached on the runner and boom, it fires up the pipeline
and have access to all those things immediately.&lt;/p&gt;
&lt;p&gt;Gitlab is better, as are any other providers who &lt;strong&gt;don&amp;rsquo;t&lt;/strong&gt; work the same as Github.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#rant #ci #github&lt;/p&gt;</description></item><item><title>Low effort issues and tickets, a cultural problem</title><link>https://danielms.site/zet/2024/low-effort-issues-and-tickets-a-cultural-problem/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/low-effort-issues-and-tickets-a-cultural-problem/</guid><description>&lt;h1 id="low-effort-issues-and-tickets-a-cultural-problem"&gt;Low effort issues and tickets, a cultural problem&lt;/h1&gt;
&lt;p&gt;Teams who lack any form a control by seniors seem to develop members who
offer low effort and poor quality issues, tickets and PR&amp;rsquo;s - especially
in young males.&lt;/p&gt;
&lt;p&gt;I try to always set a high standard - which is actually just &lt;strong&gt;the standard&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Issues have at a minimum &lt;em&gt;what&lt;/em&gt;, &lt;em&gt;why&lt;/em&gt;, &lt;em&gt;when&lt;/em&gt; and &lt;em&gt;how&lt;/em&gt;. It will always
have an end state.&lt;/p&gt;
&lt;p&gt;Pull requests will always have at least:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What this is doing or why this exists, linking to tickets if available&lt;/li&gt;
&lt;li&gt;How it can be run/tested&lt;/li&gt;
&lt;li&gt;If required, diagrams/mermaid.js and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I always act as though every ticket and PR could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reviewed by someone unfamiliar with the code (new team member, for instance)&lt;/li&gt;
&lt;li&gt;Used as reference in the future, rationale or searching for when bugs where introduced&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, in several teams, I come across a culture of laziness where these &amp;ldquo;non-code&amp;rdquo;
tasks are taken for granted.&lt;/p&gt;
&lt;p&gt;Now I refuse to review, or pick up tickets unless they have more details. I&amp;rsquo;ll just
comment (nicely) that more information is needed and when that is provided I&amp;rsquo;ll move forward
with this task.&lt;/p&gt;
&lt;p&gt;This works for me because I am typically afforded that level of leniency within the
roles I chose.&lt;/p&gt;
&lt;p&gt;But, for juniors, they don&amp;rsquo;t or can&amp;rsquo;t and will start to emulate poor performers who
don&amp;rsquo;t exhibit &lt;em&gt;the standard&lt;/em&gt; expected of all developers. Couple this with a team where
no one is given guidelines or expectations and its a recipe for disaster.&lt;/p&gt;
&lt;p&gt;A team culture is everything and it must be set by the seniors.&lt;/p&gt;
&lt;p&gt;A couple military gem&amp;rsquo;s which are corny but ultimately true when it comes to standards
and leadership:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s called leadership not like-a-ship&lt;/li&gt;
&lt;li&gt;The standard you walk past is the standard you accept&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ll do better to encourage people into making an effort with these small but
significant pieces of the software development teamwork puzzle. I hope you all
do too.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#leadership #teams&lt;/p&gt;</description></item><item><title>MITM proxying for request inspection</title><link>https://danielms.site/zet/2024/mitm-proxying-for-request-inspection/</link><pubDate>Mon, 04 Mar 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/mitm-proxying-for-request-inspection/</guid><description>&lt;h1 id="mitm-proxying-for-request-inspection"&gt;MITM proxying for request inspection&lt;/h1&gt;
&lt;p&gt;I was listening to an old &lt;a href="https://runninginproduction.com"&gt;Running in Production&lt;/a&gt;
podcast
&lt;a href="https://runninginproduction.com/podcast/98-games-directory-lets-you-sync-your-games-and-achievements-in-1-place"&gt;episode&lt;/a&gt;
in which the developer reversed engineered API&amp;rsquo;s using Fiddler.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d used Fiddler briefly and found it cumbersome, along with &lt;code&gt;mitmproxy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So on a whim I searched and found two competing but excellent alternatives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://requestly.com"&gt;Requestly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://httptoolkit.com"&gt;HTTP-Toolkit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both offer very comparable features for my use case. For simple use its
splitting hairs and likely comes to down to the number of methods they
support and their ergonomics.&lt;/p&gt;
&lt;p&gt;For instance, Requestly is better to look at in my opinion but HTTP-Toolkit
is no frills and just works.&lt;/p&gt;
&lt;p&gt;Listening to the podcast was eye opening because I&amp;rsquo;d been working on a problem
in my day job which required request inspection.
Wireshark was too heavy handed and all the HTTP traffic was encrypted plus
I can&amp;rsquo;t filter easily by only trapping certain processes, e.g. my current terminal.&lt;/p&gt;
&lt;p&gt;With a MITM proxy I was instantly able to see every request unencrypted. Within
seconds I could see that requests that we thought we going out weren&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;If you need to track network requests unencrypted from a process you control be it
a browser or terminal, you should be using a MITM proxy to intercept it.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#TIL #mitm&lt;/p&gt;</description></item><item><title>Golang: title casing first character only</title><link>https://danielms.site/zet/2024/golang-title-casing-first-character-only/</link><pubDate>Sat, 17 Feb 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/golang-title-casing-first-character-only/</guid><description>&lt;h1 id="golang-title-casing-first-character-only"&gt;Golang: title casing first character only&lt;/h1&gt;
&lt;p&gt;Technically I think title case means all major words have their first letter
capitalised.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t that; its capitalising the &lt;strong&gt;first work only&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I use this when rendering error fields to users. I like to keep all my
errors consistent and lower cased. This means some extra work when presenting
errors to users, for instance form field errors.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;strings&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WithTitleCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUpper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;sentance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;today was a good day&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;newSentance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;WithTitleCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sentance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newSentance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;go play: &lt;a href="https://go.dev/play/p/r2auIbxsiRH?v="&gt;https://go.dev/play/p/r2auIbxsiRH?v=&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#go&lt;/p&gt;</description></item><item><title>AsyncClient: python quick example</title><link>https://danielms.site/zet/2024/asyncclient-python-quick-example/</link><pubDate>Fri, 26 Jan 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/asyncclient-python-quick-example/</guid><description>&lt;h1 id="asyncclient-python-quick-example"&gt;AsyncClient: python quick example&lt;/h1&gt;
&lt;p&gt;I have started a new job in another python shop.&lt;/p&gt;
&lt;p&gt;Over the last 6-12 months, I wrote a lot more Go and yaml than python as my primary role
in my last job was kubernetes and infra management. So I am reacquainting myself
in python.&lt;/p&gt;
&lt;p&gt;This is a quick async snippet for future reference, stolen mostly from Rednafi&amp;rsquo;s
&lt;a href="https://rednafi.com/misc/eschewing_black_box_api_calls/"&gt;blog post&lt;/a&gt; and changed to
suit my needs.&lt;/p&gt;
&lt;p&gt;This code will fetch multiple &lt;code&gt;product_id&lt;/code&gt;&amp;rsquo;s from an API using asyncio and httpx.
Pydantic is used to marshall things nicely.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;httpx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pydantic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;asyncio&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://dummyjson.com/products/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fetch_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2: iPhone X, SIM-Free, Model A19211 6.5-inch Super Retina HD display with OLED technology A12 Bionic chip with ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4: OPPOF19, OPPO F19 is officially announced on April 2021.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3: Samsung Universe 9, Samsung&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;s new variant which goes beyond Galaxy to the Universe
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6: MacBook Pro, MacBook Pro &lt;span class="m"&gt;2021&lt;/span&gt; with mini-LED display may launch between September, November
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;9: Infinix INBOOK, Infinix Inbook X1 Ci3 10th 8GB 256GB &lt;span class="m"&gt;14&lt;/span&gt; Win10 Grey – &lt;span class="m"&gt;1&lt;/span&gt; Year Warranty
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1: iPhone 9, An apple mobile which is nothing like apple
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;8: Microsoft Surface Laptop 4, Style and speed. Stand out on HD video calls backed by Studio Mics. Capture ideas on the vibrant touchscreen.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;7: Samsung Galaxy Book, Samsung Galaxy Book S &lt;span class="o"&gt;(&lt;/span&gt;2020&lt;span class="o"&gt;)&lt;/span&gt; Laptop With Intel Lakefield Chip, 8GB of RAM Launched
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5: Huawei P30, Huawei’s re-badged P30 Pro New Edition was officially unveiled yesterday in Germany and now the device has made its way to the UK.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#python #async #httpx&lt;/p&gt;</description></item><item><title>HTMX and Echo; rendering HTML</title><link>https://danielms.site/zet/2024/htmx-and-echo-rendering-html/</link><pubDate>Thu, 18 Jan 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/htmx-and-echo-rendering-html/</guid><description>&lt;h1 id="htmx-and-echo-rendering-html"&gt;HTMX and Echo; rendering HTML&lt;/h1&gt;
&lt;p&gt;I am experimenting with replacing a Next.js frontend with HTMX (and Templ).
Typically, I use &lt;code&gt;chi&lt;/code&gt; as my router but this time I&amp;rsquo;ve opted for &lt;code&gt;echo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For one reason, by default it uses context and all handlers must return an error.
This prevents any missed returns when error handling (which can be caught with
linters but its an extra step).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m used to &lt;code&gt;html/template&lt;/code&gt; but getting it to work with &lt;code&gt;echo&lt;/code&gt; was a different.&lt;/p&gt;
&lt;p&gt;Following the &lt;a href="https://echo.labstack.com/docs/templates"&gt;guide&lt;/a&gt; and adding my own
spin I came up with the following.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;templates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// assets/view/ contains all my templates&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;view/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Funcs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ParseFS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EmbeddedFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;patterns&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;template.New err&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;echo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Recover&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// this is how Echo learns about our templates.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// I can pass in multiple locations for templates but I only care about&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the HTMX fragments for this renderer - everything else is handled by Templ&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Renderer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewTemplate&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fragments/*.tmpl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// all routes are loaded here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;srv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;:%d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;IdleTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeoutIdle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ReadTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeoutRead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;WriteTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeoutWrite&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Msgf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Started Webserver on &amp;#39;%d&amp;#39;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;srv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I can render HTML (with HTMX) using Echo.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#til #go #echo #templates&lt;/p&gt;</description></item><item><title>qemu kvm: operation not permitted fix</title><link>https://danielms.site/zet/2024/qemu-kvm-operation-not-permitted-fix/</link><pubDate>Thu, 18 Jan 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/qemu-kvm-operation-not-permitted-fix/</guid><description>&lt;h1 id="qemu-kvm-operation-not-permitted-fix"&gt;qemu kvm: operation not permitted fix&lt;/h1&gt;
&lt;p&gt;When starting a bridged VM and hit with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error starting domain: internal error: /usr/libexec/qemu-bridge-helper --use-vnet --br=virbr0 --fd=20: failed to communicate with bridge helper: Transport endpoint is not connected
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stderr=failed to create tun device: Operation not permitted
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is likely that &lt;code&gt;qemu&lt;/code&gt; has been updated and changed the bridge helper permissions.&lt;/p&gt;
&lt;p&gt;Fix it with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo chmod u+s /usr/lib/qemu/qemu-bridge-helper&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#qemu #vm #kvm&lt;/p&gt;</description></item><item><title>GoLand and templ with autocomplete</title><link>https://danielms.site/zet/2024/goland-and-templ-with-autocomplete/</link><pubDate>Tue, 16 Jan 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/goland-and-templ-with-autocomplete/</guid><description>&lt;h1 id="goland-and-templ-with-autocomplete"&gt;GoLand and templ with autocomplete&lt;/h1&gt;
&lt;p&gt;To use &lt;code&gt;templ&lt;/code&gt; with autocomplete via its
&lt;a href="https://github.com/templ-go/templ-jetbrains"&gt;Plugin&lt;/a&gt; you must do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Go to settings&amp;gt;templ and enter the path to the &lt;code&gt;templ&lt;/code&gt; binary (e.g. &lt;code&gt;$HOME/.local/bin/templ&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Close all GoLand instances&lt;/li&gt;
&lt;li&gt;Launch GoLand via the terminal inside the project directory (e.g. &lt;code&gt;goland .&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It works but still needs some refining at the project level.&lt;/p&gt;
&lt;p&gt;The alternative is to use &lt;code&gt;neovim&lt;/code&gt; for the &lt;code&gt;templ&lt;/code&gt; files or VSCode. As
a GoLand subscriber these are sub par alternatives but may be required in the
short term.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#go #templ&lt;/p&gt;</description></item><item><title>Design First API Development with Goa</title><link>https://danielms.site/blog/design-first-api-development-with-goa/</link><pubDate>Tue, 09 Jan 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/design-first-api-development-with-goa/</guid><description>&lt;p&gt;&lt;a href="https://goa.design"&gt;Goa.design&lt;/a&gt; is a golang tool for developing APIs using a design first approach. By leveraging Goa it is
possible to generate server and client code automatically, documentation through OpenAPI (version 2 and 3 are supported)
as well as gRPC code.&lt;/p&gt;
&lt;p&gt;This blog post introduces Goa, it&amp;rsquo;s concepts and showcases some short examples. It does not walk through the
installation, or intend to supersede its tutorial/walkthrough which you should look over,
&lt;a href="https://goa.design/learn/getting-started/"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Goa is broken into three parts; the design language (DSL), code generation and the Go package itself.&lt;/p&gt;
&lt;h2 id="what-is-a-dsl"&gt;What is a DSL?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A DSL (Domain-Specific Language) is a programming language or a set of rules and syntax specifically designed to solve problems within a particular domain or industry. It provides a higher-level abstraction that allows programmers to express solutions in a more concise and declarative manner.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The above statement sums it up succinctly. In our case the &lt;em&gt;domain&lt;/em&gt; or &lt;em&gt;industry&lt;/em&gt; specifics relate to HTTP and RPC communication
via network calls. A lot of API work is boilerplate and can be hard to implement all the things considered &lt;em&gt;best practise&lt;/em&gt;.
Goa strives to reduce the amount of programmer work required to build out well crafted APIs.&lt;/p&gt;
&lt;p&gt;Instead of writing out an OpenAPI document and then converting it to Go code such as &lt;a href="https://github.com/deepmap/oapi-codegen"&gt;oapi-codegen&lt;/a&gt;,
Goa uses its DSL to generate it (and the code).&lt;/p&gt;
&lt;p&gt;Personally, I find the DSL quite powerful and easy to understand. What&amp;rsquo;s more, it starts simple but provides rich ways
to extend it as your requirements dictate. The creator (&lt;a href="https://github.com/raphael"&gt;raphael&lt;/a&gt;) is also very active on github and golang&amp;rsquo;s slack.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a snippet of the DSL.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;design&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;goa.design/goa/v3/dsl&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// API describes the global properties of the API server.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;API&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;check-redirects&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HTTP Redirection Detection Service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HTTP service detecting and reporting any and all redirects in a HTTP request&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://localhost:9090&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;endpoints for determining service uptime and status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;healthz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/healthz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nf"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AppVersion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AppVersion&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Application version information&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nf"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Application version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;6b51bebe0f965a5fffa8ff9db5aa702c76ec47f2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Without going too deep right now, this will create an API called &lt;em&gt;server&lt;/em&gt;, a service called &lt;em&gt;health&lt;/em&gt; and within the &lt;em&gt;health&lt;/em&gt;
service build two endpoints; &lt;code&gt;healthz&lt;/code&gt; and &lt;code&gt;version&lt;/code&gt;. A custom type called &lt;code&gt;version&lt;/code&gt; will also be created and used in the
&lt;code&gt;/version&lt;/code&gt; endpoint.&lt;/p&gt;
&lt;p&gt;The DSL provides an abstraction which lets you craft your API in a declarative way. One big benefit is its just Go code
meaning you can easily extend, simplify or create generic functions when working with it.&lt;/p&gt;
&lt;h2 id="code-generation"&gt;Code generation&lt;/h2&gt;
&lt;p&gt;Writing the DSL by itself does not generate code or documentation. The &lt;code&gt;goa&lt;/code&gt; CLI does that. Specifically, &lt;code&gt;goa gen&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After &lt;a href="https://goa.design/learn/getting-started/"&gt;installing&lt;/a&gt; Goa, you can generate the client and server code.&lt;/p&gt;
&lt;p&gt;To generate the code run &lt;code&gt;goa gen&lt;/code&gt;. Typically, Goa suggests a pattern of placing all DSL files into
a directory called &lt;code&gt;design&lt;/code&gt;. You don&amp;rsquo;t have to do this, but I think it makes sense for most use cases.&lt;/p&gt;
&lt;p&gt;For the above code snippet, if it were at this path &lt;code&gt;design/design.go&lt;/code&gt; you would run
&lt;code&gt;goa gen github.com/danielmichaels/checkredirects/design&lt;/code&gt;. In this example &lt;code&gt;github.com/danielmichaels/checkredirects&lt;/code&gt; is
my module that I used during &lt;code&gt;go mod init&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="example-generation"&gt;Example generation&lt;/h4&gt;
&lt;p&gt;With this directory structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── design
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── design.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After I run &lt;code&gt;goa gen github.com/danielmichaels/checkredirects/design&lt;/code&gt;, it will create a directory called &lt;code&gt;gen&lt;/code&gt; with the following files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── design
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── design.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── gen &lt;span class="c1"&gt;# New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── health
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── client.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── endpoints.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── service.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── http
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── cli.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── health
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── client
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   │   ├── client.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   │   ├── cli.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   │   ├── encode_decode.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   │   ├── paths.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   │   └── types.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── encode_decode.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── paths.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── server.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── types.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── openapi3.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── openapi3.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── openapi.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── openapi.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we have our Go code which we can import into our services. Note, this is all auto generated and &lt;strong&gt;should not&lt;/strong&gt; be edited as
it&amp;rsquo;ll be overwritten whenever we run &lt;code&gt;goa gen&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="creating-the-services"&gt;Creating the services&lt;/h4&gt;
&lt;p&gt;Now that the package code has been generated we can create the entrypoint, and service files automatically with another command;
&lt;code&gt;goa example&lt;/code&gt;. Running this results in some new files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── cmd &lt;span class="c1"&gt;# New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── http.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── server-cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── http.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── design
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── design.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── gen &lt;span class="c1"&gt;# Truncated for brevity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── health.go &lt;span class="c1"&gt;# New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unlike &lt;code&gt;goa gen&lt;/code&gt; this is a one-shot deal; if the generated files already exist it will not re-create them. This is because all your business logic
will be inside these files and it may override things you don&amp;rsquo;t want overridden.&lt;/p&gt;
&lt;p&gt;If we peek at &lt;code&gt;health.go&lt;/code&gt; it will have stubbed out all the handlers, ready to be populated which your business logic.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;checkredirects&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;context&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;health&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/danielmichaels/checkredirects/gen/health&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// health service example implementation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// The example methods log the requests and return zero values.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;healthsrvc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// NewHealth returns the health service implementation.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewHealth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;healthsrvc&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Healthz implements healthz.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;healthsrvc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Healthz&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;health.healthz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Version implements version.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;healthsrvc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Version2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;health&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Version2&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;health.version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So after writing only 40 lines (&lt;code&gt;design/design.go&lt;/code&gt;) we were able to auto generate a complete and working web server with
two endpoints.&lt;/p&gt;
&lt;p&gt;The responses as expected do not return anything but work as demonstrated below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curlie :9090/healthz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;200&lt;/span&gt; OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Date: Tue, &lt;span class="m"&gt;09&lt;/span&gt; Jan &lt;span class="m"&gt;2024&lt;/span&gt; 04:44:21 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curlie :9090/version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;200&lt;/span&gt; OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Type: application/json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Date: Tue, &lt;span class="m"&gt;09&lt;/span&gt; Jan &lt;span class="m"&gt;2024&lt;/span&gt; 04:44:28 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="documentation"&gt;Documentation&lt;/h2&gt;
&lt;p&gt;Another great feature of Goa is how well it documents the code and that it can generate valid OpenAPI documents.&lt;/p&gt;
&lt;p&gt;This is the document it created:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;openapi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.0.3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;info&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;HTTP Redirection Detection Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;HTTP service detecting and reporting any and all redirects in a HTTP request&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.0.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;servers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;http://localhost:9090&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/healthz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;health&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;healthz health&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;health#healthz&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;200&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OK response.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;/version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;health&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;version health&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;operationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;health#version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;200&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OK response.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;application/json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;$ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#/components/schemas/Version&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;6b51bebe0f965a5fffa8ff9db5aa702c76ec47f2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Application version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;6b51bebe0f965a5fffa8ff9db5aa702c76ec47f2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;example&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;6b51bebe0f965a5fffa8ff9db5aa702c76ec47f2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;health&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;endpoints for determining service uptime and status&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Goa also provides powerful constructs to enhance the documents. For example to define a field on a &lt;code&gt;schema&lt;/code&gt; we can use
&lt;code&gt;Attribute&lt;/code&gt; or &lt;code&gt;Field&lt;/code&gt; type. For this post we&amp;rsquo;re only focusing on HTTP which uses &lt;code&gt;Attribute&lt;/code&gt; whereas &lt;code&gt;Field&lt;/code&gt; is for both
HTTP and gRPC.&lt;/p&gt;
&lt;p&gt;Example of a Goa &lt;code&gt;Payload&lt;/code&gt; and how we can add more context to it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Truncated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;Payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;username&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Username&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Example&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;MyUsername&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Pattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;^user_[a-zA-Z0-9]{12}$&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;password&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Truncated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will create an OpenAPI document which provides an example of &lt;code&gt;MyUsername&lt;/code&gt;. The &lt;code&gt;Pattern&lt;/code&gt; will also enforce the regex
and automatically handle payload validation without the need to write any logic.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;This post sought to introduce Goa in the most simple of terms. It hardly scratches the surface of its capabilities. I chose
the most simplistic example I could because it will lay the groundwork for some follow-up posts which show how to add more
realistic endpoints. In the next post I will create a service which accepts and returns JSON leveraging Goa&amp;rsquo;s &lt;code&gt;Type&lt;/code&gt;, &lt;code&gt;Error&lt;/code&gt;
and &lt;code&gt;Payload&lt;/code&gt; DSL primitives. This will be published with source code.&lt;/p&gt;</description></item><item><title>Daniel Michaels</title><link>https://danielms.site/about/</link><pubDate>Fri, 05 Jan 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/about/</guid><description>&lt;p&gt;Hey I’m Dan. I am now a software developer but in a past life was a soldier and
served nearly a decade in the special forces. This is my outlet away from the
day to day of working at a big corporation as a developer.&lt;/p&gt;
&lt;p&gt;I mostly work with Python, Go and Javascript. These days I&amp;rsquo;ll reach for Go before
anything else.&lt;/p&gt;
&lt;p&gt;Most days, I can be found either trying to launch a profitable online business
or hanging out with my family and friends. I love ice hockey (go bruins!), hunting and
in general trying to be a little bit better than yesterday.&lt;/p&gt;</description></item><item><title>Why I love gjson</title><link>https://danielms.site/zet/2024/why-i-love-gjson/</link><pubDate>Thu, 04 Jan 2024 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2024/why-i-love-gjson/</guid><description>&lt;h1 id="why-i-love-gjson"&gt;Why I love gjson&lt;/h1&gt;
&lt;p&gt;Easy parsing. That&amp;rsquo;s it. That&amp;rsquo;s the reason.&lt;/p&gt;
&lt;p&gt;I have these structs&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApiResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;message&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;status&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;code&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;return&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SystemDNSResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pfsense&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DNS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;data&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ApiResponse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When I make a request to the client I&amp;rsquo;ll get a response that looks something like
this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ok&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;return&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Success&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;dnsserver&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;1.1.1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;8.8.8.8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;dnsallowoverride&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;dnslocalhost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I can marshall this into that struct easily using the built in &lt;code&gt;encoding/json&lt;/code&gt; package.&lt;/p&gt;
&lt;p&gt;But, I want a generic methods which can be passed in a struct and do the unmarshalling further
down the line.&lt;/p&gt;
&lt;p&gt;This is where &lt;code&gt;gjson&lt;/code&gt; really shines; it can inspect &lt;code&gt;[]byte&lt;/code&gt; and output the string which I can
then do something with. I don&amp;rsquo;t need any struct just the field name I want.&lt;/p&gt;
&lt;p&gt;For example, I care about the &lt;code&gt;code&lt;/code&gt; field. And have functions which will return errors based on its
value:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// checkRawJsonStatusCode ensures that any non-200 status codes return an error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// from the firewall. The response from the firewall is not a http/net object&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// so, we must manually inspect the code int and derive their meaning here.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pfsensesrvc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;checkRawJsonStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;299&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed client validation of pfsense api: %d&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This function takes a string which would require unmarshalling to extract.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;gjson&lt;/code&gt; I can pass in the &lt;code&gt;[]byte&lt;/code&gt; and grab it out easily, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// apiResponseCode returns the pfSense-api &amp;#39;code&amp;#39; value from a successful API&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// call to the client API.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;apiResponseCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gjson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// MarshallAPIResponse parses weakly typed responses from the client device and marshall&amp;#39;s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// it into a struct.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pfsensesrvc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;MarshallAPIResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// truncated the rest of this method&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkRawJsonStatusCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;apiResponseCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see that I grab the &lt;code&gt;code&lt;/code&gt; field as an &lt;code&gt;int&lt;/code&gt; which is passed in as an argument to
&lt;code&gt;checkRawJsonStatusCode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I do it this way because in &lt;code&gt;MarshallAPIResponse&lt;/code&gt; I only have to pass in &lt;code&gt;[]byte&lt;/code&gt; whereas
if I used &lt;code&gt;encoding/json&lt;/code&gt; I would need to be more explicit about the type.&lt;/p&gt;
&lt;p&gt;Personally, I&amp;rsquo;ve found it to be invaluable, especially when creating an API which has many tens
of endpoints, each returning their own types.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#json #go #gjson&lt;/p&gt;</description></item><item><title>Linux captive portal - ubuntu</title><link>https://danielms.site/zet/2023/linux-captive-portal---ubuntu/</link><pubDate>Sun, 31 Dec 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/linux-captive-portal---ubuntu/</guid><description>&lt;h1 id="linux-captive-portal---ubuntu"&gt;Linux captive portal - ubuntu&lt;/h1&gt;
&lt;p&gt;Whenever I fly I &lt;em&gt;always&lt;/em&gt; have captive portal issues with my Linux laptop.
The initial connection pops a captive portal but if I close the laptop and
reopen it, it&amp;rsquo;ll kill the connection.&lt;/p&gt;
&lt;p&gt;So far this has managed to get a reconnection.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo dhclient
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then navigate to &lt;code&gt;https://captive.apple.com&lt;/code&gt; or &lt;code&gt;neverssl.com&lt;/code&gt; so that
it can re-open the captive portal.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#qantas #captive-portal&lt;/p&gt;</description></item><item><title>New year, new challenges</title><link>https://danielms.site/zet/2023/new-year-new-challenges/</link><pubDate>Tue, 26 Dec 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/new-year-new-challenges/</guid><description>&lt;h1 id="new-year-new-challenges"&gt;New year, new challenges&lt;/h1&gt;
&lt;p&gt;Starting off 2024 by embracing the unknown.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re moving interstate, just bought our first home, having a baby and as of
last week, now unemployed!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s interesting times in the household with a lot of exciting and daunting changes ahead.
We can&amp;rsquo;t wait to move into our new house - it&amp;rsquo;s a nice house up the mountain. Lots of space
for the kiddo&amp;rsquo;s, a pool and surrounded by nature.&lt;/p&gt;
&lt;p&gt;The lack of job is a curveball though it &lt;em&gt;should&lt;/em&gt; only be temporary (a contract error)
but nonetheless is a blemish on the whole experience.&lt;/p&gt;
&lt;p&gt;My family and friends think we&amp;rsquo;re crazy for buying a house whilst technically unemployed and moving and
expecting another child. But, you gotta risk it for the biscuit!&lt;/p&gt;
&lt;p&gt;Mind you, if anyone reading this needs some short term Go or python work - hit me up!&lt;/p&gt;
&lt;p&gt;Otherwise, bring on 2024 as I can&amp;rsquo;t wait to move up to the Sunshine state (again).&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#life&lt;/p&gt;</description></item><item><title>Laravel DX is incredible</title><link>https://danielms.site/zet/2023/laravel-dx-is-incredible/</link><pubDate>Sun, 17 Dec 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/laravel-dx-is-incredible/</guid><description>&lt;h1 id="laravel-dx-is-incredible"&gt;Laravel DX is incredible&lt;/h1&gt;
&lt;p&gt;Today, on a whim, I ran through the laravel tutorial.&lt;/p&gt;
&lt;p&gt;I know zero PHP so I didn&amp;rsquo;t understand some of the syntax but the developer experience
is absolutely first rate.&lt;/p&gt;
&lt;p&gt;If I were to choose between Django and Laravel I would definitely reach for Laravel
first. I didn&amp;rsquo;t even get into all the add-ons yet and I&amp;rsquo;ll already blown away.&lt;/p&gt;
&lt;p&gt;The PHP itself didn&amp;rsquo;t seem so bad; people bemoan it hard but on the surface
it seems pretty decent.&lt;/p&gt;
&lt;p&gt;Might knock up a little app in it over the holidays and see how the full dev-&amp;gt;deployment
cycle goes.&lt;/p&gt;
&lt;p&gt;One thing that blew me away is &lt;a href="https://laravel.com/docs/10.x/sail"&gt;sail&lt;/a&gt;. It lets you
run laravel and all of its ecosystem from within docker. I initially tried setting up PHP
but hit various roadblocks before I found sail. I removed all PHP components and installed
sail (which is really just a bash script and docker container). After that all of laravel&amp;rsquo;s
CLI tools where magically embedded into sail. I don&amp;rsquo;t know another framework/language which
does this or does it &lt;em&gt;this&lt;/em&gt; well.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#php #laravel&lt;/p&gt;</description></item><item><title>goa (goland) API design is better than oapi-codegen</title><link>https://danielms.site/zet/2023/goa-goland-api-design-is-better-than-oapi-codegen/</link><pubDate>Mon, 04 Dec 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/goa-goland-api-design-is-better-than-oapi-codegen/</guid><description>&lt;h1 id="goa-goland-api-design-is-better-than-oapi-codegen"&gt;goa (goland) API design is better than oapi-codegen&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m a weekend in switching from &lt;code&gt;deepmap/oapi-codegen&lt;/code&gt; to &amp;lt;goa.design&amp;gt; and couldn&amp;rsquo;t
be happier.&lt;/p&gt;
&lt;p&gt;So far it ticks the boxes the &lt;code&gt;oapi-codegen&lt;/code&gt; provided but is much simpler to implement.&lt;/p&gt;
&lt;p&gt;It uses a DSL instead of writing the &lt;code&gt;openapi.yaml&lt;/code&gt; file. This DSL generates &lt;code&gt;openapi&lt;/code&gt;
version 2 and 3. It also generates &lt;code&gt;gRPC&lt;/code&gt; protobuf files. I find the DSL quite idiomatic
to use and beautiful to look at - unlike yaml.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;goa&lt;/code&gt; so far has been much smoother and more expressive. Not to mention it
forces better architecture and abstractions from the get go. I typically use a
&amp;ldquo;god&amp;rdquo; struct for my application. Instead &lt;code&gt;goa&lt;/code&gt; uses a service orientated approach
except it generates 80% of it for you. It&amp;rsquo;s the opinionated yet open framework Go
lacks.&lt;/p&gt;
&lt;p&gt;So far really happy. Early days but will be recommending it to everyone.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#go #goa #api&lt;/p&gt;</description></item><item><title>Looping over embedded structs in html template</title><link>https://danielms.site/zet/2023/looping-over-embedded-structs-in-html-template/</link><pubDate>Tue, 28 Nov 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/looping-over-embedded-structs-in-html-template/</guid><description>&lt;h1 id="looping-over-embedded-structs-in-html-template"&gt;Looping over embedded structs in html template&lt;/h1&gt;
&lt;p&gt;I am using the &lt;code&gt;slack-go&lt;/code&gt; package and needed to pull out information from slacks Conversations
API.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;slack-go&lt;/code&gt; uses the following structs to store this data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Channel contains information about the channel&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Channel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;GroupConversation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;IsChannel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;is_channel&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;IsGeneral&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;is_general&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;IsMember&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;is_member&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Locale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;locale&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// GroupConversation is the foundation for Group and Channel&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupConversation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Conversation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;name&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Creator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;creator&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;IsArchived&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;is_archived&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Members&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;members&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Topic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Topic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;topic&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Purpose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Purpose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;purpose&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Getting the conversation is easy;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConversationsContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetConversationsParameters&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;TeamID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And it returns &lt;code&gt;[]slack.Channel&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In a html template I wanted to get all the available channels and put them into a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt;.
It took a long time before I realised that &lt;code&gt;GroupConversation&lt;/code&gt; is an embedded struct and thus
you cannot reference it by a struct value.&lt;/p&gt;
&lt;p&gt;To get the &lt;code&gt;GroupConversation.Name&lt;/code&gt; from a slice of &lt;code&gt;Channel&lt;/code&gt; in &lt;code&gt;.tmpl&lt;/code&gt; I used the following loop:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go-template" data-lang="go-template"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt;&amp;lt;select class=&amp;#34;select select-bordered w-full max-w-xs&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt; &amp;lt;option disabled selected&amp;gt;Slack Channel&amp;lt;/option&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Channels&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt; &amp;lt;option&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="na"&gt;.Name&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;/option&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="x"&gt; &amp;lt;/select&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;{{ range . }}&lt;/code&gt; seems a little off putting to me; magical even. But, it works. I&amp;rsquo;m hoping
there is a better way but this works quite well for now.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#TIL #go&lt;/p&gt;</description></item><item><title>air.toml interrupts and kill_delay</title><link>https://danielms.site/zet/2023/air.toml-interrupts-and-kill_delay/</link><pubDate>Mon, 27 Nov 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/air.toml-interrupts-and-kill_delay/</guid><description>&lt;h1 id="airtoml-interrupts-and-kill_delay"&gt;air.toml interrupts and kill_delay&lt;/h1&gt;
&lt;p&gt;TIL that you &lt;strong&gt;can&lt;/strong&gt; make &lt;a href="https://github.com/cosmtrek/air"&gt;air&lt;/a&gt; send system
interrupt to the process when it reloads and have a waiting period.&lt;/p&gt;
&lt;p&gt;This is really important for long running or async tasks which need to complete.
e.g. sending a notification then writing to the DB.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# full air.toml for completeness.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tmp_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tmp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;bin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./tmp/app serve&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;go build -o ./tmp/app ./cmd/app&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;exclude_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;node_modules&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tmp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vendor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;exclude_file&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;exclude_regex&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;exclude_unchanged&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;follow_symlink&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;full_bin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include_dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;include_ext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;go&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tpl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tmpl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;kill_delay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2s&amp;#34;&lt;/span&gt; &lt;span class="c"&gt;# here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;send_interrupt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c"&gt;# here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;stop_on_error&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;log&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;build-errors.log&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;yellow&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;magenta&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;green&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;watcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cyan&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;time&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;misc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;clean_on_exit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#TIL&lt;/p&gt;</description></item><item><title>Users don't read the manual</title><link>https://danielms.site/zet/2023/users-dont-read-the-manual/</link><pubDate>Thu, 09 Nov 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/users-dont-read-the-manual/</guid><description>&lt;h1 id="users-dont-read-the-manual"&gt;Users don&amp;rsquo;t read the manual&lt;/h1&gt;
&lt;p&gt;In my workplace I&amp;rsquo;ve written a couple of tools. Now its more than just me
committing code to the project but I still do 90% of the ongoing work.&lt;/p&gt;
&lt;p&gt;It bootstraps a local kubernetes cluster with all our teams resources
and applications. We&amp;rsquo;re a dev team, not platform or infra. Most of them
don&amp;rsquo;t know kubernetes which is why I created this tool.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a &lt;code&gt;pip install&lt;/code&gt; and within 3 minutes you&amp;rsquo;re entire dev env is stood up.&lt;/p&gt;
&lt;p&gt;Yet, I get constant questions. Some of the answers are literally printed
to the screen once the &lt;code&gt;bootstrap&lt;/code&gt; step completes.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard not to get frustrated. On one hand, you can&amp;rsquo;t expect users to
understand everything. On the other, there&amp;rsquo;s team members who do read
the notices and take the time to investigate the tools &lt;code&gt;--help&lt;/code&gt; before
throwing their hands up saying &amp;ldquo;it doesn&amp;rsquo;t work&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;All I can do is try to educate and keep working on it to make it better.&lt;/p&gt;
&lt;p&gt;Still, its a real epidemic in the open source world. I see so many issues
on GitHub which are borderline aggressive towards project maintainers when
their problem was literally solved in the &lt;code&gt;--help&lt;/code&gt; text.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#oss #people&lt;/p&gt;</description></item><item><title>Forking and contributing to a Go project</title><link>https://danielms.site/zet/2023/forking-and-contributing-to-a-go-project/</link><pubDate>Mon, 06 Nov 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/forking-and-contributing-to-a-go-project/</guid><description>&lt;h1 id="forking-and-contributing-to-a-go-project"&gt;Forking and contributing to a Go project&lt;/h1&gt;
&lt;p&gt;I am using &lt;a href="http://github.com/slack-go/slack"&gt;http://github.com/slack-go/slack&lt;/a&gt; for a project and it does not support
&lt;em&gt;Sign in with Slack&lt;/em&gt; OpenID connect.&lt;/p&gt;
&lt;p&gt;After writing the connector manually I decided I&amp;rsquo;d try contributing upstream.&lt;/p&gt;
&lt;p&gt;After forking and making the changes I thought would work I needed to test them against my
working application and didn&amp;rsquo;t know how to use my fork instead of the actual package.&lt;/p&gt;
&lt;p&gt;Thankfully, it&amp;rsquo;s pretty easy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Go" data-lang="Go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;edit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/slack-go/slack=github.com/danielmichaels/slack@openid-connect&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Doing this and a &lt;code&gt;go mod tidy&lt;/code&gt; pulled in my branch and a minute later I&amp;rsquo;d proven my
changes worked.&lt;/p&gt;
&lt;p&gt;Hopefully it gets merged sometime. &lt;a href="https://github.com/slack-go/slack/pull/1242"&gt;pr&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Edit: My contribution got merged!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#oss #til #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Code AI teaches with examples</title><link>https://danielms.site/zet/2023/code-ai-teaches-with-examples/</link><pubDate>Sun, 29 Oct 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/code-ai-teaches-with-examples/</guid><description>&lt;h1 id="code-ai-teaches-with-examples"&gt;Code AI teaches with examples&lt;/h1&gt;
&lt;p&gt;I use AI probably at least once a day. I use it a lot to help with &lt;code&gt;kubectl --output&lt;/code&gt;, &lt;code&gt;jq&lt;/code&gt; and
shell scripts.&lt;/p&gt;
&lt;p&gt;But, where it really shines, at least for me, is teaching me through examples.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a snippet which I really like. I was thinking about a better way to initialise
a bunch of NATS subscriptions. AI gave me a good solution that was different to what I
was thinking. And much better; I was considering a &lt;code&gt;map&lt;/code&gt; of funcs&amp;hellip;&lt;/p&gt;
&lt;p&gt;My ever growing list of subscriptions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Nats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;InitSubscribers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;inboundMailReceived&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;processInboundMail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eventUserAssignedToEmail&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actionPostMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eventChannelMessage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What I asked JetBrains AI: &lt;code&gt;Refactor this code for me&lt;/code&gt;. Here&amp;rsquo;s its output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Nats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;InitSubscribers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inboundMailReceived&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processInboundMail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventUserAssignedToEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionPostMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventChannelMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// add more function calls here if necessary&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;functions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I love this because I had a problem. Thought through it myself and then asked AI.
It showed me a way that I wasn&amp;rsquo;t initially considering, and I prefer it to my idea.&lt;/p&gt;
&lt;p&gt;Now I have a good mental model for approaching this problem in the future. It&amp;rsquo;s
like my own little peer reviewer or rubber ducky.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#til #jetbrains #ai&lt;/p&gt;</description></item><item><title>kagi.com is superior to google</title><link>https://danielms.site/zet/2023/kagi.com-is-superior-to-google/</link><pubDate>Sat, 14 Oct 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/kagi.com-is-superior-to-google/</guid><description>&lt;h1 id="kagicom-is-superior-to-google"&gt;kagi.com is superior to google&lt;/h1&gt;
&lt;p&gt;I am on the Kagi free plan and sparingly using my 100 free queries whenever I feel like
google is failing me.&lt;/p&gt;
&lt;p&gt;So far, its been 100% effective. The most recent failed google search:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;cheap or free databases&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Within the first fold are listicles, reddit posts and here&amp;rsquo;s the kicker,
relevant search results!&lt;/p&gt;
&lt;p&gt;Given the simplistic question you&amp;rsquo;d think google would nail this. Instead it
contained both obvious paid adverts (scyallaDB - how is that cheap or free!!)
and those dumb &amp;ldquo;comparison&amp;rdquo; sites which are just SEO traps; g2.com for instance.&lt;/p&gt;
&lt;p&gt;Kagi is probably worth paying for and if it keeps smoking google like this, I&amp;rsquo;ll
pay for it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard to prove it but google searches &lt;strong&gt;are&lt;/strong&gt; getting worse over time.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#google #kagi&lt;/p&gt;</description></item><item><title>Go's Functional Options pattern is great</title><link>https://danielms.site/zet/2023/gos-functional-options-pattern-is-great/</link><pubDate>Mon, 02 Oct 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/gos-functional-options-pattern-is-great/</guid><description>&lt;h1 id="gos-functional-options-pattern-is-great"&gt;Go&amp;rsquo;s Functional Options pattern is great&lt;/h1&gt;
&lt;p&gt;Some consider this a spicy take but I think it is a fantastic and idiomatic way to build functions/methods which are backwards compatible.&lt;/p&gt;
&lt;p&gt;Consider this code from &lt;code&gt;go-gitlab&lt;/code&gt;. By using the &lt;code&gt;...ClientOptionFunc&lt;/code&gt; it is possible to add new options without breaking compatibility.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// NewClient returns a new GitLab API client. To use API methods which require&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// authentication, provide a valid private or personal token.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ref: https://github.com/xanzy/go-gitlab/blob/2692fa8f0c4e16c36af8ebdc571da3a0d4ce2d19/gitlab.go#L240-L250&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;ClientOptionFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;PrivateToken&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here is how I&amp;rsquo;ve extended it with my own custom HTTP client in a project I am working on.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewGitlab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;insecure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClientOptionFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Gitlab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;tr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;TLSClientConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;tls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;InsecureSkipVerify&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;hc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithBaseURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithHTTPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hc&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;Gitlab&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this pattern in one caller I can pass in options which others shouldn&amp;rsquo;t have.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// I do not want the default retries so I disable it.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;glab&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGitlab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ClientToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GitLabURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;insecure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GitlabClientDefaultTimeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// a const from internal&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithoutRetries&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In every other instance I do not pass in the &lt;code&gt;gitlab.WithoutRetries&lt;/code&gt; because the defaults are what I want.&lt;/p&gt;
&lt;p&gt;A simple example but to me it makes life a lot easier and I&amp;rsquo;ve started adopting it more in my own work.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A good reference:&lt;/em&gt; &lt;a href="https://michalzalecki.com/golang-options-pattern/"&gt;https://michalzalecki.com/golang-options-pattern/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #code
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Sourcegraph is awesome</title><link>https://danielms.site/zet/2023/sourcegraph-is-awesome/</link><pubDate>Mon, 02 Oct 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/sourcegraph-is-awesome/</guid><description>&lt;h1 id="sourcegraph-is-awesome"&gt;Sourcegraph is awesome&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ve yet to find a way to control my snippets. I tried &lt;code&gt;snips.sh&lt;/code&gt; but found it a little too
finicky. It is a cool project but sometimes I need to capture more context than just a snippet. Like how does &lt;em&gt;x&lt;/em&gt; fit into &lt;em&gt;y&lt;/em&gt;
and it might be several lines long or be across modules (Go specific here).&lt;/p&gt;
&lt;p&gt;Sourcegraph lets me do that. I can just add my project and then search for a term and voila. It&amp;rsquo;s possible to navigate forward and backward
through interfaces, methods and even commit history. It can do much more but that covers what I wanted so far. What I also like is that it
runs on my Caprover instance and I can now login to it from anywhere. This is a cool feature as sometimes I&amp;rsquo;ll be dreaming up some idea
and need to check something. Now I just login and search around from any computer, or phone.&lt;/p&gt;
&lt;p&gt;Well worth checking out. Now I just need to convince my team we need it because searching around on GitLab when you have multiple microservices
and you can&amp;rsquo;t remember which one does &lt;em&gt;xyz&lt;/em&gt; is an absolute PITA.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#sourcegraph #code
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>http status code mistakes: 303 versus 307</title><link>https://danielms.site/zet/2023/http-status-code-mistakes-303-versus-307/</link><pubDate>Tue, 19 Sep 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/http-status-code-mistakes-303-versus-307/</guid><description>&lt;h1 id="http-status-code-mistakes-303-versus-307"&gt;http status code mistakes: 303 versus 307&lt;/h1&gt;
&lt;p&gt;I just wasted a lot of time trying to debug an issue with my template driven web app.&lt;/p&gt;
&lt;p&gt;When redirected after making a database change to another page, it kept deleting one of the fields and setting
it back to the &lt;code&gt;NOT NULL DEFAULT 0&lt;/code&gt; as defined in my table schema.&lt;/p&gt;
&lt;p&gt;I was questioning my understanding of SQL and the &lt;a href="https://en.wikipedia.org/wiki/Post/Redirect/Get"&gt;Post/Redirect/Get&lt;/a&gt;
pattern.&lt;/p&gt;
&lt;p&gt;Well it turns out I have fat fingered a tab complete and instead of &lt;code&gt;http.StatusSeeOther&lt;/code&gt; I had inadvertently used
&lt;code&gt;http.StatusTemporaryRedirect&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Quote from &lt;a href="https://datatracker.ietf.org/doc/html/rfc7231#section-6.4.7"&gt;rfc7231&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;The &lt;span class="m"&gt;307&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;Temporary Redirect&lt;span class="o"&gt;)&lt;/span&gt; status code indicates that the target
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;resource resides temporarily under a different URI and the user agent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MUST NOT change the request method &lt;span class="k"&gt;if&lt;/span&gt; it performs an automatic
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;redirection to that URI. Since the redirection can change over time,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;the client ought to &lt;span class="k"&gt;continue&lt;/span&gt; using the original effective request URI
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; future requests.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The upper cased &lt;em&gt;MUST NOT&lt;/em&gt; precending &lt;em&gt;change the request method&lt;/em&gt; is a strong signal about what was happening to my requests.&lt;/p&gt;
&lt;p&gt;TIL: 303 and 307 are very different and I should pay more attention to the redirect status code when redirecting.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#http #redirects #til
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Flush linux DNS cache</title><link>https://danielms.site/zet/2023/flush-linux-dns-cache/</link><pubDate>Wed, 13 Sep 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/flush-linux-dns-cache/</guid><description>&lt;h1 id="flush-linux-dns-cache"&gt;Flush linux DNS cache&lt;/h1&gt;
&lt;p&gt;TIL how to flush my local DNS cache in Ubuntu.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# check if cache is populated
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;resolvectl statistics
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# flush cache
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;resolvectl flush-caches
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# confirm, cache should say 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;resolvectl statistics
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I was doing some local testing with cloud providers, I switched from one VPS provider to another but kept the same subdomain.
This was causing curl and Go.http errors. Flusing the cache fixed it.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #dns #curl
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>oapi-codegen: chi router groups and middleware</title><link>https://danielms.site/zet/2023/oapi-codegen-chi-router-groups-and-middleware/</link><pubDate>Tue, 08 Aug 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/oapi-codegen-chi-router-groups-and-middleware/</guid><description>&lt;h1 id="oapi-codegen-chi-router-groups-and-middleware"&gt;oapi-codegen: chi router groups and middleware&lt;/h1&gt;
&lt;p&gt;Today I learned a valuable lession in assumptions.&lt;/p&gt;
&lt;p&gt;I assumed that putting the &lt;code&gt;oapivalidator&lt;/code&gt; with the &lt;code&gt;router.Group&lt;/code&gt; where the endoints &lt;em&gt;live&lt;/em&gt; was
how it worked. Not realising that &lt;strong&gt;zero&lt;/strong&gt; validation was being done. Not until I started implemented
JWT authentication and noticing that no errors were propagating.&lt;/p&gt;
&lt;p&gt;Here is the code that works.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// middlewares&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oapimiddleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OapiRequestValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;openapiSpec&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// endpoints&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFromMuxWithBaseURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/api/v1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #til #aar
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>VHS: your CLI home video recorder</title><link>https://danielms.site/zet/2023/vhs-your-cli-home-video-recorder/</link><pubDate>Sat, 05 Aug 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/vhs-your-cli-home-video-recorder/</guid><description>&lt;h1 id="vhs-your-cli-home-video-recorder"&gt;VHS: your CLI home video recorder&lt;/h1&gt;
&lt;p&gt;I just removed my &amp;lsquo;peek&amp;rsquo; recorded &lt;code&gt;demo.gif&lt;/code&gt; from my &lt;code&gt;gpt&lt;/code&gt; wrapper CLI tool&amp;rsquo;s &lt;code&gt;README.md&lt;/code&gt; and replaced it
with a &lt;code&gt;vhs&lt;/code&gt; tape.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s so much better looking but can seriously prevent doxing information. Sometimes when recording something you
can accidently include identifying information. Not with &lt;code&gt;vhs&lt;/code&gt;, it records in its own custom terminal. Yet still
honours your terminals bindings.&lt;/p&gt;
&lt;p&gt;See the demo: &lt;a href="https://github.com/danielmichaels/gpt"&gt;https://github.com/danielmichaels/gpt&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#charmbracelet #vhs #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>charmbracelet gum is awesome</title><link>https://danielms.site/zet/2023/charmbracelet-gum-is-awesome/</link><pubDate>Thu, 03 Aug 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/charmbracelet-gum-is-awesome/</guid><description>&lt;h1 id="charmbracelet-gum-is-awesome"&gt;charmbracelet gum is awesome&lt;/h1&gt;
&lt;p&gt;By adding &lt;code&gt;gum confirm &amp;quot;are you sure?&amp;quot;&lt;/code&gt; I removed so much
&lt;code&gt;read -p&lt;/code&gt; code and case statements.&lt;/p&gt;
&lt;p&gt;Highly recommend&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#gum #go #bash&lt;/p&gt;</description></item><item><title>Go generic: non-ptr to ptr</title><link>https://danielms.site/zet/2023/go-generic-non-ptr-to-ptr/</link><pubDate>Thu, 03 Aug 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/go-generic-non-ptr-to-ptr/</guid><description>&lt;h1 id="go-generic-non-ptr-to-ptr"&gt;Go generic: non-ptr to ptr&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m using &lt;code&gt;oapi-codegen&lt;/code&gt; and any &lt;code&gt;required&lt;/code&gt; fields are set as pointers. This is great
for doing &lt;code&gt;nil&lt;/code&gt; checks. Initialising structs was more difficult because you cannot
create a struct with &lt;code&gt;*string&lt;/code&gt; fields like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;123&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This fails because its not a pointer. Usually I&amp;rsquo;d create a variable like &lt;code&gt;var s *string&lt;/code&gt; and then
populate the struct using &lt;code&gt;s&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then I found this great use of generics and am now using it everywhere.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Ptr takes in non-pointer and returns a pointer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// in use &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// a snippet from my tests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;200: OK&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/api/v1/devices/device_000000000000&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;want&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DeviceResponseJSON&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DeviceResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ApiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;not provided&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CreatedBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;guest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeviceActivated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;device_000000000000&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;test_device&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DeviceVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;2.7&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HostAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;192.168.0.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HttpPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SshPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int32&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TeamId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Ptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;team_000000000000&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just made writing tests so much easier.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#TIL #generics #golang
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Remote and voltage; a fix</title><link>https://danielms.site/zet/2023/remote-and-voltage-a-fix/</link><pubDate>Mon, 31 Jul 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/remote-and-voltage-a-fix/</guid><description>&lt;h1 id="remote-and-voltage-a-fix"&gt;Remote and voltage; a fix&lt;/h1&gt;
&lt;p&gt;Happy that I fixed a suddenly &amp;ldquo;broken&amp;rdquo; TV remote. The TV was reporting that
the batteries had gone flat, so I replaced them. Still didn&amp;rsquo;t work and said it&amp;rsquo;s
flat.&lt;/p&gt;
&lt;p&gt;Got out the multimeter to test old and new.&lt;/p&gt;
&lt;p&gt;Old: 1.4~V
New: 1.6V&lt;/p&gt;
&lt;p&gt;So I put an old and new together in series and tested, came to ~3.0v&lt;/p&gt;
&lt;p&gt;Put them in the remote and it works perfectly.&lt;/p&gt;
&lt;p&gt;Now I know you&amp;rsquo;re not meant to mix batteries but considering how sensitive this remote is, I don&amp;rsquo;t care!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#debugging #electronics
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Custom zerolog logger</title><link>https://danielms.site/zet/2023/custom-zerolog-logger/</link><pubDate>Sun, 30 Jul 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/custom-zerolog-logger/</guid><description>&lt;h1 id="custom-zerolog-logger"&gt;Custom zerolog logger&lt;/h1&gt;
&lt;p&gt;Zerolog is my favourite logger for Go.&lt;/p&gt;
&lt;p&gt;Chi comes with &lt;code&gt;httplog&lt;/code&gt; which creates a very simple yet powerful
&lt;code&gt;http&lt;/code&gt; logger. For non-http apps which need logging I&amp;rsquo;ve copy pasted
my own logger below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/rs/zerolog&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/rs/zerolog/log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;strings&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DefaultOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;With&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;DefaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Concise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DefaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DefaultOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DefaultOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;info&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;LevelFieldName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;level&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Concise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;SkipHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RFC3339Nano&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Configure will set new global/default options for the httplog and behaviour&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// of underlying zerolog pkg and its global logger.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;info&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LevelFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LevelFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;level&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldFormat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldFormat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RFC3339Nano&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;timestamp&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Pre-downcase all SkipHeaders&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SkipHeaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SkipHeaders&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;DefaultOptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Config the zerolog global logger&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;httplog: error! %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetGlobalLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logLevel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LevelFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LevelFieldName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimestampFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldFormat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldFormat&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConsoleWriter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Out&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TimeFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldFormat&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// LogLevel defines the minimum level of severity that app should log.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;	// Must be one of: [&amp;#34;trace&amp;#34;, &amp;#34;debug&amp;#34;, &amp;#34;info&amp;#34;, &amp;#34;warn&amp;#34;, &amp;#34;error&amp;#34;, &amp;#34;critical&amp;#34;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// LevelFieldName sets the field name for the log level or severity.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Some providers parse and search for different field names.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;LevelFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// JSON enables structured logging output in json. Make sure to enable this&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// in production mode so log aggregators can receive data in parsable format.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;	// In local development mode, its appropriate to set this value to false to&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// receive pretty output and stacktraces to stdout.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Concise mode includes fewer log details during the request flow. For example&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// excluding details like request content length, user-agent and other details.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// This is useful if during development your console is too noisy.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Concise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Tags are additional fields included at the root level of all logs.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// These can be useful for example the commit hash of a build, or an environment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// name like prod/stg/dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// SkipHeaders are additional headers which are redacted from the logs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;SkipHeaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// TimeFieldFormat defines the time format of the Time field, defaulting to &amp;#34;time.RFC3339Nano&amp;#34; see options at:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// https://pkg.go.dev/time#pkg-constants&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldFormat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// TimeFieldName sets the field name for the time field.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Some providers parse and search for different field names.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TimeFieldName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This needs more tweaking but serves as a good starting point.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #logging
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Instant Motivation</title><link>https://danielms.site/zet/2023/instant-motivation/</link><pubDate>Sat, 29 Jul 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/instant-motivation/</guid><description>&lt;h1 id="instant-motivation"&gt;Instant Motivation&lt;/h1&gt;
&lt;p&gt;Sat down to do some coding and was really struggling. Motivation was low, mood felt off
and defeatist.&lt;/p&gt;
&lt;p&gt;Got off my arse and jumped on the assault bike for twenty.&lt;/p&gt;
&lt;p&gt;What a motivator; did I want to do it? No but I knew it would be what got be out of
the func I was in.&lt;/p&gt;
&lt;p&gt;Just workout people, its easy!&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#gym #motivation #mindset
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>zalando API guidelines</title><link>https://danielms.site/zet/2023/zalando-api-guidelines/</link><pubDate>Fri, 21 Jul 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/zalando-api-guidelines/</guid><description>&lt;h1 id="zalando-api-guidelines"&gt;zalando API guidelines&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://opensource.zalando.com/restful-api-guidelines/"&gt;https://opensource.zalando.com/restful-api-guidelines/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Looks like a decent guide for writing API&amp;rsquo;s&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#api 
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>golang map references</title><link>https://danielms.site/zet/2023/golang-map-references/</link><pubDate>Tue, 27 Jun 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/golang-map-references/</guid><description>&lt;h1 id="golang-map-references"&gt;golang map references&lt;/h1&gt;
&lt;p&gt;The CS naming for this type of structure escapes me. But for now, I&amp;rsquo;ll call it &amp;ldquo;dot notation&amp;rdquo;.
I use this all the time in typescript (and in python).&lt;/p&gt;
&lt;p&gt;I wanted a way to have const&amp;rsquo;s in Go but structured so that I could call them elsewhere
in the following manner; &lt;code&gt;Foo[Bar]&lt;/code&gt;. This is much cleaner than &lt;code&gt;Foo[&amp;quot;bar&amp;quot;]&lt;/code&gt; as the compiler
prevents typo&amp;rsquo;s such as &lt;code&gt;Foo[&amp;quot;baz&amp;quot;]&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MapKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Key1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MapKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Key2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MapKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Key3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MapKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;baz&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MyMap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MapKey&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Key1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Key2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Key3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;baz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MyMap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Key1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s a little repetitious but works well for me.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>ngroks newer cousin</title><link>https://danielms.site/zet/2023/ngroks-newer-cousin/</link><pubDate>Fri, 23 Jun 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/ngroks-newer-cousin/</guid><description>&lt;h1 id="ngroks-newer-cousin"&gt;ngroks newer cousin&lt;/h1&gt;
&lt;p&gt;I just stumbled upon &lt;code&gt;jprq&lt;/code&gt; a Ngrok alternative.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/azimjohn/jprq"&gt;https://github.com/azimjohn/jprq&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To use it you need to authenticate using your Github account. This has the added side affect
of using your github username as the &lt;code&gt;jprq.live&lt;/code&gt; subdomain.&lt;/p&gt;
&lt;p&gt;For instance, mine is &lt;code&gt;danielmichaels.jprq.live&lt;/code&gt;. This is much more user friendly than Nrgoks
randomly generated one.&lt;/p&gt;
&lt;p&gt;Its written in Go as well.&lt;/p&gt;
&lt;p&gt;Running it with &lt;code&gt;--debug&lt;/code&gt; exposes a nice web UI as well. The only thing it lacks which I miss
when compared to Ngrok is the ability to replay requests.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ngrok #tunnels #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Bad Request v Unprocessible Entity</title><link>https://danielms.site/zet/2023/bad-request-v-unprocessible-entity/</link><pubDate>Thu, 01 Jun 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/bad-request-v-unprocessible-entity/</guid><description>&lt;h1 id="bad-request-v-unprocessible-entity"&gt;Bad Request v Unprocessible Entity&lt;/h1&gt;
&lt;p&gt;I think this Stack Overflow answer sums it up perfectly.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Case study: GitHub API
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;https://docs.github.com/en/rest/overview/resources-in-the-rest-api#client-errors
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Maybe copying from well known APIs is a wise idea:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;There are three possible types of client errors on API calls that receive request bodies:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sending invalid JSON will result in a &lt;span class="m"&gt;400&lt;/span&gt; Bad Request response:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;400&lt;/span&gt; Bad Request
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;35&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;#34;Problems parsing JSON&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sending the wrong &lt;span class="nb"&gt;type&lt;/span&gt; of JSON values will result in a &lt;span class="m"&gt;400&lt;/span&gt; Bad Request response:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;400&lt;/span&gt; Bad Request
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;40&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;:&lt;span class="s2"&gt;&amp;#34;Body should be a JSON object&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Sending invalid fields will result in a &lt;span class="m"&gt;422&lt;/span&gt; Unprocessable Entity response:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;422&lt;/span&gt; Unprocessable Entity
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;149&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;Validation Failed&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;errors&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;resource&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;Issue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;missing_field&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#http #error
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>sqlite JSON and Go</title><link>https://danielms.site/zet/2023/sqlite-json-and-go/</link><pubDate>Sun, 28 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/sqlite-json-and-go/</guid><description>&lt;h1 id="sqlite-json-and-go"&gt;sqlite JSON and Go&lt;/h1&gt;</description></item><item><title>snips.sh and zet</title><link>https://danielms.site/zet/2023/snips.sh-and-zet/</link><pubDate>Fri, 26 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/snips.sh-and-zet/</guid><description>&lt;h1 id="snipssh-and-zet"&gt;snips.sh and zet&lt;/h1&gt;
&lt;p&gt;I like to capture snippets but have found that incorporating
well formatted, context aware and runnable examples in this zet
challenging.&lt;/p&gt;
&lt;p&gt;Today I found &lt;a href="https://github.com/robherley/snips.sh"&gt;snips.sh&lt;/a&gt; and
its really awesome. I&amp;rsquo;m going to try it out as a way to enrich my zets.&lt;/p&gt;
&lt;p&gt;Here is an example: &lt;a href="https://snips.infra.ptco.rocks/f/32pTsJRvI_"&gt;https://snips.infra.ptco.rocks/f/32pTsJRvI_&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a real file from a project I have which uses NATS as its message service.&lt;/p&gt;
&lt;p&gt;To create that snippet all I needed to do was &lt;code&gt;cat &amp;lt;file&amp;gt;.go | ssh snips&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This then saved my snippet to my &lt;code&gt;snips.sh&lt;/code&gt; instance.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;snips.sh&lt;/code&gt; takes input from stdin I think it&amp;rsquo;ll make a great partner
to this type of notes capture.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#snippets #go #ssh
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>oapi-codegen with Gin and Echo</title><link>https://danielms.site/zet/2023/oapi-codegen-with-gin-and-echo/</link><pubDate>Thu, 25 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/oapi-codegen-with-gin-and-echo/</guid><description>&lt;h1 id="oapi-codegen-with-gin-and-echo"&gt;oapi-codegen with Gin and Echo&lt;/h1&gt;
&lt;p&gt;Don&amp;rsquo;t work with anything other than the default base URL.&lt;/p&gt;
&lt;p&gt;For example,&lt;/p&gt;
&lt;p&gt;I want everything served at &lt;code&gt;/api&lt;/code&gt; to be routed through the &lt;code&gt;oapi-codegen&lt;/code&gt; generated
&lt;code&gt;ServerInteface&lt;/code&gt;. Anything else is &lt;strong&gt;not&lt;/strong&gt; routed via &lt;code&gt;oapi-codegen&lt;/code&gt; routes.&lt;/p&gt;
&lt;p&gt;Doing this allows me to have an API backend and run a traditional web application.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;go-chi&lt;/code&gt; this is achievable and works really well.&lt;/p&gt;
&lt;p&gt;In both &lt;code&gt;gin&lt;/code&gt; and &lt;code&gt;echo&lt;/code&gt; this is not possible. There are issues for &lt;code&gt;echo&lt;/code&gt; in &lt;code&gt;oapi-codegen&lt;/code&gt;&amp;rsquo;s
github repo. The gin examples and google searches proved fruitless.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t waste time on this. Sure I have to write a lot more to get &lt;code&gt;chi&lt;/code&gt; working but it is
pretty simple and highly extensible.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#echo #gin #oapi-codegen #golang
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>API project design</title><link>https://danielms.site/zet/2023/api-project-design/</link><pubDate>Fri, 19 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/api-project-design/</guid><description>&lt;h1 id="api-project-design"&gt;API project design&lt;/h1&gt;
&lt;p&gt;A rough outline of how I&amp;rsquo;ll implement an API for a project I&amp;rsquo;m developing
using &lt;code&gt;oapi-codegen&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Endpoints:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;/healthz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;/users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;PATCH&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;/monitors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;/monitors/{monitor-type}:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;PATCH&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;/monitors/{monitor-type}/{monitor:id}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;GET&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;POST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;PATCH&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The barebones are there but implementation needs some work.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#planning #openapi #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Docker layers and ARG</title><link>https://danielms.site/zet/2023/docker-layers-and-arg/</link><pubDate>Fri, 19 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/docker-layers-and-arg/</guid><description>&lt;h1 id="docker-layers-and-arg"&gt;Docker layers and ARG&lt;/h1&gt;
&lt;p&gt;I spent a lot of today debugging a PICNIC* error related to Docker&amp;rsquo;s
multi-stage builds.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PICNIC: Problem In Chair, Not In Computer&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here is the example&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# heavily truncated&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;CERTIFICATES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certificates
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="nv"&gt;$CERTIFICATES&lt;/span&gt;/* /usr/local/share/ca-certificates/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This example (in a more full examle at least) does not fail. It&amp;rsquo;ll
copy. Except its going to copy &lt;code&gt;/*&lt;/code&gt; into &lt;code&gt;/usr/local/share/ca-certificates/&lt;/code&gt;
because &lt;code&gt;$CERTIFICATES&lt;/code&gt; does not exist in the &lt;code&gt;builder&lt;/code&gt; stage.&lt;/p&gt;
&lt;p&gt;I spent a lot of time trying to figure out why my &lt;code&gt;certificates/&lt;/code&gt; directory
was getting the certs &lt;strong&gt;and&lt;/strong&gt; the source code during CI resulting in an image
full of junk in &lt;code&gt;/usr/../ca-certificates&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pretty obvious in hindsight.&lt;/p&gt;
&lt;p&gt;The fixed example Dockerfile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:latest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;CERTIFICATES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certificates &lt;span class="c"&gt;# moved from base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="nv"&gt;$CERTIFICATES&lt;/span&gt;/* /usr/local/share/ca-certificates/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Also worth noting, when installing custom certs in a multistage
you&amp;rsquo;ll need to copy the certs into the final stage and re-run
&lt;code&gt;update-ca-certificates&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# example of multistage with certs&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# do stuff&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;CERTIFICATES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;certificates
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="nv"&gt;$CERTIFICATES&lt;/span&gt;/* /usr/local/share/ca-certificates/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; update-ca-certificates&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# do stuff that needs custom cert&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --from&lt;span class="o"&gt;=&lt;/span&gt;builder /usr/local/share/ca-certificates/ /usr/local/share/ca-certificates/&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# copy whatever else&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; update-ca-certificates&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #docker #cert
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Quick math temp conversions</title><link>https://danielms.site/zet/2023/quick-math-temp-conversions/</link><pubDate>Fri, 19 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/quick-math-temp-conversions/</guid><description>&lt;h1 id="quick-math-temp-conversions"&gt;Quick math temp conversions&lt;/h1&gt;
&lt;p&gt;Commit to memory; converting to and from Celsius and Farenheit.&lt;/p&gt;
&lt;p&gt;Close enough approximations which are good enough for field work.&lt;/p&gt;
&lt;p&gt;C to F&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;Cx2&lt;span class="o"&gt;)&lt;/span&gt; + &lt;span class="m"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;10&lt;/span&gt; celsius X &lt;span class="nv"&gt;2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;20&lt;/span&gt; + &lt;span class="nv"&gt;30&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exact is formula is:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;10*1.8&lt;span class="o"&gt;)&lt;/span&gt; + &lt;span class="m"&gt;32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;so comparing the two methods the difference is
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;quick: &lt;span class="m"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exact: &lt;span class="m"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;If the temp is 30C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;the delta between the methods grows slightly
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;quick: &lt;span class="m"&gt;90&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exact: &lt;span class="m"&gt;86&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;F to C&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;F-30&lt;span class="o"&gt;)&lt;/span&gt;/2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;55&lt;/span&gt; - &lt;span class="nv"&gt;30&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;25&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;25/2 &lt;span class="o"&gt;=&lt;/span&gt; 12.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Exact formula is:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;F-32&lt;span class="o"&gt;)&lt;/span&gt;/1.8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;comparing them:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;quick: 12.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;exact: 12.7
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This cowboy math is basically all you need in life.&lt;/p&gt;
&lt;p&gt;Calculatingusing decimals and fractions is dumb when you can approximate
it using year 3 math skills.&lt;/p&gt;
&lt;p&gt;Hardest part is remembering the formula.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#math #til #skills
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Docker catalog and digest using crane</title><link>https://danielms.site/zet/2023/docker-catalog-and-digest-using-crane/</link><pubDate>Wed, 17 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/docker-catalog-and-digest-using-crane/</guid><description>&lt;h1 id="docker-catalog-and-digest-using-crane"&gt;Docker catalog and digest using crane&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ve written a number of bash scripts over my time to check container registry
catalogs and then parse them for certain tags.&lt;/p&gt;
&lt;p&gt;TIL that google&amp;rsquo;s crane supports this out of the box.&lt;/p&gt;
&lt;p&gt;Install crane with &lt;code&gt;arkade&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;arkade get crane
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s the output of &lt;code&gt;ls&lt;/code&gt; and &lt;code&gt;digest&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# list all tags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;crane ls danielmichaels/sitesearch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;915194c0cd6350048a4b6c3a24eb555bae7e0a11
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;9c7d76991e79a8dd0fa1f081e70b5c2daf6838e1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;a12e6f4326f0a3397df42cade3e4382dd1915f83
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ab658d594ab00b36eb7a66d9493654d2d0f7d971
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;d7a22105650736aaa3f3fbf8a81a063824be6d0a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ed22c15b0057c1a5ad93a836e1793b01c7f2e195
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;okteto
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;staging
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# get a tag digest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;crane digest danielmichaels/sitesearch:latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sha256:2fdca04e110e75df31c22a33fcd6db9edda6fb36cc86c44bece01d0725346e72
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #crane #docker #registry
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Running a business with OpenFaaS, Stripe and Sendgrid</title><link>https://danielms.site/blog/openfaas-stripe-sendgrid-a-business-model/</link><pubDate>Mon, 08 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/openfaas-stripe-sendgrid-a-business-model/</guid><description>&lt;p&gt;I did some work recently to set up a payments and notification backend for a &lt;a href="https://carrd.co"&gt;Carrd&lt;/a&gt; website.
The website integrates with some booking services which are linked to payment providers.&lt;/p&gt;
&lt;p&gt;The frontend needed two more services added that could not integrate with the booking service,
and that got me thinking about how to level up the site without too much effort.&lt;/p&gt;
&lt;p&gt;I have some experience using &lt;a href="https://openfaas.com"&gt;OpenFaaS&lt;/a&gt; and its smaller cousin, &lt;a href="https://docs.openfaas.com/deployment/faasd/"&gt;Faasd&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="faasd-openfaas"&gt;Faasd? OpenFaaS?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://openfaas.com"&gt;OpenFaaS&lt;/a&gt; is an easy to host Functions-as-a-Service platform. It is a very capable platform
which lets you write functions in almost any language, on any cloud without any vendor lock-in.
It has great tooling including its own CLI and UI. It is often deployed to Kubernetes which I&amp;rsquo;ve
used to achieve interesting things in the past.&lt;/p&gt;
&lt;p&gt;This project needed something a lot smaller and lightweight enough to run on a small and cheap VPS.
That&amp;rsquo;s where &lt;a href="https://docs.openfaas.com/deployment/faasd/"&gt;faasd&lt;/a&gt; comes in.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a single Go binary with enough firepower to easily run this app. Here&amp;rsquo;s the minimum
&lt;a href="https://docs.openfaas.com/deployment/faasd/#deployment"&gt;system requirements&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;512MB-1GB RAM&lt;/li&gt;
&lt;li&gt;1-4 vCPU cores&lt;/li&gt;
&lt;li&gt;10-25GB of disk space&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="functions"&gt;Functions&lt;/h2&gt;
&lt;p&gt;The way I think about functions is as handlers for requests. Each function serves a &lt;em&gt;route&lt;/em&gt; and
that&amp;rsquo;s about it. Though I don&amp;rsquo;t split functions up by HTTP method, only by the url they&amp;rsquo;re
responsible for.&lt;/p&gt;
&lt;p&gt;In OpenFaaS speak each function gets its own url path such as &lt;code&gt;https://fn.faas.url/function/&amp;lt;fn-name&amp;gt;/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;How it works is pretty impressive and well detailed across all the OpenFaaS blogs and documentation.&lt;/p&gt;
&lt;p&gt;But, I&amp;rsquo;ll do my best to give a run down on how OpenFaaS works.&lt;/p&gt;
&lt;p&gt;OpenFaaS takes a lower level concept like container orchestration and kubernetes and abstracts away
all the complexity by exposing an application surface. This application surface is in the form of
&lt;em&gt;functions&lt;/em&gt;. Each function is its own container and for the most part accepts something from &lt;code&gt;stdin&lt;/code&gt;
and outputs &lt;code&gt;stdout&lt;/code&gt; or &lt;code&gt;stderr&lt;/code&gt; to the caller. Functions can be invoked via HTTP, the CLI or
through event bridges such as Kafka, NATS, MQTT and so on. It also has a &lt;a href="https://docs.openfaas.com/reference/cron/"&gt;cron&lt;/a&gt; trigger which is a
great builtin feature.&lt;/p&gt;
&lt;p&gt;Since each function is its own container OpenFaaS makes it easier to scale up your functions. Just
add more containers for that function. Isolation between functions is also a benefit. One bad function
won&amp;rsquo;t crash your entire app.&lt;/p&gt;
&lt;p&gt;Being that everything is containers means I can have functions in Go, python, node, C# or even bash.
I typically use Go these days but have used a python function to augment my Go functions because
it was the right choice. Had that project been a server written in Go more than likely I would
of had to stand up a microservice and then setup NATS or HTTP endpoints for them to interact.
Using OpenFaaS made it a cinch.&lt;/p&gt;
&lt;p&gt;Although it&amp;rsquo;s not perfect. Sometimes function codebases can have a lot of repeated code.
As an example, both my functions use the same logger and database packages which are identical. It&amp;rsquo;s
not so bad because I have scripts that autogenerate new functions and handle these things.
I will point out that you can create your own &lt;a href="https://docs.openfaas.com/cli/templates/#customise-a-template"&gt;function template&lt;/a&gt; which could include
any required modules effectively removing this hiccup.&lt;/p&gt;
&lt;p&gt;Another beautiful addition is prometheus and Alert Manager out of the box. Whilst I am not using
these for this project it is nice knowing I can easily hook up &lt;a href="https://docs.openfaas.com/openfaas-pro/grafana-dashboards/"&gt;grafana&lt;/a&gt; in the future.&lt;/p&gt;
&lt;p&gt;For a really succinct summary of OpenFaaS with great explanatory images please read
&lt;a href="https://twitter.com/iximiuz"&gt;Ivan Velichko&lt;/a&gt;&amp;rsquo;s &lt;a href="https://iximiuz.com/en/posts/openfaas-case-study/"&gt;blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="function-stripe"&gt;Function: Stripe&lt;/h3&gt;
&lt;p&gt;The primary function is the Stripe webhook handler which serves as the most common entrypoint.&lt;/p&gt;
&lt;p&gt;When a customer makes a purchase the webhook handler consumes the response and based on what it is,
acts on it.&lt;/p&gt;
&lt;p&gt;As an example, when a &lt;code&gt;checkout.session.complete&lt;/code&gt; is received, it is marshalled into a struct. Then
inserted into a database, in this case &lt;a href="https://www.mongodb.com/atlas"&gt;MongoDB Atlas&lt;/a&gt;. The function
then calls another function passing along the key for the recently inserted data.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve found this works excellently. It quickly writes to the database and send the POST to the next
function - both done in goroutines.&lt;/p&gt;
&lt;h3 id="function-sendgridemail"&gt;Function: Sendgrid/Email&lt;/h3&gt;
&lt;p&gt;Once a payment intent is received and dealt with in the Stripe function the email function is
triggered. In my opinion this is one of the powerful parts of OpenFaaS; function chaining.&lt;/p&gt;
&lt;p&gt;Once the payload is received the function checks which product was purchased and triggers a series
of emails to the customer and website administrators.&lt;/p&gt;
&lt;p&gt;Leveraging SendGrid here made sense because it has a generous free tier of 100 emails per day.
Even then once you start paying it is still quite reasonable. It also has a decent email template
generator which is used to deliver specific emails based on the product purchased.&lt;/p&gt;
&lt;p&gt;After all that, the functions, third-party integrations and how its invoked look something like
this.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/faasd-overview.png" alt="" title="faasd carrd overview"&gt;&lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;To integrate Stripe with Carrd I used &lt;a href="https://stripe.com/en-au/payments/payment-links"&gt;payment links&lt;/a&gt;
It was my first time using them, and they are &lt;strong&gt;fantastic&lt;/strong&gt;. Hooking up the URL
to Carrd is as easy as creating a button which directs the customer to the payment link.&lt;/p&gt;
&lt;p&gt;Initially, I thought Carrd was only good for a simple landing page but with payment links you
can really stretch it quite far.&lt;/p&gt;
&lt;p&gt;It was the realisation that this project was really just the confluence of several other API&amp;rsquo;s that
made me think about a different way to deal with this problem.&lt;/p&gt;
&lt;p&gt;Is it better than a long-running server? I think it depends on what you want or need out of it.
Do Functions-as-a-Service replace servers? No.&lt;/p&gt;
&lt;p&gt;rRght tool for the right job and I think this is the right tool for now. Knowing that when
this gets bigger I can lift and shift into kubernetes is nice. Nicer still, I don&amp;rsquo;t have to
write any deployment manifests to make it all work because OpenFaaS handles everything under the hood.&lt;/p&gt;
&lt;p&gt;I think in the future I will start all designs with a simple question; do I really need a
backend, or do I just need some functions?&lt;/p&gt;</description></item><item><title>Helm upgrade dynamically with Go</title><link>https://danielms.site/zet/2023/helm-upgrade-dynamically-with-go/</link><pubDate>Sun, 07 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/helm-upgrade-dynamically-with-go/</guid><description>&lt;h1 id="helm-upgrade-dynamically-with-go"&gt;Helm upgrade dynamically with Go&lt;/h1&gt;
&lt;p&gt;I wrote a python file to create &lt;code&gt;helm upgrade&lt;/code&gt; commands dynamically
based on environment variables.&lt;/p&gt;
&lt;p&gt;Not happy with that I also wrote it in Go. I much prefer it because its
easier to distribute and has zero dependencies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Auto generate a helm deployment for use across multiple projects in CI.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// To use this the following is required:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// - Environment variables set with the values needed. Read carefully for how to use each&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// type of variable.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// - GitlabCI, this is primarily designed to be used in GitlabCI using its includes and&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// artifact generation functionality&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// See comments for how to use local environment variables to set values for helm.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// To use this in GitlabCI you only need to set the &amp;#39;variables&amp;#39; block with your required values.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Typically, this job will generate an artifact with the script as its output which a deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// job will have a needs relationship with and will then execute the script. YMMV&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;html/template&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;log&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;strings&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Environment variables, to use HELM_SET_FILE_PREFIX and HELM_SET_PREFIX&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// the following considerations must be taken into a account.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// helm&amp;#39;s &amp;#39;--set&amp;#39; and &amp;#39;--set-file&amp;#39; are used to override yaml fields inside a values.yml file.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Using the following yaml as a example we&amp;#39;ll illustrate how to use these envvars.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// example values.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// db&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//	name: myDatabase&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//	password: &amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//	caCert: {}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Shell environment variables do not allow for &amp;#34;.&amp;#34; notation. To get around this limitation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// it is expected that any &amp;#34;_&amp;#34; after the prefix will be translated into a &amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// For instance, HELM_SET_db_password=myPass would become &amp;#39;--set db.password=myPass&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// HELM_SET_FILE_db_caCert=/tmp/path/0/ca.crt becomes &amp;#39;--set-file db.caCert=/tmp/path/0/ca.crt&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// The outlier here is HELM_VALUES which is always provided as a string such as HELM_VALUES=helm/values.yml,helm/values2.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// which generates &amp;#39;--values helm/values.yml,helm/values2.yml&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;HELM_SET_PREFIX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HELM_SET_VALUE_&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;HELM_SET_FILE_PREFIX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HELM_SET_VALUE_&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;HELM_VALUES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HELM_VALUES&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;extractFileAndSetFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;re_prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Environ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;re_prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;trimmedValues&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SplitN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;re_prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;splitOnEquals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SplitN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trimmedValues&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;dotValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReplaceAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;splitOnEquals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;splitOnEquals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;extractValuesFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;re_prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Environ&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;re_prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SplitN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TemplateData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ChartPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;SetValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;SetFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ValuesFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Allow for --dry-run to be passed in the script; usefule for debugging&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// or when wanting to use helm to template but kubectl to execute/apply&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;DryRun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;setValues&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;extractFileAndSetFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HELM_SET_PREFIX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;setFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;extractFileAndSetFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HELM_SET_FILE_PREFIX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;valuesFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;extractValuesFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;HELM_VALUES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HELM_RELEASE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;chartPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HELM_CHART_PATH&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TemplateData&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Release&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;release&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ChartPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chartPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;SetValue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setValues&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;SetFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;ValuesFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;valuesFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;DryRun&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DRYRUN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;helm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;helmTemplate&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;helmTemplate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;helm upgrade --install &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Release&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.ChartPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.SetValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.SetValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	--set &amp;#39;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.SetFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.SetFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	--set-file &amp;#39;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.ValuesFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.ValuesFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	--values &amp;#39;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;&amp;#39; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	&lt;/span&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	--atomic --timeout 300s &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.DryRun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;--dry-run&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To build this as a distributable binary is as simple as &lt;code&gt;go build main.go&lt;/code&gt;.
You can name the binary with &lt;code&gt;go build -o &amp;lt;name&amp;gt; main.go&lt;/code&gt;. The run the
binary and pipe its output to a script for execution when required. e.g.
&lt;code&gt;go run main.go &amp;gt; deploy-helm.sh&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It is a simple solution and would be keen to here how others solve it.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#helm #go #templates
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Dynamically generate helm commands in CI</title><link>https://danielms.site/zet/2023/dynamically-generate-helm-commands-in-ci/</link><pubDate>Fri, 05 May 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/dynamically-generate-helm-commands-in-ci/</guid><description>&lt;h1 id="dynamically-generate-helm-commands-in-ci"&gt;Dynamically generate helm commands in CI&lt;/h1&gt;
&lt;p&gt;We run &lt;code&gt;helm&lt;/code&gt; for our deployments (eventually switching to Argo thankfully)
and it works OK. What it doesn&amp;rsquo;t do well in is re-usability in CI.&lt;/p&gt;
&lt;p&gt;We have a project which all other projects inherit from for CI and it works
pretty well. It takes some organisation but once its done you can basically
set and forget your deployments. Except that helm sucks for that and
we ended up with each project needing a bespoke &lt;code&gt;helm-deploy&lt;/code&gt; job.&lt;/p&gt;
&lt;p&gt;This week I sat down and designed a better solution using python templating.
Next week I think I&amp;rsquo;ll re-do it in Go because its in the standard lib.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I&amp;rsquo;m doing it python.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;jinja2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;helm_set_prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HELM_SET_VALUE_&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;helm_set_file_prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HELM_SET_FILE_&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;helm_values_prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;HELM_VALUES&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re_prefix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{prefix}&lt;/span&gt;&lt;span class="s2"&gt;\w&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re_prefix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;helm_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helm_set_prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helm_set_prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;helm_set_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helm_set_file_prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;_&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helm_set_file_prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# this is just an array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;helm_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helm_values_prefix&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;#!/bin/bash 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;helm upgrade --install {{ release }} {{ chart_path }} &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;%- i&lt;/span&gt;&lt;span class="s2"&gt;f helm_set -%}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;%- f&lt;/span&gt;&lt;span class="s2"&gt;or k,v in helm_set.items() -%}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --set {{ k }}={{ v }} &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndfor %}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndif %}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;%- i&lt;/span&gt;&lt;span class="s2"&gt;f helm_set_file -%}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;%- f&lt;/span&gt;&lt;span class="s2"&gt;or k,v in helm_set_file.items() -%}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --set-file {{ k }}={{ v }} &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndfor %}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndif %}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;%- i&lt;/span&gt;&lt;span class="s2"&gt;f helm_values -%}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;%- f&lt;/span&gt;&lt;span class="s2"&gt;or v in helm_values -%}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --values {{ v }} &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndfor %}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; {&lt;/span&gt;&lt;span class="si"&gt;% e&lt;/span&gt;&lt;span class="s2"&gt;ndif %}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --atomic --timeout {{ timeout }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;timeout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;TIMEOUT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;300s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;release&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RELEASE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;chart_path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CHART_PATH&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;helm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;helm_set&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;helm_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;helm_set_file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;helm_set_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;helm_values&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;helm_values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# run me like so:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# python main.py &amp;gt; script.sh&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;ll bypass the how of getting this going in CI jobs but to pass values
its as simple as providing environment variables with prefixes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;RELEASE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MyRelease&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HELM_SET_db_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;foobar&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HELM_SET_FILE_cacert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ca.crt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HELM_VALUES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;helm/values.yml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which would create a bash script which looks like&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm upgrade --install MyRelease helm &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set db.name&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foobar&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set-file &lt;span class="nv"&gt;cacert&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ca.crt&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --values &lt;span class="s1"&gt;&amp;#39;helm/values.yml&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --atomic --timeout 300s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This script can then be executed in another job as needed when output
as an artifact which makes its highly extensible.&lt;/p&gt;
&lt;p&gt;Update: I also wrote this in Go which is nicer because you don&amp;rsquo;t need
to install anything and can build it as a binary and pass around easier.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#python #ci #helm
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go: simple background task</title><link>https://danielms.site/zet/2023/go-simple-background-task/</link><pubDate>Wed, 26 Apr 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/go-simple-background-task/</guid><description>&lt;h1 id="go-simple-background-task"&gt;Go: simple background task&lt;/h1&gt;
&lt;p&gt;A really simple background worker snippet. Far from perfect!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Launch a background goroutine.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WaitGroup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// Recover any panic.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// zerolog&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;background-task&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="c1"&gt;// Execute the arbitrary function that we passed as the parameter.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;wg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #snippet
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>zerolog: reference article</title><link>https://danielms.site/zet/2023/zerolog-reference-article/</link><pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/zerolog-reference-article/</guid><description>&lt;h1 id="zerolog-reference-article"&gt;zerolog: reference article&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/rs/zerolog"&gt;zerolog&lt;/a&gt; is my go to logger for all Go
applications.&lt;/p&gt;
&lt;p&gt;I stumbled upon this &lt;a href="https://betterstack.com/community/guides/logging/zerolog/"&gt;article&lt;/a&gt;
and had to capture it somewhere for future reference. It covers every thing
I&amp;rsquo;ll ever need. Already it&amp;rsquo;s given me some inspiration for cleaning up
some loggers.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #zerolog #logging
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>garbage collecting nix</title><link>https://danielms.site/zet/2023/garbage-collecting-nix/</link><pubDate>Sun, 16 Apr 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/garbage-collecting-nix/</guid><description>&lt;h1 id="garbage-collecting-nix"&gt;garbage collecting nix&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;nix-collect-garbage -d&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Also deleting &lt;code&gt;nix profiles&lt;/code&gt; is done by &lt;code&gt;nix profile list&lt;/code&gt; and then
selecting the profile index.&lt;/p&gt;
&lt;p&gt;For instance, &lt;code&gt;nix profile 1&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#nix 
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>kubernetes UnexpectedAdmissionError</title><link>https://danielms.site/zet/2023/kubernetes-unexpectedadmissionerror/</link><pubDate>Wed, 05 Apr 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/kubernetes-unexpectedadmissionerror/</guid><description>&lt;h1 id="kubernetes-unexpectedadmissionerror"&gt;kubernetes UnexpectedAdmissionError&lt;/h1&gt;
&lt;p&gt;Today we lost our storage from underneath us. This spawned hundreds of
pods which failed with &lt;code&gt;UnexpectedAdmissionError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To fix this, all we needed to do was restart our nodes after the storage
issue was fixed.&lt;/p&gt;
&lt;p&gt;To delete all those pods I ran:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; pod in &lt;span class="k"&gt;$(&lt;/span&gt;kubectl get pods -n &amp;lt;namespace&amp;gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -i UnexpectedAdmissionError &lt;span class="p"&gt;|&lt;/span&gt; cut -d&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt; -f 1&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;do&lt;/span&gt; kubectl delete pod &lt;span class="nv"&gt;$pod&lt;/span&gt; -n &amp;lt;namespace&amp;gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kubernetes
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Qantas Wifi and Docker</title><link>https://danielms.site/zet/2023/qantas-wifi-and-docker/</link><pubDate>Sat, 01 Apr 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/qantas-wifi-and-docker/</guid><description>&lt;h1 id="qantas-wifi-and-docker"&gt;Qantas Wifi and Docker&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m currently in the air and spent ages trying to figure out why I could
connect to the in-flight wifi but not trigger the captive portal.&lt;/p&gt;
&lt;p&gt;It was Docker.&lt;/p&gt;
&lt;p&gt;The in-flight wifi shares an address space with docker&amp;rsquo;s defaults.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;172.18.0.0/23&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It did not occur to me that the &lt;code&gt;br&lt;/code&gt; network sharing this was from Docker.
Instead I spent a bit of time trying to manually remove it, and more time
still &lt;code&gt;curl&lt;/code&gt;&amp;lsquo;ing and pinging the gateway to reverse engineer the portal
URL.&lt;/p&gt;
&lt;p&gt;In the end a &lt;code&gt;docker system prune -af&lt;/code&gt; did the trick and immediately
fired the captive portal.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#captive-portal #qantas #networking #docker
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>A viable openapi documentation platform</title><link>https://danielms.site/zet/2023/a-viable-openapi-documentation-platform/</link><pubDate>Wed, 15 Mar 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/a-viable-openapi-documentation-platform/</guid><description>&lt;h1 id="a-viable-openapi-documentation-platform"&gt;A viable openapi documentation platform&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;ve been toying with how best to deploy my &lt;code&gt;openapi&lt;/code&gt; spec file so that
users can integrate with the backend. Initially, I thought about hosting
it on the API server itself. But, it seemed quite complicated and sub optimal
for a go server to do this.&lt;/p&gt;
&lt;p&gt;Instead I think docusarus (which I already use in other projects) has
first class support for this exact use case.&lt;/p&gt;
&lt;p&gt;Demo: &lt;a href="https://docusaurus-openapi.netlify.app/docs/intro"&gt;https://docusaurus-openapi.netlify.app/docs/intro&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#openapi #documentation
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Erik Prince: The Rise and Fall of Blackwater</title><link>https://danielms.site/zet/2023/erik-prince-the-rise-and-fall-of-blackwater/</link><pubDate>Mon, 27 Feb 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/erik-prince-the-rise-and-fall-of-blackwater/</guid><description>&lt;h1 id="erik-prince-the-rise-and-fall-of-blackwater"&gt;Erik Prince: The Rise and Fall of Blackwater&lt;/h1&gt;
&lt;p&gt;Today I listened to a podcast with Erik Prince the founder of Blackwater.&lt;/p&gt;
&lt;p&gt;It was honestly the most inspiring and interesting podcast I&amp;rsquo;ve listened
to in a long time. Some key takeaways,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bias for action: Erik gets it done.&lt;/li&gt;
&lt;li&gt;Pragmatic and practical. He does not overthink (or underthink)&lt;/li&gt;
&lt;li&gt;Men, Mission and then Me; a true leader putting the men first&lt;/li&gt;
&lt;li&gt;A strong believer in acting within the spirit of &lt;em&gt;A letter to Garcia&lt;/em&gt;.
&lt;ul&gt;
&lt;li&gt;I hadn&amp;rsquo;t thought about this in a few years and it made me sad.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Better to employ your rivals than fight them&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Re-cock and go again; don&amp;rsquo;t let failure get in the way just take another angle.&lt;/li&gt;
&lt;li&gt;A &lt;em&gt;why can&amp;rsquo;t we do that&lt;/em&gt; guy - things aren&amp;rsquo;t just &amp;ldquo;that&amp;rsquo;s how it is&amp;rdquo; with Erik&lt;/li&gt;
&lt;li&gt;A fantastic businessman with a good appreciation for history.&lt;/li&gt;
&lt;li&gt;Military bureaucracy breeds &lt;em&gt;zampolit&lt;/em&gt; behaviour.
&lt;ul&gt;
&lt;li&gt;Case in point we had lawyers in our organisation that vetted each job; not to protect us but to
make sure it conformed to the will of the political power!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So much of what Erik spoke about absolutely resonated with me. I fought in
Iraq and Afghanistan and what a fucking joke those wars became through
pathetic leadership at the top and in parliament.&lt;/p&gt;
&lt;p&gt;Link to the episode,
&lt;a href="https://www.listennotes.com/podcasts/shawn-ryan-show/29-erik-prince-inside-the-yE_kBbH2xw5/"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pmc #podcast
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Mongorestore notes</title><link>https://danielms.site/zet/2023/mongorestore-notes/</link><pubDate>Mon, 20 Feb 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/mongorestore-notes/</guid><description>&lt;h1 id="mongorestore-notes"&gt;Mongorestore notes&lt;/h1&gt;</description></item><item><title>Taskfile is my new Makefile</title><link>https://danielms.site/zet/2023/taskfile-is-my-new-makefile/</link><pubDate>Fri, 17 Feb 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/taskfile-is-my-new-makefile/</guid><description>&lt;h1 id="taskfile-is-my-new-makefile"&gt;Taskfile is my new Makefile&lt;/h1&gt;
&lt;p&gt;For every project one of the first things I do is create a Makefile.
Generally this works well except when I need to do some sort of validation, for
example checking the existence of a directory. It&amp;rsquo;s possible but I have to
google it every time.&lt;/p&gt;
&lt;p&gt;Enter &lt;code&gt;Taskfile&lt;/code&gt; a yaml based Makefile written in Go. I am very familiar
with Gitlab pipelines so writing yaml like this comes naturally. To me,
this is so much easier to read and write.&lt;/p&gt;
&lt;p&gt;Example&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# https://taskfile.dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;REGISTRY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;docker.io/me&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;IMAGE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;myimage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; List all available tasks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;task --list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Print all environment variables sorted alphabetically&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;env | sort&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;check&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Run the local development environment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;air&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;docker-build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Build the docker image&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;docker build . -f deploy/Dockerfile -t &amp;#34;{{.REGISTRY}}/{{.IMAGE}}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;docker-push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;deps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;docker-build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Push the docker image&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;docker push &amp;#34;{{.REGISTRY}}/{{.IMAGE}}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This has been incredibly useful in the development of a Django-channels
application where I needed a lot of setup. I was able to pass this over
to my team and the concept well received.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#taskfile #go #makefile #til
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>reproducible random strings in python</title><link>https://danielms.site/zet/2023/reproducible-random-strings-in-python/</link><pubDate>Thu, 16 Feb 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/reproducible-random-strings-in-python/</guid><description>&lt;h1 id="reproducible-random-strings-in-python"&gt;reproducible random strings in python&lt;/h1&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;hashlib&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Type your seed_string here&amp;#39;&lt;/span&gt; &lt;span class="c1"&gt;#Read comment below&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;new_uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I used this to create kube resources programatically whilst keeping
their names consistent for easier deletion and management.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#python #kubernetes
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Makefile check args are passed in</title><link>https://danielms.site/zet/2023/makefile-check-args-are-passed-in/</link><pubDate>Sun, 12 Feb 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/makefile-check-args-are-passed-in/</guid><description>&lt;h1 id="makefile-check-args-are-passed-in"&gt;Makefile check args are passed in&lt;/h1&gt;
&lt;p&gt;TIL how to get a Makefile to abort if arguments are not supplied.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;## sops/decrypt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.PHONY: sops/decrypt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sops/decrypt:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	@test &lt;span class="k"&gt;$(&lt;/span&gt;file&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file= not set&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	@test &lt;span class="k"&gt;$(&lt;/span&gt;regex&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;regex= not set&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	@echo &lt;span class="s2"&gt;&amp;#34;decrypting &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; with regex (data|&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;regex&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	@sops --decrypt --age &lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;cat &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SOPS_AGE_KEY_FILE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -oP &lt;span class="s2"&gt;&amp;#34;public key: \K(.*)&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; --encrypted-regex &lt;span class="s2"&gt;&amp;#34;^(data|&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;regex&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$$&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --in-place &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will error with &lt;code&gt;regex= not set&lt;/code&gt; if not supplied.&lt;/p&gt;
&lt;p&gt;A simple solution that works &lt;em&gt;good enough&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#TIL #Makefile
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>debug failed pod</title><link>https://danielms.site/zet/2023/debug-failed-pod/</link><pubDate>Wed, 08 Feb 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/debug-failed-pod/</guid><description>&lt;h1 id="debug-failed-pod"&gt;debug failed pod&lt;/h1&gt;
&lt;p&gt;I was trying to debug a python job which created a pod using
a bunch of &lt;code&gt;argparse&lt;/code&gt; arguments.&lt;/p&gt;
&lt;p&gt;This is how I figured out how to run all the python args and if
it failed throw a sleep in bash. This then let me jump in
and see what was actually happening on the container.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;appreviated&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# truncated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;&amp;gt;-&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python script.py -l foo -b far || sleep 10000;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# truncated&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ref: &lt;a href="https://devops.stackexchange.com/a/15184/34192"&gt;https://devops.stackexchange.com/a/15184/34192&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kubernetes #til #debug
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>k3s hostmount</title><link>https://danielms.site/zet/2023/k3s-hostmount/</link><pubDate>Tue, 07 Feb 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/k3s-hostmount/</guid><description>&lt;h1 id="k3s-hostmount"&gt;k3s hostmount&lt;/h1&gt;
&lt;p&gt;Using a host mount for local k3s development. This negates the need
for skaffold, okteto etc for 80% use cases.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PROGNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;hostmount&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;usage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROGNAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;container&amp;gt; -- Host mount local files into k3s
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;Must be located in the root directory of the git repo&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; ! -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$usage&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;jsonfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/patch.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;spec&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;template&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;spec&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;containers&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;name&amp;#34;: &amp;#34;%s&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;command&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;sh&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;-c&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;apk add py-watchdog &amp;amp;&amp;amp; watchmedo auto-restart -d $PWD -R -- entrypoint.py&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;volumeMounts&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;mountPath&amp;#34;: &amp;#34;/usr/src/app&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;name&amp;#34;: &amp;#34;host-mount&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;volumes&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;name&amp;#34;: &amp;#34;host-mount&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;hostPath&amp;#34;: {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;path&amp;#34;: &amp;#34;%s&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$service&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt; &lt;span class="nv"&gt;$jsonfile&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &lt;span class="nv"&gt;$jsonfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl patch deployment &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;service&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --patch-file &lt;span class="nv"&gt;$jsonfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This needs a bit of massaging for each project but after that this works great for python.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll create Go version using &lt;code&gt;air&lt;/code&gt; when I have a project that needs it.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#k3s #development
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go-colly meta tags</title><link>https://danielms.site/zet/2023/go-colly-meta-tags/</link><pubDate>Tue, 31 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/go-colly-meta-tags/</guid><description>&lt;h1 id="go-colly-meta-tags"&gt;Go-colly meta tags&lt;/h1&gt;
&lt;p&gt;TIL how to scrape meta tags, specifically &lt;code&gt;og:title&lt;/code&gt; and so on using &lt;code&gt;go-colly&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;html&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;colly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HTMLElement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// note the use of backticks and double quotes - it has to be exactly like this&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ChildAttr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`meta[property=&amp;#34;og:title&amp;#34;]`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#scraping #go #colly
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go oapi-codegen chi subrouters</title><link>https://danielms.site/zet/2023/go-oapi-codegen-chi-subrouters/</link><pubDate>Wed, 25 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/go-oapi-codegen-chi-subrouters/</guid><description>&lt;h1 id="go-oapi-codegen-chi-subrouters"&gt;Go oapi-codegen chi subrouters&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m experimenting with open-api code generation. So far its pretty positive; write some yaml
and it generates a bunch of interfaces. Those interfaces are where you write the handler
business logic. It&amp;rsquo;ll handle missing data in and out and error if so.&lt;/p&gt;
&lt;p&gt;As a &lt;code&gt;chi&lt;/code&gt; user I&amp;rsquo;ve started making a simple todo app to test it all out. I was disappointed
to learn that &lt;code&gt;oapi-codegen&lt;/code&gt; doesn&amp;rsquo;t create the swagger UI for you but it will render the
yaml file for you.&lt;/p&gt;
&lt;p&gt;Except it doesn&amp;rsquo;t work out of the box for &lt;code&gt;chi&lt;/code&gt;. In fact no other endpoint works - only
those specified the specification.&lt;/p&gt;
&lt;p&gt;To get around this you must create a subrouter and use that to do the specification
validation else it&amp;rsquo;ll always fail for other routes. It makes sense in the end.&lt;/p&gt;
&lt;p&gt;This is how I&amp;rsquo;ve done it in a simple way.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httplog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;mudmap&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;httplog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Concise&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;DEBUG&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;With&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Caller&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;swagger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSwagger&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Error loading swagger spec\n: %s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;swagger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Servers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewTodoStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;RealIP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Compress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httplog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RequestLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/docs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;swagger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Add n number of non-specification endpoints. But use sparingly.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;subRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;swagger&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HandlerFromMux&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Addr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0.0.0.0:9090&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;starting server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// subRouter holds the oapi-codegen validators which are &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// mounted to the parent chi router.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;subRouter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;openapi3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Handler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;chi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewRouter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;oapimiddleware&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OapiRequestValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#chi #go #openapi #codegen
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>docker run helper</title><link>https://danielms.site/zet/2023/docker-run-helper/</link><pubDate>Tue, 24 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/docker-run-helper/</guid><description>&lt;h1 id="docker-run-helper"&gt;docker run helper&lt;/h1&gt;
&lt;p&gt;I get tired of running &lt;code&gt;docker run --rm -it &amp;lt;image&amp;gt;:&amp;lt;tag&amp;gt; sh&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;PROGNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;dockerr&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;usage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROGNAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;image&amp;gt; [-h] [-t] [-c] -- docker run helper 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;where:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; -h, --help Show this help text
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; -t, --tag Image tag to use (default: latest)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; -c, --command Command to execute when container runs (default: none)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --entrypoint Override containers entrypoint. This is mutually exclusive with
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; --command and will remove it if applied
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;examples:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROGNAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; python
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROGNAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; python -t buster -c bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PROGNAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; hashicorp/vault -t dev --entrypoint sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;tag=latest
executable=&amp;quot;&amp;quot;
entrypoint=&amp;quot;&amp;quot;&lt;/p&gt;
&lt;p&gt;if [[ ${i} != -* ]]
then
image=&amp;quot;${i}&amp;quot;
fi&lt;/p&gt;
&lt;p&gt;if [ &amp;ldquo;$#&amp;rdquo; -ne 0 ]; then
while [ &amp;ldquo;$#&amp;rdquo; -gt 0 ]; then
do
case &amp;ldquo;$i&amp;rdquo; in
-h|&amp;ndash;help)
usage
;;
-t|&amp;ndash;tag)
tag=&amp;quot;$2&amp;quot;
;;
-c|&amp;ndash;command)
executable=&amp;quot;$2&amp;quot;
;;
&amp;ndash;entrypoint)
entrypoint=&amp;quot;&amp;ndash;entrypoint $2&amp;quot;
executable=&amp;quot;&amp;quot;
;;
&amp;ndash;)
break
;;
-*)
echo &amp;ldquo;Invalid option &amp;lsquo;$1&amp;rsquo;. Use &amp;ndash;help to see arguments&amp;rdquo; &amp;gt;&amp;amp;2
exit 1
;;
*)
;;
esac
shift
done
else
echo &amp;ldquo;$usage&amp;rdquo;
exit 1
fi&lt;/p&gt;
&lt;p&gt;echo &amp;ldquo;running: docker run &amp;ndash;rm -it $entrypoint $image:$tag $executable&amp;rdquo;
docker run &amp;ndash;rm -it $entrypoint $image:$tag $executable&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;This saves me a lot of needless typing. It doesn&amp;#39;t handle &amp;lt;image&amp;gt;:&amp;lt;tag&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;but that can be achieved. This works good enough.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Tags:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #bash #docker 
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Python's distutil strtobool replacement</title><link>https://danielms.site/zet/2023/pythons-distutil-strtobool-replacement/</link><pubDate>Mon, 16 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/pythons-distutil-strtobool-replacement/</guid><description>&lt;h1 id="pythons-distutil-strtobool-replacement"&gt;Python&amp;rsquo;s distutil strtobool replacement&lt;/h1&gt;
&lt;p&gt;From 3.10 &lt;code&gt;distutil.util.strtobool&lt;/code&gt; is deprecated with removal slated for
3.11.&lt;/p&gt;
&lt;p&gt;This is a trivial function to replace its functionality and useful for kubernetes
helm values.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;strtobool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;y&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;yes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #python #helm
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>hstr: bash history fuzzy finder</title><link>https://danielms.site/zet/2023/hstr-bash-history-fuzzy-finder/</link><pubDate>Sat, 14 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/hstr-bash-history-fuzzy-finder/</guid><description>&lt;h1 id="hstr-bash-history-fuzzy-finder"&gt;hstr: bash history fuzzy finder&lt;/h1&gt;
&lt;p&gt;I usually opt for &lt;code&gt;history | grep -i &amp;lt;term&amp;gt;&lt;/code&gt; or &lt;code&gt;history | ag &amp;lt;term&amp;gt;&lt;/code&gt;
depending on which system I&amp;rsquo;m on.&lt;/p&gt;
&lt;p&gt;For local development, I&amp;rsquo;ve started using &lt;code&gt;hstr&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;ref: &lt;a href="https://github.com/dvorka/hstr"&gt;https://github.com/dvorka/hstr&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Fedora: &lt;code&gt;dnf install hstr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ubuntu 22.04 &lt;code&gt;apt instll hstr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Or, just use &lt;code&gt;nix-env -i hstr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Personally, I&amp;rsquo;ve opted for &lt;code&gt;nix&lt;/code&gt; because I&amp;rsquo;m still on 20.04 and I am
tired of installing &lt;code&gt;ppa&lt;/code&gt;&amp;rsquo;s only to have their keys expire and break
all updates on the system.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#nix #tools
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>pfSense on Vultr (and a rant)</title><link>https://danielms.site/zet/2023/pfsense-on-vultr-and-a-rant/</link><pubDate>Sat, 14 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/pfsense-on-vultr-and-a-rant/</guid><description>&lt;h1 id="pfsense-on-vultr-and-a-rant"&gt;pfSense on Vultr (and a rant)&lt;/h1&gt;
&lt;p&gt;I run test pfSense firewalls on Vultr. It is a pain in ass hand cranking
them each time.&lt;/p&gt;
&lt;p&gt;I have a terraform module which can create the instance but not fully. Two issues
arise using terraform.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can only create an instance with an OS or an ISO&lt;/li&gt;
&lt;li&gt;pfSense ISO instances break (it cannot detect the disk) and OS installs don&amp;rsquo;t have pfSense&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So even though I can create the instance using the OS, then switch it to ISO via
two terraform &lt;code&gt;apply&lt;/code&gt;&amp;rsquo;s (there is probably a better one-shot way). It still requires
even more manual steps.&lt;/p&gt;
&lt;p&gt;Next problem is once its started you have to manually step through the TUI installer.
After its installed you need to reboot and &lt;em&gt;manually&lt;/em&gt; remove the ISO in the Vultr
admin dashboard (&lt;em&gt;settings&amp;gt;iso&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Yet another step, after that you have to configure the interfaces via the TUI.&lt;/p&gt;
&lt;p&gt;And finally, then go to https://&lt;!-- raw HTML omitted --&gt;, login with &lt;em&gt;admin:pfsene&lt;/em&gt; and run through
another installer.&lt;/p&gt;
&lt;p&gt;Now you&amp;rsquo;re ready to actually configure it the firewall itself. Such as SSH rules etc.&lt;/p&gt;
&lt;p&gt;A massive pain the arse.&lt;/p&gt;
&lt;p&gt;Also a pain the arse; poor terraform errors like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// this error is actually related to the network block about 30 lines
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// below the resource at line 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// this is up there with ansibles useless &amp;#39;syntax error here or maybe somewhere else&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// errors
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vm&lt;/span&gt; &lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="m"&gt;101&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="nx"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;proxmox_vm_qemu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pfsense_node&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tf&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_vm_qemu&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pfsense_node&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_vm_qemu&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pfsense_node&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;│&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;╵&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here is a really helpful guide: &lt;a href="https://jarrodstech.net/project-pfsense-setup-on-vultr-with-private-lan/"&gt;https://jarrodstech.net/project-pfsense-setup-on-vultr-with-private-lan/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All this BS makes me wish their was a great infrastructure as code firewall which is
open source. A lot of people will say they&amp;rsquo;d never want their firewall managed using
IaC but I say they&amp;rsquo;re living in the past.&lt;/p&gt;
&lt;p&gt;If it ain&amp;rsquo;t in code, it ain&amp;rsquo;t tracked - spreadsheets, device42 etc aren&amp;rsquo;t the same.&lt;/p&gt;
&lt;p&gt;If someone can log on and add or delete a rule and something can&amp;rsquo;t detect that then
its, in my opinion, a liability.&lt;/p&gt;
&lt;p&gt;If anyone reads this and wants to educate me, please (and I&amp;rsquo;m not joking)
email me and let&amp;rsquo;s talk.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pfsense #rant #terraform #iac
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>ArgoCD: refresh v sync</title><link>https://danielms.site/zet/2023/argocd-refresh-v-sync/</link><pubDate>Wed, 11 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/argocd-refresh-v-sync/</guid><description>&lt;h1 id="argocd-refresh-v-sync"&gt;ArgoCD: refresh v sync&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Sync&lt;/strong&gt;: Reconciles the current cluster state with the target state in git.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Refresh&lt;/strong&gt;: Fetches the latest manifests from git and compares the diff.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Hard Refresh&lt;/strong&gt;: Clears any caches and does a &lt;strong&gt;refresh&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #kubernetes #argocd 
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>fc is a hidden linux gem</title><link>https://danielms.site/zet/2023/fc-is-a-hidden-linux-gem/</link><pubDate>Sun, 08 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/fc-is-a-hidden-linux-gem/</guid><description>&lt;h1 id="fc-is-a-hidden-linux-gem"&gt;fc is a hidden linux gem&lt;/h1&gt;
&lt;p&gt;TIL about &lt;code&gt;fc&lt;/code&gt; or fix command.&lt;/p&gt;
&lt;p&gt;Typing &lt;code&gt;fc&lt;/code&gt; and hitting enter will drop you into your &lt;code&gt;$EDITOR&lt;/code&gt; and let
you edit the last command. Saving will execute it.&lt;/p&gt;
&lt;p&gt;For long curl requests its amazing. Now I can send a command and &lt;code&gt;fc&lt;/code&gt; into
to alter the endpoint data etc using vim whilst keeping all the authentication
fields intact.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s far easier than re-issuing the command via the terminal prompt even
with vim bindings.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #linux #tools
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Mudmap updates Jan</title><link>https://danielms.site/zet/2023/mudmap-updates-jan/</link><pubDate>Sun, 08 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/mudmap-updates-jan/</guid><description>&lt;h1 id="mudmap-updates-jan"&gt;Mudmap updates Jan&lt;/h1&gt;
&lt;p&gt;Mudmap updates for my end of month Retro because I always forget what
I did during the month.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenVPN status endpoint (backend) deployed Sun 08 Jan 2023&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#retro #mudmap
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>December 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-dec-2022/</link><pubDate>Sat, 07 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-dec-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Two releases for &lt;a href="https://mudmap.io/?utm_campaign=retro-nov-22&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; and two weeks in &lt;a href="https://en.wikipedia.org/wiki/Perth"&gt;Perth&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;System update available widget&lt;/li&gt;
&lt;li&gt;Installed packages table&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After finishing up the above features I took an extended break from all things code. I still
took my laptop away but never really felt the need to open it up. My holiday back home was
far too relaxing!&lt;/p&gt;
&lt;h3 id="system-update-available"&gt;System Update Available&lt;/h3&gt;
&lt;p&gt;In pfSense, on the main dashboard, it tells if the version is up-to-date or if a
newer version is available.&lt;/p&gt;
&lt;p&gt;Mudmap now does this too. A small quality of life improvement but another step in replicating
the pfSense experience within Mudmap.&lt;/p&gt;
&lt;p&gt;This was &lt;em&gt;simple&lt;/em&gt; for customers who are on the API version 1.5.0 and above. However, it needed
to be backwards compatible for users that aren&amp;rsquo;t. Quite a few are using the older version and I
did not want to prompt them to upgrade. This meant checking their currently installed version
and providing a fallback for them. This fallback indicates that they should update and provides
a link with how to do so.&lt;/p&gt;
&lt;h3 id="installed-packages"&gt;Installed Packages&lt;/h3&gt;
&lt;p&gt;Another quality of life improvement. Users can now check which third party packages are installed
and their versions. It is read-only, as in they cannot upgrade from Mudmap, but it is a start.&lt;/p&gt;
&lt;p&gt;The inclusion of the system update and installed packages means auditing devices got a little easier.&lt;/p&gt;
&lt;h2 id="random-mobility-workout-of-the-day-mwod"&gt;Random Mobility Workout of the Day (MWOD)&lt;/h2&gt;
&lt;p&gt;This was something I knocked up in two evenings to service my own simple needs.&lt;/p&gt;
&lt;p&gt;I love watching and following along with &lt;a href="https://en.wikipedia.org/wiki/Kelly_Starrett"&gt;Kelly Starret&lt;/a&gt;. When I was in the military
the MWOD gave me a lot of relief from pain. Its tough work doing cool shit! Since I&amp;rsquo;ve become
a desk warrior I sort of let it go but that has brought its own problems. I have new musculoskeletal
pains.&lt;/p&gt;
&lt;p&gt;Anyway, I built this to prompt me each day with a random workout to perform. It&amp;rsquo;s a Go app using
templates to pull a list of 160+ videos from YouTube and embed them in the site. Go plus Sqlite
makes life pretty easy. &lt;a href="https://github.com/danielmichaels/rmwod"&gt;Codebase&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Check it out, &lt;a href="https://randommwod.com?utm_source=danielms"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="cookiecutters"&gt;Cookiecutter&amp;rsquo;s&lt;/h2&gt;
&lt;p&gt;I also knocked up a couple of [cookiecutter] application templates. This was the fist time I&amp;rsquo;ve
created my own. I took inspiration from &lt;a href="https://simonwillison.net/"&gt;Simon Willison&amp;rsquo;s&lt;/a&gt; &lt;a href="https://github.com/simonw/click-app"&gt;Click-App&lt;/a&gt; cookiecutter
approach.&lt;/p&gt;
&lt;p&gt;For most of my CLI applications I use &lt;a href="https://github.com/rwxrob/bonzai"&gt;bonzai&lt;/a&gt;, which supports
nested composition meaning I can include multiple Bonzai apps in one. The hard part is creating a
new Bonzai app - it required cloning, or using a template from GitHub. I didn&amp;rsquo;t like this because
the template has a load of boilerplate &lt;em&gt;I&lt;/em&gt; don&amp;rsquo;t need.&lt;/p&gt;
&lt;p&gt;Instead, I now create new Bonzai apps using
&lt;a href="https://github.com/danielmichaels/bonzai-app"&gt;cookiecutter https://github.com/danielmichaels/bonzai-app&lt;/a&gt;. It&amp;rsquo;s pretty handy for my use case
and I think most people could knock up a unique cookiecutter for themselves in a couple of hours.&lt;/p&gt;
&lt;p&gt;Taking heed to my advice above, and just as I was about to start building out &lt;a href="https://randommwod.com?utm_source=danielms"&gt;Random MWOD&lt;/a&gt;
I decided this was a good opportunity to create a Go web app cookiecutter.&lt;/p&gt;
&lt;p&gt;I like my Go web apps as a starting point to use &lt;a href="https://github.com/go-chi/chi"&gt;go-chi&lt;/a&gt;, &lt;a href="https://tailwindcss.com"&gt;tailwind&lt;/a&gt; and &lt;a href="https://alpinejs.dev"&gt;alpine&lt;/a&gt;. So I
created the cookiecutter to build a minimal app with these tools. It also uses &lt;a href="https://github.com/cosmtrek/air"&gt;air&lt;/a&gt; for
hot-reloading and &lt;a href="https://github.com/pressly/goose"&gt;goose&lt;/a&gt; for database migrations.&lt;/p&gt;
&lt;p&gt;This highly opinionated cookiecutter can be found, &lt;a href="https://github.com/danielmichaels/go-web-app"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;Book: 100 Go Mistakes and How to Avoid Them (&lt;a href="https://www.manning.com/books/100-go-mistakes-and-how-to-avoid-them"&gt;link&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;I write a decent amount of Go but there are various parts of the language which have tripped my
up in the past. This book covers all the issues I&amp;rsquo;ve ever footgunned myself with. Each one explained
well with not just why it happened but also the methods to fix it.&lt;/p&gt;
&lt;p&gt;For any Go developer I think its worthwhile reading. So far I&amp;rsquo;ve learned a lot more about slices
(under the hood), strings (particularly the importance of Runes) and solidified my knowledge in
control structures.&lt;/p&gt;
&lt;p&gt;There is usually a few ways of achieving things in programming. Something I really like about
this book is that the author is opinionated about why &lt;em&gt;x&lt;/em&gt; thing is the best method. When
there are caveats those are well explained, too.&lt;/p&gt;
&lt;p&gt;For instance, when checking if a slice is empty the best way is to check its length. This will
trap both &lt;code&gt;nil&lt;/code&gt; and &lt;code&gt;empty&lt;/code&gt; slices.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// checks for nil and empty&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// empty slices have a len of zero and nil slices are always empty&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I&amp;rsquo;m only about 40% of the way through, and I&amp;rsquo;ve referenced it a couple of times already. Another
thing which is really neat; succinct little sections for each &lt;em&gt;Go Mistake&lt;/em&gt;. This means you can easily
pick it up whenever you&amp;rsquo;ve got a spare moment and it reads well on Kindle.&lt;/p&gt;
&lt;h2 id="beach-mode"&gt;Beach Mode&lt;/h2&gt;
&lt;p&gt;The rest of this month was me soaking up rays at Perth&amp;rsquo;s beautiful beaches.&lt;/p&gt;
&lt;p&gt;&lt;img src="beach-mode.jpeg" alt="" title="chilling at the beach"&gt;&lt;/p&gt;</description></item><item><title>Kong is an amazing CLI for Go apps</title><link>https://danielms.site/zet/2023/kong-is-an-amazing-cli-for-go-apps/</link><pubDate>Sat, 07 Jan 2023 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2023/kong-is-an-amazing-cli-for-go-apps/</guid><description>&lt;h1 id="kong-is-an-amazing-cli-for-go-apps"&gt;Kong is an amazing CLI for Go apps&lt;/h1&gt;
&lt;p&gt;Firstly, I want to acknowledge that I love Bonzai for command composition and
&lt;em&gt;most&lt;/em&gt; CLI&amp;rsquo;s. It&amp;rsquo;s my goto and is well designed.&lt;/p&gt;
&lt;p&gt;However, for complex applications in a team (who need get-opt like tooling)
Bonzai doesn&amp;rsquo;t quite fit the bill.&lt;/p&gt;
&lt;p&gt;Why not Cobra? It bloated as hell. The code generator creates in my opinion code
that quickly gets messy and is hard to test.&lt;/p&gt;
&lt;p&gt;I was using urfave/cli for quite a while and like it but started hitting some
issues when I structured it how I like it. Funny formatting issues, global flags
not working correctly, or overwriting subcommand flags.&lt;/p&gt;
&lt;p&gt;Now I use alecthomas/kong. Here&amp;rsquo;s an example from its git repo using a struct
based approach (it&amp;rsquo;s kind of similar to mitchellh/cli in this way).&lt;/p&gt;
&lt;p&gt;This is a Docker like CLI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// ref: https://github.com/alecthomas/kong/blob/master/_examples/docker&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/alecthomas/kong&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Location of client config files&amp;#34; default:&amp;#34;~/.docker&amp;#34; type:&amp;#34;path&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Debug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`short:&amp;#34;D&amp;#34; help:&amp;#34;Enable debug mode&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`short:&amp;#34;H&amp;#34; help:&amp;#34;Daemon socket(s) to connect to&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;LogLevel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`short:&amp;#34;l&amp;#34; help:&amp;#34;Set the logging level (debug|info|warn|error|fatal)&amp;#34; default:&amp;#34;info&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TLS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Use TLS; implied by --tls-verify&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TLSCACert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`name:&amp;#34;tls-ca-cert&amp;#34; help:&amp;#34;Trust certs signed only by this CA&amp;#34; default:&amp;#34;~/.docker/ca.pem&amp;#34; type:&amp;#34;path&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TLSCert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Path to TLS certificate file&amp;#34; default:&amp;#34;~/.docker/cert.pem&amp;#34; type:&amp;#34;path&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TLSKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Path to TLS key file&amp;#34; default:&amp;#34;~/.docker/key.pem&amp;#34; type:&amp;#34;path&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TLSVerify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Use TLS and verify the remote&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`name:&amp;#34;version&amp;#34; help:&amp;#34;Print version information and quit&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DecodeContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;IsBool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;BeforeApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Attach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AttachCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`cmd:&amp;#34;&amp;#34; help:&amp;#34;Attach local standard input, output, and error streams to a running container&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BuildCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`cmd:&amp;#34;&amp;#34; help:&amp;#34;Build an image from a Dockerfile&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// omitted all the other options&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AttachCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;DetachKeys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Override the key sequence for detaching a container&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;NoStdin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Do not attach STDIN&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;SigProxy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`help:&amp;#34;Proxy all received signals to the process&amp;#34; default:&amp;#34;true&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`arg required help:&amp;#34;Container ID to attach to.&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;AttachCmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Config: %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Attaching to: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;SigProxy: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SigProxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BuildCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Arg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`arg required`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;BuildCmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLI&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0.1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;docker&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;A self-sufficient runtime for containers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsageOnError&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureHelp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HelpOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Compact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vars&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0.0.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FatalIfErrorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example usage:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# go run . --help&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Usage: docker &amp;lt;command&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;A self-sufficient runtime &lt;span class="k"&gt;for&lt;/span&gt; containers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Flags:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -h, --help Show context-sensitive help.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --config&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;~/.docker&amp;#34;&lt;/span&gt; Location of client config files
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -D, --debug Enable debug mode
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H, --host&lt;span class="o"&gt;=&lt;/span&gt;HOST,... Daemon socket&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; to connect to
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -l, --log-level&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;info&amp;#34;&lt;/span&gt; Set the logging level &lt;span class="o"&gt;(&lt;/span&gt;debug&lt;span class="p"&gt;|&lt;/span&gt;info&lt;span class="p"&gt;|&lt;/span&gt;warn&lt;span class="p"&gt;|&lt;/span&gt;error&lt;span class="p"&gt;|&lt;/span&gt;fatal&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --tls Use TLS&lt;span class="p"&gt;;&lt;/span&gt; implied by --tls-verify
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --tls-ca-cert&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;~/.docker/ca.pem&amp;#34;&lt;/span&gt; Trust certs signed only by this CA
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --tls-cert&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;~/.docker/cert.pem&amp;#34;&lt;/span&gt; Path to TLS certificate file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --tls-key&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;~/.docker/key.pem&amp;#34;&lt;/span&gt; Path to TLS key file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --tls-verify Use TLS and verify the remote
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --version Print version information and quit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Commands:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; attach Attach &lt;span class="nb"&gt;local&lt;/span&gt; standard input, output, and error streams to a running container
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; build Build an image from a Dockerfile
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Run &lt;span class="s2"&gt;&amp;#34;docker &amp;lt;command&amp;gt; --help&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; more information on a command.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Calling &lt;code&gt;attach&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# go run . attach --sig-proxy=false container123&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Config: /home/dan/.docker
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Attaching to: container123
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SigProxy: &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What I love about it is the ease of use when structing your application. I like
to put things into &lt;code&gt;internal&lt;/code&gt; and &lt;code&gt;Kong&lt;/code&gt; makes it easy to place my CLI command
structs outside of the &lt;code&gt;main&lt;/code&gt;/&lt;code&gt;run&lt;/code&gt; command within &lt;code&gt;cmd/cli/main.go&lt;/code&gt;. These CLI
structs are then placed into the above examples &lt;code&gt;CLI&lt;/code&gt; struct as fields and
everything &lt;em&gt;just works&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I swapped out urfave/cli for Kong in about 40 minutes - about 7 commands in
total.&lt;/p&gt;
&lt;p&gt;Highly recommend it.&lt;/p&gt;
&lt;h2 id="additional-content-11-july-2024"&gt;Additional content (11 July 2024)&lt;/h2&gt;
&lt;p&gt;Here is another example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/alecthomas/kong&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;kongyaml&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/alecthomas/kong-yaml&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;os&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;path/filepath&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;text/template&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pvenotify&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ConfigFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ConfigFlag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`short:&amp;#34;c&amp;#34; help:&amp;#34;Location of client config files&amp;#34; type:&amp;#34;path&amp;#34; default:&amp;#34;${config_path}&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`name:&amp;#34;version&amp;#34; help:&amp;#34;Print version information and quit&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`name:&amp;#34;username&amp;#34; help:&amp;#34;Username to authenticate with&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DecodeContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;IsBool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;BeforeApply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Kong&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Watch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WatchCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`cmd:&amp;#34;&amp;#34; help:&amp;#34;Watch Proxmox for updates&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;WatchCmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;WatchCmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Executing &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;development&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CLI&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;VersionFlag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="c1"&gt;// Display help if no args are provided instead of an error message&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;--help&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;configDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UserConfigDir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Failed to get user config dir: %v\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;defaultConfigPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;defaultConfigFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaultConfigPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config.yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialiseConfigFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;defaultConfigPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;defaultConfigFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to initialise config file:&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;A Proxmox Notification Service&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UsageOnError&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureHelp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HelpOptions&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Compact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;kongyaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Loader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;defaultConfigFile&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DefaultEnvars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;appName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;kong&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vars&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config_path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;defaultConfigFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;cli&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FatalIfErrorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;initialiseConfigFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configFileName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;doesNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configFileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config file does not exist. attempting to create it. &amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;CreateDirectoryIfNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileData&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;globals&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;tfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;./config.yaml&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Must&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tfile&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generateDefaultConfigFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configFileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;config file created at: %s\n&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;configFileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doesNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;CreateDirectoryIfNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0755&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Globals&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;generateDefaultConfigFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;FileData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;doesNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This example will read from (and in this exact order):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;flag&lt;/li&gt;
&lt;li&gt;config file&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a little surprising to me. I would expect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;flags&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;config file&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hopefully I am doing it wrong and theres a way to enforce that. Until then, I
would have to rethink how to best configure such a CLI/tool and whether or not
to leverage a config file/env/flag mixture.&lt;/p&gt;
&lt;p&gt;In contrast, &lt;a href="https://github.com/peterbourgon/ff"&gt;https://github.com/peterbourgon/ff&lt;/a&gt; does work as I expect. I have
another &lt;code&gt;zet&lt;/code&gt; with a working code snippet showcasing it.&lt;/p&gt;
&lt;p&gt;Still, I much prefer the &lt;em&gt;style&lt;/em&gt; of &lt;code&gt;kong&lt;/code&gt;. The interface approach works well
for my mental model, as does the struct tags approach. It is very easy to follow
the code especially when the structs and/or methods are split across multiple
files.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#cli #go #kong
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Why twitter beats mastodon</title><link>https://danielms.site/zet/2022/why-twitter-beats-mastodon/</link><pubDate>Wed, 21 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/why-twitter-beats-mastodon/</guid><description>&lt;h1 id="why-twitter-beats-mastodon"&gt;Why twitter beats mastodon&lt;/h1&gt;
&lt;p&gt;Mastodon is the HackerNews version of Twitter. Only technical people navigate
to the site, and even less contribute.&lt;/p&gt;
&lt;p&gt;One of Twitter&amp;rsquo;s downsides is it is an echo chamber with zero-bar for entry. It&amp;rsquo;s
designed to draw engagement out from every single user. From the follower counts,
retweets, likes and impressions - literally everything encourages maximum &lt;em&gt;engagement&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This can breed interactions akin to the child who seeks attention from their
parents but doesn&amp;rsquo;t care if its good or bad attention.&lt;/p&gt;
&lt;p&gt;But, you can bypass all this stuff through curation, muting, blocking and
ignoring. Between stimulus and response lies the freedom to choose your
on way - it&amp;rsquo;s you who chooses to &lt;em&gt;engage&lt;/em&gt; or &lt;em&gt;enrage&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Some will say the Mastodon is the answer here. I strongly disagree. It is
a walled garden that you build yourself. You design your own cage based on
the thoughts and feelings you like. I thought Twitter was an echo chamber
and then I found Mastodon.&lt;/p&gt;
&lt;p&gt;There is limited discovery of new ideas. Very few reasons to expand your
own horizons. And a corpus of mostly one-sided political and societal views.
&lt;em&gt;Tech Twitter&lt;/em&gt; is a vacuum but the majority of &lt;strong&gt;new&lt;/strong&gt; Tooters are either
leaving because they&amp;rsquo;re following the latest trend, or disagree with the
current leadership.&lt;/p&gt;
&lt;p&gt;To the last point, that&amp;rsquo;s a valid reason. But now you&amp;rsquo;re just attracting
a whole bunch of folks who look different but think alike. Hardly an environment
to generate discourse with anything more than thinly veiled like-a-ship
seeking behaviour.&lt;/p&gt;
&lt;p&gt;Couple all of this with the overall difficulty in finding a server, an
app to use and the finding good folks to follow (who are often disparately
spread across several servers) its a steep hill to climb. For the tech
crowd it&amp;rsquo;s a badge of honour that you figured it all out. For the sports,
educational, political, societal, environmental and the list goes on, others
who don&amp;rsquo;t have the inclination, time or prowess to figure it all out?
Well too bad dummy! iTs NoT ThAT hArD :sponge_bob_meme:&lt;/p&gt;
&lt;p&gt;My parents can get on Twitter. They won&amp;rsquo;t make it to Mastodon in this lifetime.&lt;/p&gt;
&lt;p&gt;But maybe that&amp;rsquo;s what the &lt;strong&gt;new&lt;/strong&gt; tooters kind of want - a walled garden to
call their own.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #mastodon #twitter
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>k3s: tuning local development</title><link>https://danielms.site/zet/2022/k3s-tuning-local-development/</link><pubDate>Mon, 19 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/k3s-tuning-local-development/</guid><description>&lt;h1 id="k3s-tuning-local-development"&gt;k3s: tuning local development&lt;/h1&gt;
&lt;p&gt;A couple of tunables for single node master/agent
k3s.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fix high CPU on gnome&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Higher usage on Gnome/KDE can be caused by the large
number of overlay volumes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;systemctl stop --user gvfs-udisks2-volume-monitor&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disable leader election&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For a single node development environment removing leader
election can reduce CPU usage.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;lt;&amp;lt; EOF &lt;span class="p"&gt;|&lt;/span&gt; sudo tee /etc/rancher/k3s/config.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-controller-manager-arg:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="s2"&gt;&amp;#34;leader-elect=false&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="s2"&gt;&amp;#34;node-monitor-period=60s&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Tags:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;#k3s #kubernetes #TIL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>Go:embed reading individual files</title><link>https://danielms.site/zet/2022/goembed-reading-individual-files/</link><pubDate>Sun, 18 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/goembed-reading-individual-files/</guid><description>&lt;h1 id="goembed-reading-individual-files"&gt;Go:embed reading individual files&lt;/h1&gt;
&lt;p&gt;I was having trouble accessing a particular file (json, in this case).
In the past, I&amp;rsquo;ve had no drama getting at templates and the like but was
clearly missing something here.&lt;/p&gt;
&lt;p&gt;To access a single file, you can use the &lt;code&gt;ReadFile&lt;/code&gt; method on the embedded
filesystem.&lt;/p&gt;
&lt;p&gt;For instance,&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# tree .&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── embeds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── stuff.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I want to explicitly unmarshal &lt;code&gt;stuff.json&lt;/code&gt; in my &lt;code&gt;main.go&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;An abridged version of how to do that.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;//go:embed files&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jsonFiles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fun&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;jsonFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embeds/stuff.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatalln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;failed to read embedded file&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This opens the file for reading and gets all of its contents. A perfect
solution for my needs.&lt;/p&gt;
&lt;p&gt;To read the entire directory just swap out &lt;code&gt;ReadFile&lt;/code&gt; with
&lt;code&gt;ReadDir(&amp;quot;embeds&amp;quot;)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; #go #embed #TIL
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Python rich progress bar and httpx</title><link>https://danielms.site/zet/2022/python-rich-progress-bar-and-httpx/</link><pubDate>Thu, 15 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/python-rich-progress-bar-and-httpx/</guid><description>&lt;h1 id="python-rich-progress-bar-and-httpx"&gt;Python rich progress bar and httpx&lt;/h1&gt;
&lt;p&gt;How to create a progress bar using &lt;code&gt;rich&lt;/code&gt; and &lt;code&gt;httpx&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/tmp/file&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://releases.ubuntu.com/20.04/ubuntu-20.04.3-desktop-amd64.iso&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;follow_redirects&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;Progress&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Content-Length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wb&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[red]Downloading Ubuntu[/red]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iter_raw&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;advance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[yellow]Downloading...[/yellow]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;progress&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[green]Download Complete[/green]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#python #httpx
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Reminiscing</title><link>https://danielms.site/zet/2022/reminiscing/</link><pubDate>Tue, 13 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/reminiscing/</guid><description>&lt;h1 id="reminiscing"&gt;Reminiscing&lt;/h1&gt;
&lt;p&gt;Listening to a workmate talk about shooting MP5&amp;rsquo;s and other
guns whilst at re:Invent in Vegas last week brought back fond memories.&lt;/p&gt;
&lt;p&gt;He was talking about how wild it was shooting an MP5 on fully automatic.&lt;/p&gt;
&lt;p&gt;Meanwhile, I&amp;rsquo;m sitting at my computer watching my Gitlab pipelines run thinking back
to the last time I shot one. It was before breakfast on a random weekday
down at the range practicing vehicle break contacts. We rolled up in the
car to a (fake) checkpoint which had some drop head target dummies. On
initiation I dropped a whole mag straight through a windscreen then made a fighting
withdrawal throwing smoke, grenades and shooting off 40mm.
The MP5k has some kick but it don&amp;rsquo;t matter too much when you trying to kill anything
in front of you with it.&lt;/p&gt;
&lt;p&gt;Fond memories from a life I used to lead and also took for granted. We
truly lived most men&amp;rsquo;s dreams.&lt;/p&gt;
&lt;p&gt;10/10 do not regret my time in the Regiment.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#sas #shooting #reminisce
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Golang: JSON API returning different responses</title><link>https://danielms.site/zet/2022/golang-json-api-returning-different-responses/</link><pubDate>Sun, 11 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/golang-json-api-returning-different-responses/</guid><description>&lt;h1 id="golang-json-api-returning-different-responses"&gt;Golang: JSON API returning different responses&lt;/h1&gt;
&lt;p&gt;I am interfacing with an API which returns different responses from the endpoint
dependant on a success or error. I want to be able to handle every response and this
is how I did that.&lt;/p&gt;
&lt;p&gt;A successful response looks something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;hop&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://danielms.site&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http_version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;HTTP/1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status_code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;200&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;phrase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;OK&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And an error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;The URL could not be resolved.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://danielms.site2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user-agent&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In order to handle both events I created response structs for each and
then added those a generic response struct. The generic response struct I
could then create a &lt;code&gt;UnmarshalJSON&lt;/code&gt; method for which fulfils the &lt;code&gt;Unmarshaller&lt;/code&gt;
interface.&lt;/p&gt;
&lt;p&gt;Here is the struct and method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResponseTypes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;RedirectResponse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ErrorResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrResponse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ResponseTypes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;UnmarshalJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unmarshalErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;unmarshalErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ErrResponse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;errData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrorResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;unmarshalErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;status_code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;respData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;RedirectResponse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;respData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;respData&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unmarshalErr&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The hard part was handling both slice and single element structs at the
same time. Initially I had issues because in order to inspect the response
I needed to unmarshal it. There might be a better way but above I&amp;rsquo;ve just
unmarshalled again if there isn&amp;rsquo;t any error.&lt;/p&gt;
&lt;p&gt;Because Go is awesome, I don&amp;rsquo;t have to do anything out of the ordinary
to use the &lt;code&gt;ResponseTypes.UnmarshalJSON&lt;/code&gt; method. The interface is fulfilled
so I just call it as I would any other &lt;code&gt;UnmarshalJSON&lt;/code&gt; method call.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// used in another function&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ResponseTypes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;refs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.calhoun.io/how-to-parse-json-that-varies-between-an-array-or-a-single-item-with-go/"&gt;https://www.calhoun.io/how-to-parse-json-that-varies-between-an-array-or-a-single-item-with-go/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #TIL
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Automatic Pet Door</title><link>https://danielms.site/zet/2022/automatic-pet-door/</link><pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/automatic-pet-door/</guid><description>&lt;h1 id="automatic-pet-door"&gt;Automatic Pet Door&lt;/h1&gt;
&lt;p&gt;I have a dog and recently got a cat. The cat keeps going out the dog
door which poses a problem; how do I allow the dog to go out freely
but prevent the cat from going out?&lt;/p&gt;
&lt;p&gt;Enter a DIY automatic pet door.&lt;/p&gt;
&lt;p&gt;Requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dog can go freely&lt;/li&gt;
&lt;li&gt;Cat must not be able to exit freely&lt;/li&gt;
&lt;li&gt;Anything on animals must be passive i.e. not battery powered&lt;/li&gt;
&lt;li&gt;No circuitry must live on the pet door (a flapper)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Idea: tag based access control:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the dog door locks when a tag is presented (the cats).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think the door being default to unlocked works better than the
inverse. This way if the cat is at the door whilst the dog it outside
the dog would be locked out. This is a design consideration which
should prevent the cat from tail gating or ambushing the door when
the dog enters or exits.&lt;/p&gt;
&lt;p&gt;My current dog door is a flap based system which uses light magnets
to keep the flap in place. I want to keep using this as its a pet
door which can be used in any sliding door (great for rentals).&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;locking&lt;/em&gt; mechanism would be stronger magnets which are energised
whenever the cat is present preventing the flap from opening.&lt;/p&gt;
&lt;p&gt;A few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Acousto Magnetic detection&lt;/li&gt;
&lt;li&gt;RF detection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rfid #rf #diy #iot
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Hobby as a job has its downsides</title><link>https://danielms.site/zet/2022/hobby-as-a-job-has-its-downsides/</link><pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/hobby-as-a-job-has-its-downsides/</guid><description>&lt;h1 id="hobby-as-a-job-has-its-downsides"&gt;Hobby as a job has its downsides&lt;/h1&gt;
&lt;p&gt;When I was in the army my mental stimulation hobby was coding. I&amp;rsquo;d work
odd hours, sometimes away for weeks at a time with no downtime. So whenever
I could I would be coding up something. This process was hugely rewarding to me.&lt;/p&gt;
&lt;p&gt;Now that my day job is coding, and my hobby is coding it&amp;rsquo;s &lt;em&gt;less&lt;/em&gt; rewarding.&lt;/p&gt;
&lt;p&gt;Not in a bad way - I still love getting home to work on &lt;em&gt;my&lt;/em&gt; stuff. Instead
in a way that I now know how hard it can be to build something from start
to finish, and to maintain it. It makes me less keen on building things
that maybe a few years ago I would of jumped at. This means I&amp;rsquo;m giving
up things that I&amp;rsquo;d otherwise do simply because my hobby is also my job.
At least that&amp;rsquo;s how I see it.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #life
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>RF based detection: terms and definitions</title><link>https://danielms.site/zet/2022/rf-based-detection-terms-and-definitions/</link><pubDate>Sat, 10 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/rf-based-detection-terms-and-definitions/</guid><description>&lt;h1 id="rf-based-detection-terms-and-definitions"&gt;RF based detection: terms and definitions&lt;/h1&gt;
&lt;p&gt;A few definitions for radio frequency detection tools.&lt;/p&gt;
&lt;h2 id="acousto-magnetic"&gt;Acousto-Magnetic&lt;/h2&gt;
&lt;p&gt;Emits a signal at 58kHz in bursts which energise any tag caught in the surveillance zone.
These are very effective at tracking items which may contain metals or metallic paint.
Tags are quite small though I don&amp;rsquo;t think they are coded meaning you cannot track items
with this alone.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rf #definitions
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>My site has RSS</title><link>https://danielms.site/zet/2022/my-site-has-rss/</link><pubDate>Tue, 06 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/my-site-has-rss/</guid><description>&lt;h1 id="my-site-has-rss"&gt;My site has RSS&lt;/h1&gt;
&lt;p&gt;The other day I was trying to figure out if my site; danielms.site has
support for RSS.&lt;/p&gt;
&lt;p&gt;Hugo supports RSS out of the box which means my site does too but I was looking
in the wrong location.&lt;/p&gt;
&lt;p&gt;RSS: &lt;a href="https://danielms.site/index.xml"&gt;https://danielms.site/index.xml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#til #hugo #rss
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>November 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-nov-2022/</link><pubDate>Fri, 02 Dec 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-nov-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Three releases for &lt;a href="https://mudmap.io/?utm_campaign=retro-nov-22&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;! &amp;#x1f389;&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Switched data model to &lt;a href="https://sqlc.dev"&gt;sqlc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Added multi-stage installer&lt;/li&gt;
&lt;li&gt;&lt;em&gt;pfSense&lt;/em&gt; logs&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="switching-to-sqlc"&gt;Switching to &lt;a href="https://github.com/kyleconroy/sqlc"&gt;sqlc&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This isn&amp;rsquo;t something I needed to do so why do it? Two reasons; type safety for the data model
and reduce friction when adding or updating models.&lt;/p&gt;
&lt;p&gt;I experimented with gRPC for another project a couple of months ago and was awestruck by how much
the gRPC compiler does for you. It autogenerates thousands of lines of type safe code which you can
then call without needing to write anything yourself. As an average at best developer I loved
this. Updating the yaml definition rebuilds the interface with a single command and all the callers
get updated. Of course, you might have to change your business logic depending on what you&amp;rsquo;ve
updated, but you don&amp;rsquo;t need to fiddle with any of the service layer. It&amp;rsquo;s a huge time saver.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sqlc&lt;/code&gt; gives you this same experience but with sql. If tomorrow my &lt;code&gt;Device&lt;/code&gt; model needs to include a
&lt;code&gt;Serial&lt;/code&gt; field I would have to update every sql statement, all the &lt;code&gt;sql.Scan&lt;/code&gt; method
calls and then worry about the implementation within handler and service layers. Instead, by
using &lt;code&gt;sqlc&lt;/code&gt; I just update my sql queries and model definitions and the entire data model will be
auto generated for me with those changes. Now all I need to do is focus on business logic
changes (which the go type system will catch).&lt;/p&gt;
&lt;p&gt;It was a bit of learning curve at first especially with &lt;code&gt;uuid&lt;/code&gt; fields and &lt;code&gt;null&lt;/code&gt;&amp;rsquo;s but that
speaks more to my flawed database schema design choices. &lt;em&gt;Rant alert:&lt;/em&gt; for any product/project
I produce in the future I will never use another &lt;code&gt;uuid&lt;/code&gt;. A Stripe-like &lt;a href="https://gist.github.com/fnky/76f533366f75cf75802c8052b577e2a5"&gt;id&lt;/a&gt; (e.g. &lt;code&gt;pi_123abc456&lt;/code&gt;)
or &lt;a href="https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/pet"&gt;Terraform&lt;/a&gt;&amp;rsquo;s &lt;code&gt;random_pet&lt;/code&gt; (e.g. &lt;code&gt;epic_sawfish_123&lt;/code&gt;) would be more than sufficient.&lt;/p&gt;
&lt;h3 id="multi-stage-modals"&gt;Multi-Stage Modals&lt;/h3&gt;
&lt;p&gt;Perhaps my biggest cross the bear in Mudmap is the flaky installer. It really gets me down, and I&amp;rsquo;ve
still yet to find a good way to fix this issue - its churned one of my biggest customers too.&lt;/p&gt;
&lt;p&gt;This feature does not &lt;strong&gt;fix&lt;/strong&gt; the problem, instead it&amp;rsquo;s part of my pathway to making the installation
process more pleasant.&lt;/p&gt;
&lt;p&gt;How it works (starts at &lt;em&gt;Password Confirmation&lt;/em&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img src="mm-modal.svg" alt="" title="A crude picture of how the process works"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;A crude drawing&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This change introduced a multi-stage modal instead of a single step. Using a multi-stage allowed for
better error handling and branching when prompting users for input.&lt;/p&gt;
&lt;p&gt;Additionally, the Device&amp;rsquo;s root password is now being stored in an in-memory database. I&amp;rsquo;ve made an
ideological stand against storing this password in Mudmap&amp;rsquo;s actual database. I feel that storing the
password in a memory datastore is a good practice and does not break the promise of not storing it
in &lt;strong&gt;the&lt;/strong&gt; database. &lt;a href="https://github.com/hashicorp/go-memdb"&gt;go-memdb&lt;/a&gt; was an easy choice for this.
The detractor is that in the future I may need to switch to an external key value store such as Redis
if I spin up multiple services.&lt;/p&gt;
&lt;p&gt;By storing a Device&amp;rsquo;s root password in memory it can now be retrieved during the installation process
across multiple stages. Without doing this, each stage would require the user to re-enter the
password. This is not only inconvenient but also introduces needless complexities like what happens
if they re-enter it wrong - it is another error handler and conditional branch that needs to be
dealt with.&lt;/p&gt;
&lt;p&gt;Adding this also opens up future enhancements such as a proper Deletion event. When a Device is
deleted or the installation fails, Mudmap must be removed from the firewall. This requires the
removal of the Mudmap service account before removing the API. Once the Mudmap service account is
removed every action taken must be done as the root user. This requires root access which with this
new addition does not require user intervention.&lt;/p&gt;
&lt;p&gt;A bad practice which I am trying to fix (and this fixes most of it) is not deleting Mudmap artifacts
when an installation fails. It is possible that these artifacts on subsequent re-installation
attempts may even cause issues. But, the biggest problem is Mudmap isn&amp;rsquo;t being a good citizen and
reverting a user&amp;rsquo;s device back to the state it was when they first tried the platform out.&lt;/p&gt;
&lt;p&gt;I do not expect this will fix failed installations. What I might try next is to let users
install it manually. This would require a few steps and may be too much friction, but it gives
the user power to do something when an issue is encountered. Another thing I am contemplating is
storing some metadata about each device in an attempt to draw some correlations between successful and
unsuccessful installation attempts. I am unsure about this though.&lt;/p&gt;
&lt;h3 id="pfsense-logs"&gt;pfSense Logs&lt;/h3&gt;
&lt;p&gt;This month I also added a few more endpoints and user-facing pages for
viewing the following logs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firewall&lt;/li&gt;
&lt;li&gt;System&lt;/li&gt;
&lt;li&gt;DHCP&lt;/li&gt;
&lt;li&gt;pfSenseAPI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A small-ish update but added functionality all the same. Users can now view the
logging of the above from within Mudmap. The pfSenseAPI audit logger is
particularly handy. Eventually I will need to augment this with the user
controlling the API, not just the API itself.&lt;/p&gt;
&lt;p&gt;Writing this feature required a series of regex parsers to be created. The
response from the API returns a single string per event. By creating custom
parsers it was possible to create useful struct fields which could then be
returned as json objects. A methodology which will likely serve other
endpoints as they are integrated. Not captured in this work (scope creep) but
planned is reading, updating and deleting system packages. Installing
packages will be its own ticket but the methodology to do that is already
there.&lt;/p&gt;
&lt;p&gt;These &lt;em&gt;small&lt;/em&gt; features when stacked up actually make Mudmap much more
useful. Having to SSH into every device just to do a system and third party
package audit is painful, soon Mudmap will remove that burden.&lt;/p&gt;
&lt;h2 id="zettelkastens"&gt;Zettelkastens&lt;/h2&gt;
&lt;p&gt;I use a &lt;a href="https://github.com/danielmichaels/zet-cmd/"&gt;custom implementation&lt;/a&gt; of a &lt;a href="https://en.wikipedia.org/wiki/Zettelkasten"&gt;zettelkasten&lt;/a&gt;
which I use to store snippets of information. The storage backend is GitHub and each Zet is
just a single markdown file stored in a timestamped directory within my &lt;a href="https://github.com/danielmichaels/zet/"&gt;zet&lt;/a&gt;
repo. Sometimes I want to review something I&amp;rsquo;ve
written, but I&amp;rsquo;m not at my computer and searching for it on GitHub is inefficient.
So I wrote a Go tool which would embed it into this &lt;a href="https://danielms.site/zet"&gt;site&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At first, I wrote it to just link directly to the Zet&amp;rsquo;s markdown file on GitHub.
I found this wasn&amp;rsquo;t a great experience as it would take a few seconds to transition
and the layout shift was jarring. I did learn how to render a json file using
Hugo&amp;rsquo;s custom &lt;a href="https://github.com/danielmichaels/danielms/blob/9b0d9d473196e47dc3d629b3552f77525d870839/layouts/shortcodes/render-zet.html"&gt;shortcodes&lt;/a&gt; though.&lt;/p&gt;
&lt;p&gt;Instead of this I decided host the markdown files directly on this site. I enjoyed
writing this because it leverages one of Go&amp;rsquo;s truly great strengths; &lt;code&gt;text/template&lt;/code&gt;.
If you&amp;rsquo;ve come from python you&amp;rsquo;ve probably heard of or used &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja2&lt;/a&gt;
as the templating engine. It&amp;rsquo;s a sweet module but its another dependency and having
an inbuilt templating engine within Go is one of its underappreciated features.&lt;/p&gt;
&lt;p&gt;How I leverage Go&amp;rsquo;s &lt;code&gt;text/template&lt;/code&gt; to auto generate Hugo&amp;rsquo;s frontmatter can be found &lt;a href="https://github.com/danielmichaels/danielms/blob/9b0d9d473196e47dc3d629b3552f77525d870839/create-zet-as-md.go#L166-L191"&gt;here&lt;/a&gt;.
The code is pragmatic and works but don&amp;rsquo;t look to it as an exemplar of &lt;em&gt;good&lt;/em&gt; but instead
&lt;em&gt;practical&lt;/em&gt;. &lt;a href="https://github.com/danielmichaels/danielms/blob/master/fetch-zets.go"&gt;file1&lt;/a&gt;, &lt;a href="https://github.com/danielmichaels/danielms/blob/master/create-zet-as-md.go"&gt;file2&lt;/a&gt; and &lt;a href="https://github.com/danielmichaels/danielms/blob/master/.github/workflows/zet-creator.yaml"&gt;GitHub actions&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.imdb.com/title/tt22872838/"&gt;FIFA Uncovered&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Shock horror FIFA is corrupt as hell. A well crafted overview of how FIFA quickly
became a money &lt;em&gt;under the table&lt;/em&gt; organisation and how it continues to do so today.
When people can wield absolute power (without any oversight) they &lt;em&gt;will&lt;/em&gt; abuse
it for their own gain.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m testing out a new work/fun balance; 3-4 days on mudmap 3-4 days on whatever makes
me happy per week. Going too hard too soon on Mudmap has been the thing that burns me out.
Also, once I release a feature or update I take a couple of days off to build whatever as a
reward. Doing this has actually lead to days where I&amp;rsquo;ve just jumped on &lt;a href="https://excalidraw.com"&gt;Excalidraw&lt;/a&gt; and
explored a Mudmap feature to build in the future. I&amp;rsquo;ll play with this a see where it takes
me.&lt;/p&gt;
&lt;h2 id="goals"&gt;Goals&lt;/h2&gt;
&lt;p&gt;I want to release another Mudmap update before Christmas.&lt;/p&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-11-01"
 let end_date = "2022-11-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-11-01"
 let end_date = "2022-11-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>Golang: find uniques</title><link>https://danielms.site/zet/2022/golang-find-uniques/</link><pubDate>Tue, 29 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/golang-find-uniques/</guid><description>&lt;h1 id="golang-find-uniques"&gt;Golang: find uniques&lt;/h1&gt;
&lt;p&gt;Coming from python where getting a Set from a list is as simple as calling
&lt;code&gt;s = set(array)&lt;/code&gt; the go version is quite verbose.&lt;/p&gt;
&lt;p&gt;Here is how I am finding unique values from an array of integers.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;dedupeInts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ints&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// create a map of int:bool to track found int&amp;#39;s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// create an array of int&amp;#39;s which we&amp;#39;ll add any unique entries to&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ints&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// if value (int) is not in the map marked as true then add it the&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// the map and assign true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// this is how we check for unique entries&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#golang
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Golang: case statements with an OR clause</title><link>https://danielms.site/zet/2022/golang-case-statements-with-an-or-clause/</link><pubDate>Sat, 26 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/golang-case-statements-with-an-or-clause/</guid><description>&lt;h1 id="golang-case-statements-with-an-or-clause"&gt;Golang: case statements with an OR clause&lt;/h1&gt;
&lt;p&gt;TIL how to use Go&amp;rsquo;s switch and case statement with &lt;code&gt;||&lt;/code&gt; (logical OR)
like functionality:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;thing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;things&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;captured both&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Reference:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://go.dev/ref/spec#Switch_statements"&gt;https://go.dev/ref/spec#Switch_statements&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Mudmap: installer fixes</title><link>https://danielms.site/zet/2022/mudmap-installer-fixes/</link><pubDate>Fri, 25 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/mudmap-installer-fixes/</guid><description>&lt;h1 id="mudmap-installer-fixes"&gt;Mudmap: installer fixes&lt;/h1&gt;
&lt;p&gt;This week I&amp;rsquo;ve been working on fixing Mudmap&amp;rsquo;s installer.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve done the math and nearly 50% of installs have failed inexplicitly
in the last 3 months. For users which installation worked without issue
they&amp;rsquo;re happy and never complain. Obviously, the inverse has a lot of
complaints. I am no closer to diagnosing exactly what causes this. A
consistent (&amp;gt;60%) of failures trigger at the user account install step.&lt;/p&gt;
&lt;p&gt;Given this I am exploring that step in detail but also changing the way
the installer is handled on the front end. The error users receive just
confuses them and that needs to go.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also focusing on revert the device back to its pre-mudmap state with
better consistency. This is important for users, and for future installs.
To do this without storing users root password, I&amp;rsquo;ve elected to use an
in memory data store - &lt;code&gt;go-memdb&lt;/code&gt; - which is super lightweight and easy to
understand.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#mudmap #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Slack apps</title><link>https://danielms.site/zet/2022/slack-apps/</link><pubDate>Fri, 25 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/slack-apps/</guid><description>&lt;h1 id="slack-apps"&gt;Slack apps&lt;/h1&gt;
&lt;p&gt;I&amp;rsquo;m thinking about making a slack app. I&amp;rsquo;ve been playing around with it
on/off for the last week and it is an impressive API surface. Documentation
is really good. Perhaps the hardest part is that most examples and tutorials
centre around Node, Python or Java. Slack provides &lt;code&gt;bolt&lt;/code&gt; in those languages
but I basically refuse to write anything in python or node considering their
memory bloat and difficult package management.&lt;/p&gt;
&lt;p&gt;Writing a Slack app using Go has not been as easy as it would be using
python or node though. This is due to the way Slack&amp;rsquo;s BlockKit is designed.
Being able to dynamically update json is much easier in those languages. Stil,
the &lt;code&gt;slack-go&lt;/code&gt; library is pretty good. It&amp;rsquo;s only detractor is documentation.
I&amp;rsquo;ve had to read source, and just try various things to get parts working.&lt;/p&gt;
&lt;p&gt;The app I want to explore, and if viable write is a cloud platform manager.
Render, Fly, Digital Ocean, Vultr etc all provide API access and some a
full CLI. I think being able to get a read out of services, logs, billing
etc via slack would be pretty useful. This means you can check your cloud
assets via slack from anywhere - the CLI is limited to &lt;em&gt;your&lt;/em&gt; machine. It
also means you need an API on the device you&amp;rsquo;re using.&lt;/p&gt;
&lt;p&gt;A stretch goal should this be viable is to create is for several clouds.
I think at first it would be for Render or Fly.&lt;/p&gt;
&lt;p&gt;Lots more ideation and overall design is needed but at worst this would
be something I&amp;rsquo;m keen on using (I use both Render and Fly).&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ideation #slack #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>inlets troubleshooting</title><link>https://danielms.site/zet/2022/inlets-troubleshooting/</link><pubDate>Mon, 14 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/inlets-troubleshooting/</guid><description>&lt;h1 id="inlets-troubleshooting"&gt;inlets troubleshooting&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://inlets.dev"&gt;inlets.dev&lt;/a&gt; troubleshooting.&lt;/p&gt;
&lt;p&gt;So far, I&amp;rsquo;ve had some issues getting to work out of the box. I think this
mostly comes down to the documentation drifting out of sync with the project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inletsctl create &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--access-token-file ~/.doctl-token &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--provider digitalocean &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--region sgp1 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--letsencrypt-domain inlets.cupscanteen.com &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--letsencrypt-domain t.cupscanteen.com &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--letsencrypt-domain slack.cupscanteen.com &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--letsencrypt-email webmaster@cupscanteen.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sometimes this doesn&amp;rsquo;t work. So to get it working, I jump on the host. For
DO the password is emailed when you create the host (which the above step
does for you).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vim /etc/default/inlets-pro
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# add any extra domains with --letsencrypt-domain &amp;lt;sub&amp;gt;.&amp;lt;domain&amp;gt;.&amp;lt;tld&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# after the DOMAINS=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The documentation states its &lt;code&gt;--letsencrypt-domain=foo.com,bar.com&lt;/code&gt; but
this does not seem to work.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;systemctl restart inlets-pro&lt;/code&gt; and see if the process has started correctly. It
can take time to get the cert from Let&amp;rsquo;s Encrypt.&lt;/p&gt;
&lt;p&gt;If this isn&amp;rsquo;t working, try changing &lt;code&gt;/etc/default/inlets-pro&lt;/code&gt; &lt;code&gt;ISSUER&lt;/code&gt;
from &lt;code&gt;prod&lt;/code&gt; to &lt;code&gt;staging&lt;/code&gt; and restart the service. This should connect
and if so, you know its a Let&amp;rsquo;s Encrypt issue. Potentially the IP has been
temporarily rate limited, or the DNS entry does not exist so it cannot do
a HTTP01 interrogation&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#inlets #tunnelling #troubleshooting
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Golang *pq.Error handler for postgres</title><link>https://danielms.site/zet/2022/golang-pq.error-handler-for-postgres/</link><pubDate>Sat, 12 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/golang-pq.error-handler-for-postgres/</guid><description>&lt;h1 id="golang-pqerror-handler-for-postgres"&gt;Golang *pq.Error handler for postgres&lt;/h1&gt;
&lt;p&gt;Since switching over to &lt;a href="https://sqlc.dev"&gt;sqlc&lt;/a&gt; I no longer have access
to the models. This means custom error message returns are not possible
(at least to my knowledge). This caught me out as a duplicate key error
which used to get trapped was slipping through my validation in
&lt;a href="https://mudmap.io?ref=danielms.site.zet"&gt;Mudmap&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how to catch postgres errors using &lt;code&gt;github.com/lib/pq&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Note: apparently &lt;code&gt;pgx&lt;/code&gt; has much better handling for this out of the box.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// from inside a Mudmap handler.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DeviceOrgInsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;orgArgs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// here we crate a variable of the pq.Error type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pgErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;pq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Error&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Using errors.As we inspect the error type&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;pgErr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// I chose to be explicit and call .Name() rather than&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// case over the Code which in this case is 25302 or something&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pgErr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;unique_violation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;devices_host_address_key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;a device with this host address already exists&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failedValidationResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;pg-error&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serverErrorResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #postgres #errors
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Typescript: basics</title><link>https://danielms.site/zet/2022/typescript-basics/</link><pubDate>Fri, 11 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/typescript-basics/</guid><description>&lt;h1 id="typescript-basics"&gt;Typescript: basics&lt;/h1&gt;
&lt;p&gt;Return type on an arrow function&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;greet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`Hi &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Dan&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Prefer &lt;code&gt;type&lt;/code&gt; or &lt;code&gt;interface&lt;/code&gt; when return types are more complicated.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getPlayer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Player&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Jordan&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If your function could return different types, use a &lt;code&gt;union&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getNameOrNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Jordan&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;who cares&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#typescript #programming
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>October 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-oct-2022/</link><pubDate>Sat, 05 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-oct-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;A month of working on only things I find &lt;em&gt;fun&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="contract-hour-tracking"&gt;Contract Hour Tracking&lt;/h2&gt;
&lt;p&gt;I am a full-time employee on a hourly contract basis. It&amp;rsquo;s a funny position
because I&amp;rsquo;m effectively paid casual rates but full-time. Unlike most other
contracts the possibility of my contract being cancelled is nearly zero due
to the extreme staff shortages in my workplace.&lt;/p&gt;
&lt;p&gt;With that, I am given a set number of hours I can work within the contract
period. I cannot go over but any hours remaining at contract end is simply
money I&amp;rsquo;m robbing myself of. Unfortunately, I&amp;rsquo;m pretty lazy about tracking
it all and when I did, I realised that I&amp;rsquo;ve under paid myself over this
contract period. A project idea was spawned.&lt;/p&gt;
&lt;p&gt;Enter a rather simple contract timesheet calculator; &lt;a href="https://github.com/danielmichaels/faas-templates/tree/main/timesheet-calculator"&gt;timesheet-calculator&lt;/a&gt;.
It&amp;rsquo;s pretty simplistic in the output it provides but so far has been pretty
helpful. Every day it sends an email with some stats to keep an eye on such
as my mean daily required hours to reach contract zero. The most useful
emails are my weekly and monthly ones. The monthly email is the most useful
as it spits out the daily log in tabular format making filling out my
monthly timesheet much easier.&lt;/p&gt;
&lt;p&gt;A trivial project but for those with my level of laziness automation is a
solution worth the time investment.&lt;/p&gt;
&lt;h2 id="nomad"&gt;Nomad&lt;/h2&gt;
&lt;p&gt;At work, we&amp;rsquo;re pretty tied to Kubernetes and are actively looking at
replacing our OpenShift cluster. The reasons are varied, but it mostly comes
down this fact; it&amp;rsquo;s too much for us. We&amp;rsquo;re a small team of developers who
also maintain our own Kubernetes cluster which all of our workloads run on.
Without a dedicated Ops team its starting to drain our resources.&lt;/p&gt;
&lt;p&gt;Our initial investigations into alternatives mostly centred around other
Kubernetes platforms. One of the key metrics is it must be on-prem without
the need for an active internet connection - we live and die by the proxy.&lt;/p&gt;
&lt;p&gt;At first, we dismissed Nomad, mostly because of the time we&amp;rsquo;d spent on
Kubernetes and the significant amount of work that&amp;rsquo;s gone into creating its
supporting assets; manifests and pipelines as the exemplars.&lt;/p&gt;
&lt;p&gt;Sunk cost fallacy aside, Nomad, on the surface looks like it suits our
needs more adeptly. I wrote a &lt;a href="https://github.com/danielmichaels/zet/search?q=nomad"&gt;couple of zets&lt;/a&gt; about Nomad; some basics
and what I think is interesting about it. I spent a bit of time playing
around with the &lt;code&gt;nomad agent -dev&lt;/code&gt; mode but couldn&amp;rsquo;t commit too much more
time to getting a home lab up and running. Though I think that is something
I need to do in November, or over the Christmas holiday period.&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;p&gt;I have not done any work on Mudmap this month.&lt;/p&gt;
&lt;p&gt;Honestly, I am still unsure what to do with it. People are still interested,
but I am unsure if it&amp;rsquo;s still right for me.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Andor_(TV_series)"&gt;Andor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the first Disney spin-off that I have enjoyed. I wrote an entire rant
about it &lt;a href="https://github.com/danielmichaels/zet/blob/main/20221022005935/README.md"&gt;here&lt;/a&gt;.
It feels like the first &lt;em&gt;real&lt;/em&gt; storyline to emerge from the Star Wars
universe that isn&amp;rsquo;t just a rehash of existing lore, or absolute garbage
(Mandalorian and Obi-Wan).&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ll keep on making things that are useful to me whilst I figure out the
bigger picture of what I want to do.&lt;/p&gt;
&lt;h2 id="goals"&gt;Goals&lt;/h2&gt;
&lt;p&gt;I think my main thing will be to continue this year seeking enjoyment in my
downtime.&lt;/p&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-10-01"
 let end_date = "2022-10-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-10-01"
 let end_date = "2022-10-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>Nomad Homelab</title><link>https://danielms.site/zet/2022/nomad-homelab/</link><pubDate>Thu, 03 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/nomad-homelab/</guid><description>&lt;h1 id="nomad-homelab"&gt;Nomad Homelab&lt;/h1&gt;
&lt;p&gt;I use kubernetes at work as a developer and as a maintainer. This includes
OpenShift and k3s. For big org&amp;rsquo;s or teams this is great but I am starting
to come around to the idea that it&amp;rsquo;s too much for us. Perhaps Nomad
is a better more easy to administer solution.&lt;/p&gt;
&lt;p&gt;Things I am interested in about Nomad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;qemu&lt;/code&gt; driver and &lt;code&gt;docker&lt;/code&gt; driver&lt;/li&gt;
&lt;li&gt;HCL (we use HCL for packer and terraform already)&lt;/li&gt;
&lt;li&gt;Simpler, single binary setup&lt;/li&gt;
&lt;li&gt;Native integration into Vault&lt;/li&gt;
&lt;li&gt;Service discovery in-built (or can use Consul)&lt;/li&gt;
&lt;li&gt;Great UI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Things I&amp;rsquo;ll miss:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Terminal interface like k9s (thouse &lt;code&gt;damon&lt;/code&gt; exists as a Nomad version)&lt;/li&gt;
&lt;li&gt;K8s ecosystem&lt;/li&gt;
&lt;li&gt;Much less internet documentation&lt;/li&gt;
&lt;li&gt;More work to get some apps going (compared to the plethora of helm charts out there)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I worry about (for org, not pers use):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enterprise paywall&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Over this holidays I will be bringing up my own cluster to play
around with.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#hashicorp #kubernetes #nomad #infra
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Workout finishers</title><link>https://danielms.site/zet/2022/workout-finishers/</link><pubDate>Thu, 03 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/workout-finishers/</guid><description>&lt;h1 id="workout-finishers"&gt;Workout finishers&lt;/h1&gt;
&lt;p&gt;Some cheeky workout finishers:&lt;/p&gt;
&lt;h2 id="strength-w-cardio"&gt;Strength w/ Cardio&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# repeat 3-4 times&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# choose your adventure:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 12, 16 or 20KG buyers choice&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;8&lt;/span&gt; static lunges &lt;span class="o"&gt;(&lt;/span&gt;each side&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;5&lt;/span&gt; clean and press &lt;span class="o"&gt;(&lt;/span&gt;each side&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;12&lt;/span&gt; swings
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rest 1 min&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3 rounds&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;100KG sled push @ 15m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;5&lt;/span&gt; 24&lt;span class="s2"&gt;&amp;#34; box jump
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;5 Turkish Getup @ 5kn ea side
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;# no rest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="strength"&gt;Strength&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# repeat 3-4 times&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;12&lt;/span&gt; push-ups &lt;span class="o"&gt;(&lt;/span&gt;banded&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;20sec hollow hold
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;12&lt;/span&gt; single-leg calf raise @ 32KG KB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rest 1 min&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="cardio"&gt;Cardio&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# assault bike&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;3&lt;/span&gt; x 15s on / 15s off
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1min pedal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# repeat 2-3 times&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="rehab"&gt;Rehab&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;hips&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;6&lt;/span&gt; single-leg bulgarian split squat &lt;span class="o"&gt;(&lt;/span&gt;12kg&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;5&lt;/span&gt; arabesques &lt;span class="o"&gt;(&lt;/span&gt;6kg&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3 rounds &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6m each way, banded side walk with 10kg plate above head
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3 rounds (plate keeps you from leaning)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;20s copenhagen
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6e/side seated straight leg lift over kettlebell &lt;span class="o"&gt;(&lt;/span&gt;don&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t lean back&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6e/side matching side plank
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# circuit 3x through&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kettlebell #workout #fitness
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Format JSON on The CLI Easily</title><link>https://danielms.site/zet/2022/format-json-on-the-cli-easily/</link><pubDate>Tue, 01 Nov 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/format-json-on-the-cli-easily/</guid><description>&lt;h1 id="format-json-on-the-cli-easily"&gt;Format JSON on The CLI Easily&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;python -m json.tool &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This can then be chained to overwrite an existing file, effectively
formatting it.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;python -m json.tool &amp;lt;file&amp;gt; | tee /tmp/file.json &amp;amp;&amp;amp; cat /tmp/file.json &amp;gt; &amp;lt;file&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This gives the command multiple out&amp;rsquo;s without breaking the originating file.
Probably a far better way but this works.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jq&lt;/code&gt; would work but it isn&amp;rsquo;t installed on remote Unix systems whereas
&lt;code&gt;python -m json.tool&lt;/code&gt; always works.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#linux #json
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Nomad: basics</title><link>https://danielms.site/zet/2022/nomad-basics/</link><pubDate>Tue, 25 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/nomad-basics/</guid><description>&lt;h1 id="nomad-basics"&gt;Nomad: basics&lt;/h1&gt;
&lt;p&gt;Nomad is a scheduler/orchestrator for tasks. It can handle containers,
VMs, .NET and Java application through its scheduler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Highlights&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Single binary; distributed; leader election and replication for HA&lt;/li&gt;
&lt;li&gt;Plugin support, and GPU, FPGA and TPU out of the box support&lt;/li&gt;
&lt;li&gt;Multi-region federation&lt;/li&gt;
&lt;li&gt;Highly scalable (10k+ nodes in production in real-world examples)&lt;/li&gt;
&lt;li&gt;Native integration into Hashicorp products like Vault, Consul and Terraform&lt;/li&gt;
&lt;li&gt;Batch processing support (Kubernetes doesn&amp;rsquo;t do this well)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Versus Kubernetes&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kubernetes is an orchestration system for containers originally designed by Google, now governed by the Cloud Native Computing Foundation (CNCF) and developed by Google, Red Hat, and many others. Kubernetes and Nomad support similar core use cases for application deployment and management, but they differ in a few key ways. Kubernetes aims to provide all the features needed to run Linux container-based applications including cluster management, scheduling, service discovery, monitoring, secrets management and more. Nomad only aims to focus on cluster management and scheduling and is designed with the Unix philosophy of having a small scope while composing with tools like Consul for service discovery/service mesh and Vault for secret management.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://developer.hashicorp.com/nomad/docs/nomad-vs-kubernetes"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Installing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ubuntu:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -fsSL https://apt.releases.hashicorp.com/gpg &lt;span class="p"&gt;|&lt;/span&gt; sudo apt-key add -
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-add-repository &lt;span class="s2"&gt;&amp;#34;deb [arch=amd64] https://apt.releases.hashicorp.com &lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;lsb_release -cs&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; main&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo apt-get install nomad
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# grab all Hashicorp tools if needed with&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# sudo apt-get install packer terraform consul vault nomad&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Definitions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Cluster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;agent&lt;/code&gt; - An agent is a Nomad process running in server or client mode. These
are the building blocks of a Nomad cluster.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dev agent&lt;/code&gt; - Developer agent with defaults for running experiments or a
dev cluster. It does not persist any state to disk.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;server&lt;/code&gt; - Server mode agents are the &amp;ldquo;Control Plane&amp;rdquo; for the cluster. There is
a cluster of servers per &lt;em&gt;region&lt;/em&gt; and they manage all the jobs and clients, run
evaluations, and create the task allocations.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;leader&lt;/code&gt; - This is the server which performs the majority of the cluster management.
It is in charge of applying plans, deriving vault tokens for workloads and
administering the cluster state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;follower&lt;/code&gt; - Followers submit plans to the leader to provide more scheduling
capacity to the cluster. The &lt;code&gt;leader&lt;/code&gt; executes plans from the followers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;client&lt;/code&gt; - An agent running in client mode. These agents watch for any work
and execute tasks. Client connections are multiplexed to servers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nomad Objects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;job&lt;/code&gt; - Defines one or more task groups which contain one or more tasks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;job spec&lt;/code&gt; - Schema definition for a Nomad job. Describes the type, tasks
and resources required to run the job.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;task group&lt;/code&gt; - A set of tasks which must be run together. Example, a web server
may require a log shipping co-process is always running as well. A task group ensures
that these two tasks are always run together.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;task&lt;/code&gt; - The smallest unit of work in Nomad. These are executed by &lt;code&gt;task drivers&lt;/code&gt;
which allow Nomad to be flexible in the type of tasks it supports.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;allocation&lt;/code&gt; - A mapping between a task group and a client node. A job can have
hundreds or thousands of &lt;code&gt;task groups&lt;/code&gt; which means an equivalent number of &lt;code&gt;allocations&lt;/code&gt;
must be mapped to client machines. Allocations are created from scheduling decisions
known as an &lt;code&gt;evaluation&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evaluation&lt;/code&gt; - The way in which Nomad makes scheduling decisions. When the state changes
a new evaluation is triggered to determine the next action.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scheduling:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bin packing&lt;/code&gt; - This is how Nomad maximises allocations on a device.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spread scheduling&lt;/code&gt; - The opposite of &lt;code&gt;bin packing&lt;/code&gt;; this attempts to
evenly spread work across all the machines in the fleet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Starting Nomad&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Nomad relies upon agent; an agent must be on every machine in the cluster. The
server is responsible for managing the cluster and all other agents should be in
client mode. Clients register the machine, conduct heart beats and run tasks
when assigned from servers.&lt;/p&gt;
&lt;p&gt;In development:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo nomad agent -dev -bind 0.0.0.0 -log-level INFO&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#hashicorp #nomad
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Andor: finally a great disney spin off</title><link>https://danielms.site/zet/2022/andor-finally-a-great-disney-spin-off/</link><pubDate>Sat, 22 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/andor-finally-a-great-disney-spin-off/</guid><description>&lt;h1 id="andor-finally-a-great-disney-spin-off"&gt;Andor: finally a great disney spin off&lt;/h1&gt;
&lt;p&gt;As a Star Wars fan who hated the Mandalorian and all the other crap
Disney+ money grabs, I am pretty happy with Andor.&lt;/p&gt;
&lt;p&gt;The plot is interesting, Cassian&amp;rsquo;s character is realistic and gritty.
Diego Luna does a great job playing him unlike the half-arsed acting in
the other spin-offs. That villain woman from Obiwan was so unbelievable
and over the top that it totally ruined it for me.&lt;/p&gt;
&lt;p&gt;Star Wars should focus more on these plot driven, unrelated to to the typical
story line shows. I mean, c&amp;rsquo;mon Luke Skywalker in the Mandalorian was just.. bad!
Also, the Mandalorian literally had no storyline. Just crap.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>kubernetes: connection to the server localhost:8080 error</title><link>https://danielms.site/zet/2022/kubernetes-connection-to-the-server-localhost8080-error/</link><pubDate>Thu, 20 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/kubernetes-connection-to-the-server-localhost8080-error/</guid><description>&lt;h1 id="kubernetes-connection-to-the-server-localhost8080-error"&gt;kubernetes: connection to the server localhost:8080 error&lt;/h1&gt;
&lt;p&gt;This annoying error is the result of a bad &lt;code&gt;$KUBECONFIG&lt;/code&gt; file/ reference.
To fix this you will need to get a new copy from the server.&lt;/p&gt;
&lt;p&gt;For k3s this will mean scp&amp;rsquo;ing &lt;code&gt;/etc/rancher/k3s/k3s.yaml&lt;/code&gt; to your client
and then changing the server IP from &lt;code&gt;127.0.0.1&lt;/code&gt; to the actual IP of the
remote.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;oc&lt;/code&gt; helpfully does all this for you but &lt;code&gt;k3s&lt;/code&gt; does not.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kubernetes #troubleshooting
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>goland: highlight error without mouse hover</title><link>https://danielms.site/zet/2022/goland-highlight-error-without-mouse-hover/</link><pubDate>Mon, 17 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/goland-highlight-error-without-mouse-hover/</guid><description>&lt;h1 id="goland-highlight-error-without-mouse-hover"&gt;goland: highlight error without mouse hover&lt;/h1&gt;
&lt;p&gt;In Goland (and probably all intellij products; keymap dependant) to display
the squiggly underline tooltip for a problem/error all you need is Alt+t&lt;/p&gt;
&lt;p&gt;Couple this with displaying a function/methods comments (Shift+k) and I
now rarely need to use my mouse at all.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; #golang #productivity #intellij
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>create cli reminder script</title><link>https://danielms.site/zet/2022/create-cli-reminder-script/</link><pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/create-cli-reminder-script/</guid><description>&lt;h1 id="create-cli-reminder-script"&gt;create cli reminder script&lt;/h1&gt;
&lt;p&gt;I need to create a simple CLI interface that creates email reminders
for things I need to do.&lt;/p&gt;
&lt;p&gt;Use Bonzai to do this and send messages with a datetime to the cloud&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#project #bonzai #go
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>go parsing seconds into time.Duration</title><link>https://danielms.site/zet/2022/go-parsing-seconds-into-time.duration/</link><pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/go-parsing-seconds-into-time.duration/</guid><description>&lt;h1 id="go-parsing-seconds-into-timeduration"&gt;go parsing seconds into time.Duration&lt;/h1&gt;
&lt;p&gt;If you have time in seconds and need to turn that into a
duration such as &lt;code&gt;2h30m&lt;/code&gt; use &lt;code&gt;time.ParseDuration&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="cp"&gt;//go.dev/play/p/6C_eVWyTeAD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;86900&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseDuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;%ds&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 24h8m20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Hours&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 24.138888888888889&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 24h8m20s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you can do operations on this as a &lt;code&gt;time&lt;/code&gt; object or cast it
to an float using &lt;code&gt;h.Hours()&lt;/code&gt; and do math operations.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #time
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>I need to track contract time</title><link>https://danielms.site/zet/2022/i-need-to-track-contract-time/</link><pubDate>Sun, 16 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/i-need-to-track-contract-time/</guid><description>&lt;h1 id="i-need-to-track-contract-time"&gt;I need to track contract time&lt;/h1&gt;
&lt;p&gt;In a given contract year I am entitled to work 1920 hours. I cannot go over but
I can go under. Going under is money lost for me - my contract owner gets the
total allotment of their share.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve done the math and over the last two and I&amp;rsquo;ll be pulling up short
by ~300 hours (probably 295). This equates to several tens of thousands
of dollars in income. Income I&amp;rsquo;ve left on the table due to my own ignorance
of my worked hours.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m lazy - literally the epitome of Just-In-Time everything. Learning,
chores, presents for family any topic you can think of. So whilst I
track every hour I do using an app on my phone I rarely do any tracking
long term. Meaning only today (with 32 working days left on my current contract)
am I figuring out how far behind I am.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;1920&lt;/span&gt; / &lt;span class="m"&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="m"&gt;240&lt;/span&gt; &lt;span class="c1"&gt;# days at 8 hrs per day to complete contract hours at zero&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So what can I do about this? Build a tool to prompt me every day of course.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;contract-timetracker&lt;/code&gt; (terrible placeholder name). It should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;report hours remaining on contract&lt;/li&gt;
&lt;li&gt;report mean hours per day required to achieve zero balance&lt;/li&gt;
&lt;li&gt;calculate number of possible working days left in contract - taking public holidays in to account&lt;/li&gt;
&lt;li&gt;produce a end of month report with total hours worked and number of days (possibly with each day&amp;rsquo;s
total)&lt;/li&gt;
&lt;li&gt;(optional) write to my contract timesheet excel file with the datetimes and email to myself&lt;/li&gt;
&lt;li&gt;run using OpenFaaS on a cronjob so I don&amp;rsquo;t need to manage the service&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Considering how JIT I am this seems like a decent 80% solution for keeping me on track for my
next contract which starts in November. I got a pay rise and want to capitalise on it and not
leave money I&amp;rsquo;m entitled to on the table.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#project #contract #openfaas
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Contract time countdown and hours remaining</title><link>https://danielms.site/zet/2022/contract-time-countdown-and-hours-remaining/</link><pubDate>Tue, 11 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/contract-time-countdown-and-hours-remaining/</guid><description>&lt;h1 id="contract-time-countdown-and-hours-remaining"&gt;Contract time countdown and hours remaining&lt;/h1&gt;
&lt;p&gt;I am useless and tracking my remaining hours for my contract year. Typically,
I do my timesheets at the end of the month which isn&amp;rsquo;t helpful for forecasting
how many hours I need to do over the course of the month to keep pace with my
contract. I would also like a way to see how many hours I could achieve in a month
and/or how many I need to do per day to achieve a set number, such as 160.&lt;/p&gt;
&lt;p&gt;Example 1: Remaining hours in contract&lt;/p&gt;
&lt;p&gt;This would calculate how many hours I have done since contract start date
until today and then output how many I have remaining&lt;/p&gt;
&lt;p&gt;Example 2: Given a target output how I have remaining for the month&lt;/p&gt;
&lt;p&gt;Subtract hours done from target each day, returning a mean for each working
day remaining in the month.&lt;/p&gt;
&lt;p&gt;I think this could be achieved via my current timesheet accounting app. I
can export the database each day and upload it to the cloud. A cron could
fire and do all the calculations. A daily email, or notification could be
sent with the figures presented.&lt;/p&gt;
&lt;p&gt;Extra bonus: apply the timesheet data into my official excell spreadsheet
which I need to submit to management each month. This is a timesink I could
easily automate.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#accounting #project
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>pop-os shell over i3</title><link>https://danielms.site/zet/2022/pop-os-shell-over-i3/</link><pubDate>Tue, 11 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/pop-os-shell-over-i3/</guid><description>&lt;h1 id="pop-os-shell-over-i3"&gt;pop-os shell over i3&lt;/h1&gt;
&lt;p&gt;I have been an i3 user for maybe two years now. It is a great experience,
or I should say, the good parts make for a great experience. The bad parts
make for a terrible experience (for me).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not a &lt;em&gt;ricer&lt;/em&gt;, I don&amp;rsquo;t care about &lt;em&gt;gaps&lt;/em&gt;. I just want to switch between
panes and windows easily. In i3, I put golang in window 6, webstorm in 5 and
pycharm in 4. All my browsers go in 2 (I usually run two browsers at the
same time - Vivaldi and Opera). Email in 10, VirtualBox in 9, and so on.&lt;/p&gt;
&lt;p&gt;This works &lt;strong&gt;so&lt;/strong&gt; much better than Gnome or whatever&amp;rsquo;s tabbing experience. Yes,
I can use workspaces but its really not the same.&lt;/p&gt;
&lt;p&gt;In saying all this, there are some things I don&amp;rsquo;t like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;learning all the esoteric short keys&lt;/li&gt;
&lt;li&gt;it needs a lot of customisation&lt;/li&gt;
&lt;li&gt;sometimes things don&amp;rsquo;t work well&lt;/li&gt;
&lt;li&gt;resizing things can be a pain&lt;/li&gt;
&lt;li&gt;I use a mouse 30-40% of the time so it needs to work&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whereas, I like Gnome&amp;rsquo;s &amp;ldquo;it just works&amp;rdquo; approach. Running some applications
work much better in a Gnome environment, for instance, OBS.&lt;/p&gt;
&lt;p&gt;Today I found &lt;a href="https://github.com/pop-os/shell"&gt;pop-os shell&lt;/a&gt;. It seems
like a great alternative. At work, I use Gnome because I need to work,
not fiddle with i3 customizations - the juice ain&amp;rsquo;t worth the squeeze.
So, I installed it pop-os/shell at work and instantly fell in love with it.
It&amp;rsquo;s not perfect but it basically covers my needs - I don&amp;rsquo;t think I need
all that i3 provides, &lt;code&gt;shell&lt;/code&gt; is enough.&lt;/p&gt;
&lt;p&gt;The only big thing that sucks and its not &lt;code&gt;shell&lt;/code&gt; its stupid as Windows.
At home I&amp;rsquo;m a linux native but at work I recently had to switch from a
linux workstation to a beefy windows box where I use VMWare to run my
fedora VM. The thing that fucking sucks is that windows hardcodes the
Super/Win key shortcuts, such as WIN+L (and WIN+G for XBOX crap). This
totally breaks &lt;code&gt;shell&lt;/code&gt;&amp;rsquo;s vim bindings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: After nearly a month of running pop-os shell, I really love it.
Its super simple and meets my needs. I do miss the switching between workspaces
using the i3 number system but other than that, everything else &lt;em&gt;Just Works&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#linux #i3 #tiling #windows
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>SQLite select between dates</title><link>https://danielms.site/zet/2022/sqlite-select-between-dates/</link><pubDate>Tue, 11 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/sqlite-select-between-dates/</guid><description>&lt;h1 id="sqlite-select-between-dates"&gt;SQLite select between dates&lt;/h1&gt;
&lt;p&gt;SQLite has spartan types and today I learnt how to select between a date
range.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;JULIANDAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JULIANDAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;between&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2022-09-01&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2022-09-30&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Outputs&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .mode column&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .headers on&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;date1 date2 amount diff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;---------------- ---------------- ---------- ----------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2022-09-02 08:25 2022-09-02 17:11 1008.17 31560.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2022-09-05 08:22 2022-09-05 17:05 1002.42 31380.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2022-09-06 08:56 2022-09-06 16:30 870.17 27240.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2022-09-07 08:34 2022-09-07 17:05 979.42 30660.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using &lt;code&gt;JULIANDAY&lt;/code&gt; and multiplying by &lt;code&gt;86400&lt;/code&gt; returns the difference in
seconds between &lt;code&gt;date1&lt;/code&gt; and &lt;code&gt;date2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Last seven days&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;JULIANDAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;JULIANDAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-7 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Since start of the month&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;JULIANDAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;JULIANDAY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;86400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;start of month&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;localtime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#sql
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Hugo sort json</title><link>https://danielms.site/zet/2022/hugo-sort-json/</link><pubDate>Sun, 09 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/hugo-sort-json/</guid><description>&lt;h1 id="hugo-sort-json"&gt;Hugo sort json&lt;/h1&gt;
&lt;p&gt;Sorting JSON in hugo is quite easy.&lt;/p&gt;
&lt;p&gt;Take this JSON object&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-JSON" data-lang="JSON"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;three&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;7/02/2022&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;one&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;5/01/2022&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;two&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;7/01/2022&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To sort this by date in Hugo you can use &lt;code&gt;sort&lt;/code&gt;. In the HTML page to sort by &lt;code&gt;desc&lt;/code&gt;;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{ $items := getJSON &amp;#34;sort.json&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{ range $item := sort $items &amp;#34;date&amp;#34; &amp;#34;desc&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ $item.name}}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{ $item.date }}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#hugo #json #web
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Sept 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-sept-2022/</link><pubDate>Fri, 07 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-sept-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s been two months since I last wrote a retrospective. This is why.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="work"&gt;Work&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m really enjoying my day job.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The team&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The work is tough at times but the team is excellent. None of
us are rockstar engineer&amp;rsquo;s, but we all muck in, learn what we need and get it done.
It&amp;rsquo;s a long way from my time in the special forces but for a development team it&amp;rsquo;s pretty
darn good. I&amp;rsquo;m proud of them and what we&amp;rsquo;re achieving with all thats stacked against us.
And, from my experience, it&amp;rsquo;s in &lt;em&gt;battle&lt;/em&gt; or in times of hardship that you really become a
team, and also become friends. Smooth sailing never made a skilled sailor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The work&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to dox my employer. But the work we do makes a difference. You likely can&amp;rsquo;t
see it, and if we do our job right, never will but it matters. At the micro, I do stuff
that any developer does daily; code, debug, fix, review, and deploy. Except that in this
job I have numerous business and environmental constraints that 99% of other engineers
would never come across.&lt;/p&gt;
&lt;p&gt;It makes work slow, error-prone and brittle at times.
Local development is hard. Sometimes impossible -
&lt;a href="https://www.youtube.com/watch?v=vu2NK5REvWM"&gt;we&amp;rsquo;ll do it live&lt;/a&gt; is a pretty normal!
This sounds like a whinge but it&amp;rsquo;s actually a binding force for us in our mission.
Embracing the suck has made us invent ingenious ways to overcome some of these
constraints and exposed me to so much more than other developers in my position.&lt;/p&gt;
&lt;h2 id="life"&gt;Life&lt;/h2&gt;
&lt;p&gt;I only have so much time each day to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Work my day job&lt;/li&gt;
&lt;li&gt;Be present with the family&lt;/li&gt;
&lt;li&gt;Work on after hours pursuits&lt;/li&gt;
&lt;li&gt;Exercise&lt;/li&gt;
&lt;li&gt;Chores&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Something has to give but also energy has to be dispersed across all.&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;p&gt;Mudmap still lives but I&amp;rsquo;ve given it less attention than it deserves for the reasons
stated above. I&amp;rsquo;d love to do more, and be able to build the things users ask for.
Maintaining it is an energy drain that isn&amp;rsquo;t refilled by my interest in working on it.
Though at times, I have had bursts of inspiration because of its potential, and its
that potential which prevents me from shutting it down.&lt;/p&gt;
&lt;p&gt;Possibly the biggest gripe is the difficulty in getting everything to install
correctly on each device. I&amp;rsquo;ve yet to discover a repeatable or diagnosable reason
as to why it works for some but not others. I think this is a problem many similar
bring-your-own-hardware platforms have to deal with. Each time I get a customer email
stating they couldn&amp;rsquo;t install correctly my heart sinks a little. It&amp;rsquo;s also hard not to
&lt;em&gt;be your code&lt;/em&gt; and blame yourself (a distinct possibility) for the issues. Energy sinkhole.&lt;/p&gt;
&lt;h2 id="pursuit-of-joy"&gt;Pursuit of joy&lt;/h2&gt;
&lt;p&gt;Like all developers, I love creating and trying out new toys and techniques.
Building a new application is great fun and is a great way to procrastinate. It
is also a great way to try new things and bring them in to your day job, or gain
better understanding of how something in your day job works. Lots of my colleagues
don&amp;rsquo;t do this and seem to learn everything on the job without any additional work.
That ain&amp;rsquo;t me, never has been. In the army, I&amp;rsquo;d go home and read the manual so that
things sunk in. I have to do that in tech too - I&amp;rsquo;m a hard worker not a smart guy.
My school aptitude tests put me in the &amp;ldquo;labour hire&amp;rdquo; job prospects category not
the &amp;ldquo;attend university&amp;rdquo; one. So, sometimes I get engrossed in learning something
at the detriment of other things like Mudmap.&lt;/p&gt;
&lt;p&gt;On the flip side, my after hours learning has earned me a reputation as the
Kubernetes guy at work. I&amp;rsquo;m also the Go guy now too. I can now say I&amp;rsquo;ve a working
familiarity with gRPC and NATS as well. Doing these things helped me make several
interview rounds (and get offers) in the last three months for 20-40% pay rises
without having to do a single LeetCode session or other Code riddle bullshit. That
being said, I acknowledge my CompSci fundamentals are a limiting factor so I&amp;rsquo;m now
reading up on those things to round out my knowledge.&lt;/p&gt;
&lt;p&gt;Lots of words to say; I enjoy my craft and like to jump around at the detriment of
long-term projects such as Mudmap.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;I love how well &lt;a href="https://mtlynch.io/retrospectives/"&gt;mtlynch&lt;/a&gt; and
&lt;a href="https://www.coryzue.com/writing/"&gt;czue&lt;/a&gt; write their retro&amp;rsquo;s. I took most
of my inspiration from Michael&amp;rsquo;s, but I think pigeonholing myself into
something that works for him is a misstep.&lt;/p&gt;
&lt;p&gt;For instance, Michael is doing a fantastic job of documenting his many
projects but mostly his successful business, &lt;a href="https://tinypilotkvm.com"&gt;TinyPilot&lt;/a&gt;.
For me though, I work 40 hours a week in a day job I can&amp;rsquo;t talk much about
and approximately 10-20 hours outside of that broken into 1-2 hour chunks
over nights and weekends. Setting myself ambitious goals was actually causing
me some anxiety, especially if my month&amp;rsquo;s work took a big hit from external
factors.&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;m planning to do now is just be more &lt;em&gt;retrospective&lt;/em&gt; about what I
did rather than only talk about how well I did on &lt;em&gt;x&lt;/em&gt; goal. At the end
of the day, I write on here for me not an audience. So my writings should
reflect that and help guide future me when looking back.&lt;/p&gt;
&lt;p&gt;Does this mean I&amp;rsquo;ll forsake any thought of setting goals for each month?
No but I&amp;rsquo;m not going to be dogmatic about how I layout the retrospective
and instead let it flow. Some months I might only do 10 hours on Mudmap but do 30
hours on something that teaches me a hell of a lot about &lt;em&gt;&amp;lt;insert tech&amp;gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="goals"&gt;Goals&lt;/h2&gt;
&lt;p&gt;Okay, I&amp;rsquo;m setting one goal for next month.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Write Octobers retrospective.&lt;/strong&gt; Time to get back on the retro horse.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-09-01"
 let end_date = "2022-09-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-09-01"
 let end_date = "2022-09-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>September Retro</title><link>https://danielms.site/zet/2022/september-retro/</link><pubDate>Fri, 07 Oct 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/september-retro/</guid><description>&lt;h1 id="september-retro"&gt;September Retro&lt;/h1&gt;
&lt;p&gt;It&amp;rsquo;s been two months since I last wrote a retrospective. This is why.&lt;/p&gt;
&lt;h2 id="work"&gt;Work&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m really enjoying my day job.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The team&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The work is tough at times but the team is excellent. None of
us are rockstar engineer&amp;rsquo;s but we all muck in, learn what we need and get it done.
It&amp;rsquo;s a long way from my time in the special forces but for a development team it&amp;rsquo;s pretty
darn good. I&amp;rsquo;m proud of them and what we&amp;rsquo;re achieving with all thats stacked against us.
And, from my experience, its in &lt;em&gt;battle&lt;/em&gt; or in times of hardship that you really become a
team, and also become friends. Smooth sailing never made a skilled sailor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The work&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not going to dox my employer. But the work we do makes a difference. You likely can&amp;rsquo;t
see it, and if we do our job right, never will but it matters. At the micro, I do stuff
that any developer does daily; code, debug, fix, review, and deploy. Except that in this
job I have numerous business and environmental constraints that 99% of other engineers
would never come across.&lt;/p&gt;
&lt;p&gt;It makes work slow, error prone and brittle at times.
Local development is hard. Sometimes impossible -
&lt;a href="https://www.youtube.com/watch?v=vu2NK5REvWM"&gt;we&amp;rsquo;ll do it live&lt;/a&gt; is a pretty normal!
This sounds like a whinge but it&amp;rsquo;s actually a binding force for us in our mission.
Embracing the suck has made us invent ingenious ways to overcome some of these
constraints and exposed me to so much more than other developers in my position.&lt;/p&gt;
&lt;h2 id="life"&gt;Life&lt;/h2&gt;
&lt;p&gt;I only have so much time each day to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Work my day job&lt;/li&gt;
&lt;li&gt;Be present with the family&lt;/li&gt;
&lt;li&gt;Work on after hours pursuits&lt;/li&gt;
&lt;li&gt;Exercise&lt;/li&gt;
&lt;li&gt;Chores&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Something has to give but also energy has to be dispersed across all.&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;p&gt;Mudmap still lives but I&amp;rsquo;ve given it less attention than it deserves for the reasons
stated above. I&amp;rsquo;d love to do more, and be able to build the things users ask for.
Maintaining it is an energy drain that isn&amp;rsquo;t refilled by my interest in working on it.
Though at times, I have had bursts of inspiration because of its potential, and its
that potential which prevents me from shutting it down.&lt;/p&gt;
&lt;p&gt;Possibly the biggest gripe is the difficulty in getting everything to install
correctly on each device. I&amp;rsquo;ve yet to discover a repeatable or diagnosable reason
as to why it works for some but not others. I think this is a problem many similar
bring-your-own-hardware platforms have to deal with. Each time I get a customer email
stating they couldn&amp;rsquo;t install correctly my heart sinks a little. It&amp;rsquo;s also hard not to
&lt;em&gt;be your code&lt;/em&gt; and blame yourself (a distinct possibility) for the issues. Energy sinkhole.&lt;/p&gt;
&lt;h2 id="pursuit-of-joy"&gt;Pursuit of joy&lt;/h2&gt;
&lt;p&gt;Like all developers, I love creating and trying out new toys and techniques.
Building a new application is great fun and is a great way to procrastinate. It
is also a great way to try new things and bring them in to your day job, or gain
better understanding of how something in your day job works. Lots of my colleagues
don&amp;rsquo;t do this and seem to learn everything on the job without any additional work.
That ain&amp;rsquo;t me, never has been. In the army, I&amp;rsquo;d go home and read the manual so that
things sunk in. I have to do that in tech too - I&amp;rsquo;m a hard worker not a smart guy.
My school aptitude tests put me in the &amp;ldquo;labour hire&amp;rdquo; job prospects category not
the &amp;ldquo;attend university&amp;rdquo; one. So, sometimes I get engrossed in learning something
at the detriment of other things like Mudmap.&lt;/p&gt;
&lt;p&gt;On the flip side, my after hours learning has earned me a reputation as the
Kubernetes guy at work. I&amp;rsquo;m also the Go guy now too. I can now say I&amp;rsquo;ve a working
familiarity with gRPC and NATS as well. Doing these things helped me make several
interview rounds (and get offers) in the last three months for 20-40% pay rises
without having to do a single LeetCode session or other Code riddle bullshit. That
being said, I acknowledge my CompSci fundamentals are a limiting factor so I&amp;rsquo;m now
reading up on those things to round out my knowledge.&lt;/p&gt;
&lt;p&gt;Lots of words to say; I enjoy my craft and like to jump around at the detriment of
long-term projects such as Mudmap.&lt;/p&gt;
&lt;h2 id="whats-next"&gt;What&amp;rsquo;s next&lt;/h2&gt;
&lt;p&gt;I love how well &lt;a href="https://mtlynch.io/retrospectives/"&gt;mtlynch&lt;/a&gt; and
&lt;a href="https://www.coryzue.com/writing/"&gt;czue&lt;/a&gt; write their retro&amp;rsquo;s. I took most
of my inspiration from Michael&amp;rsquo;s but I think pigeon holing myself into
something that works for him is a misstep.&lt;/p&gt;
&lt;p&gt;For instance, Michael is doing a fantastic job of documenting his many
projects but mostly his successful business, &lt;a href="https://tinypilotkvm.com"&gt;TinyPilot&lt;/a&gt;.
For me though, I work 40 hours a week in a day job I can&amp;rsquo;t talk much about
and approximately 10-20 hours outside of that broken into 1-2 hour chunks
over nights and weekends. Setting myself ambitious goals was actually causing
me some anxiety, especially if my month&amp;rsquo;s work took a big hit from external
factors.&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;m planning to do now is just be more &lt;em&gt;retrospective&lt;/em&gt; about what I
did rather than only talk about how well I did on &lt;em&gt;x&lt;/em&gt; goal. At the end
of the day, I write on here for me not an audience. So my writings should
reflect that and help guide future me when looking back.&lt;/p&gt;
&lt;p&gt;Does this mean I&amp;rsquo;ll forsake any thought of setting goals for each month?
No but I&amp;rsquo;m not going to be dogmatic about how I layout the retrospective
and instead let it flow. Some months I might only do 10 hours on Mudmap but do 30
hours on something that teaches me a hell of a lot about &lt;em&gt;&lt;!-- raw HTML omitted --&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="goals"&gt;Goals&lt;/h2&gt;
&lt;p&gt;Okay, I&amp;rsquo;m setting one goal for next month.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Write a Octobers retrospective.&lt;/strong&gt; Time to get back on the retro horse.&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Job tasks I do these days</title><link>https://danielms.site/zet/2022/job-tasks-i-do-these-days/</link><pubDate>Mon, 26 Sep 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/job-tasks-i-do-these-days/</guid><description>&lt;h1 id="job-tasks-i-do-these-days"&gt;Job tasks I do these days&lt;/h1&gt;
&lt;p&gt;A list of all the types of things I am doing in my day job, and in my own time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CI/CD
&lt;ul&gt;
&lt;li&gt;gitlab pipelines&lt;/li&gt;
&lt;li&gt;GitLab API access to cross system boundaries with runners in different tenancies (no access
outside certain operations)&lt;/li&gt;
&lt;li&gt;Pipeline maintenance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes
&lt;ul&gt;
&lt;li&gt;Building applications for kubernetes
&lt;ul&gt;
&lt;li&gt;Using non-greedy operations because scaling horizontally is better&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Administering production and staging clusters&lt;/li&gt;
&lt;li&gt;Administering user access and control for the team&lt;/li&gt;
&lt;li&gt;Fixing bad deployments and broken helm charts&lt;/li&gt;
&lt;li&gt;Attempting to transition the environment over to ArgoCD (we have little oversight when things
drift)&lt;/li&gt;
&lt;li&gt;Stood up and introduced k3s to the team
&lt;ul&gt;
&lt;li&gt;we now run k3s locally with verified fakes for our third party API&amp;rsquo;s&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Bash Scripting
&lt;ul&gt;
&lt;li&gt;I use Bash for so many things and feel like I&amp;rsquo;m getting decent at it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Python
&lt;ul&gt;
&lt;li&gt;I code review major deployments&lt;/li&gt;
&lt;li&gt;I write python most days using Rabbit and websocket backends&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Golang
&lt;ul&gt;
&lt;li&gt;Fairly competent having written a large code base entirely in it&lt;/li&gt;
&lt;li&gt;Reach for it instead of python for CLI or automations which bash isn&amp;rsquo;t suitable for&lt;/li&gt;
&lt;li&gt;Experienced somewhat in writing gRPC code for Go&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#checkin #career #skills
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Ballbag quote of the day</title><link>https://danielms.site/zet/2022/ballbag-quote-of-the-day/</link><pubDate>Wed, 21 Sep 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/ballbag-quote-of-the-day/</guid><description>&lt;h1 id="ballbag-quote-of-the-day"&gt;Ballbag quote of the day&lt;/h1&gt;
&lt;p&gt;A question posted on a Slack I am in:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Question - on behalf of one of my students - does working in IT for a defence company (think one of the defence contractors out Williamtown) look “bad” on your resume in the long term considering defence is a “touchy” subject ? I&amp;rsquo;ve never heard of such a thing, but have had it suggested to me. What are the thoughts of the group ?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This response absolutely got me raging.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Interesting conversation. There&amp;rsquo;s no one answer and it depends on the person and the specifics of the defense role. I&amp;rsquo;d be curious about what they did and why they left and ethics would be part of that.
It&amp;rsquo;s not a blanket no - for sure. People can change. Some ex-bankers even turn out ok. BUT I am surprised at the number of people here that seem to think what a person can offer is always far more important than the type of human they are. And often (not exclusively) that&amp;rsquo;s reflected in choices they&amp;rsquo;ve made including who they are willing to work for. (edited)
Short answer - it should probably be a flag to learn more about the individual and their ethics&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What an absolute dickhead way of thinking about someones choice of employer.
This kind of idiot probably thinks working for Google or Facebook is &lt;em&gt;ethical&lt;/em&gt;.
I&amp;rsquo;m literally at a loss that someone could think this way, honestly I&amp;rsquo;d &lt;strong&gt;hate&lt;/strong&gt;
to work with this kind of moron. He&amp;rsquo;d hate the fact that I&amp;rsquo;ve fought in two wars
for my country and also worked in defence industry. I can only imagine what
he thinks I am based on what this opinion.&lt;/p&gt;
&lt;p&gt;Honestly, I&amp;rsquo;m fuming and this kind of thinking makes you lose faith in
people as a collective.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-09-23</title><link>https://danielms.site/zet/2022/wgd-fri-2022-09-23/</link><pubDate>Mon, 19 Sep 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-09-23/</guid><description>&lt;h1 id="wgd-fri-2022-09-23"&gt;WGD Fri 2022-09-23&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Deployed Jellyfin in my house.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Can&amp;rsquo;t believe how good it is and why I didn&amp;rsquo;t do this sooner&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Refactoring Mudmap sql to use SQLc&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s a lot of work but so far is totaly worth it. Building new queries is so easy&lt;/li&gt;
&lt;li&gt;The only thing I don&amp;rsquo;t like is how it handles &lt;code&gt;enums&lt;/code&gt; in Postgres&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Spent all week at work refactoring a series of Gitlab pipelines&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nested &lt;code&gt;includes&lt;/code&gt; are not a lot of fun.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Started learning a bit more about gRPC and Protobuf&amp;rsquo;s for a project&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s a different way of thinking when you&amp;rsquo;ve mostly done REST using JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;p&gt;#wgd&lt;/p&gt;</description></item><item><title>docker buildkit breaks 'docker build -o'</title><link>https://danielms.site/zet/2022/docker-buildkit-breaks-docker-build--o/</link><pubDate>Tue, 13 Sep 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/docker-buildkit-breaks-docker-build--o/</guid><description>&lt;h1 id="docker-buildkit-breaks-docker-build--o"&gt;docker buildkit breaks &amp;lsquo;docker build -o&amp;rsquo;&lt;/h1&gt;
&lt;p&gt;I wasted a day only to discover that enabling &lt;code&gt;DOCKER_BUILDKIT=1&lt;/code&gt; will
break the &lt;code&gt;docker build -o &amp;lt;bla.tar.gz&amp;gt;...&lt;/code&gt;. Instead of creating a
tar file, it instead creates a directory! The only reason I figured this
out was by running it manually and seeing that it was a directory called
&lt;code&gt;bla.tar.gz&lt;/code&gt;. Debugging this when it was used in our runners was a nightmare.&lt;/p&gt;
&lt;p&gt;I turned off that feature entirely out of spite, rather than fix it. Besides,
our pipelines are doing well without buildkit.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #docker #spite
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>programmer quote of the day</title><link>https://danielms.site/zet/2022/programmer-quote-of-the-day/</link><pubDate>Sun, 04 Sep 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/programmer-quote-of-the-day/</guid><description>&lt;h1 id="programmer-quote-of-the-day"&gt;programmer quote of the day&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;You know they say economists know the price of everything and the value of nothing? Well
programmers know the benefits of everything and the trade-offs of nothing. - Rich Hickey&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This has to be the greatest programming quote I&amp;rsquo;ve ever seen.&lt;/p&gt;</description></item><item><title>self-hosted gitlab-runner queue slow</title><link>https://danielms.site/zet/2022/self-hosted-gitlab-runner-queue-slow/</link><pubDate>Sun, 04 Sep 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/self-hosted-gitlab-runner-queue-slow/</guid><description>&lt;h1 id="self-hosted-gitlab-runner-queue-slow"&gt;self-hosted gitlab-runner queue slow&lt;/h1&gt;
&lt;p&gt;Gitlab self-hosted runners seem to take a long time to pick up jobs
from the queue. In many cases its was over 3 minutes before the pipeline
would even start and then have lengthy pauses between stages.&lt;/p&gt;
&lt;p&gt;Changing two lines in the &lt;code&gt;/etc/gitlab-runner/config.toml&lt;/code&gt; file seems to
have fixed this issue.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;concurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;check_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://gitlab.com/gitlab-org/gitlab-runner/-/issues/4567"&gt;link&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also, as my runner is a docker-runner inside a container I could not
edit this file. The Dockerfile is locked down so I had to &lt;code&gt;docker cp&lt;/code&gt; the
file out of the container, edit it, and then &lt;code&gt;docker cp&lt;/code&gt; it back in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker cp &amp;lt;Container-ID&amp;gt;:/etc/gitlab-runner/config.toml .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vim config.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker cp config.toml &amp;lt;Container-ID&amp;gt;:/etc/gitlab-runner/config.toml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#cicd #gitlab #runners #docker
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>kubernetes ingress and metallb</title><link>https://danielms.site/zet/2022/kubernetes-ingress-and-metallb/</link><pubDate>Thu, 01 Sep 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/kubernetes-ingress-and-metallb/</guid><description>&lt;h1 id="kubernetes-ingress-and-metallb"&gt;kubernetes ingress and metallb&lt;/h1&gt;
&lt;p&gt;For my homelab, I&amp;rsquo;m teaching myself how to setup a bare metal cluster using
k3s, nginx-ingress and metallb. It&amp;rsquo;s show soo many holes in my knowledge
and understanding of k8s. When you work on established systems such as
OpenShift at your day job, you take for granted all the things it does for you.&lt;/p&gt;
&lt;h2 id="i-misunderstand-ingress-vs-services"&gt;I misunderstand Ingress vs Services&lt;/h2&gt;
&lt;p&gt;At work, we use Ingress and NodePorts to allow traffic to our applications
and I thought I understood it. But, now that I&amp;rsquo;m throwing Metallb into the
mix, I clearly don&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;My understanding at the minute&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a metallb static IP for my nginx-ingress&lt;/li&gt;
&lt;li&gt;Define a Service for my app&lt;/li&gt;
&lt;li&gt;Use the container port on the Ingress and set a host&lt;/li&gt;
&lt;li&gt;Create a DNS host override in pfSense&lt;/li&gt;
&lt;li&gt;Use the metallb static IP of the nginx-ingress service for all the hostnames&lt;/li&gt;
&lt;li&gt;&amp;lsquo;argocd.home.lab&lt;code&gt;and&lt;/code&gt;tekton.home.lab&lt;code&gt;map to&lt;/code&gt;192.168.20.199` and nginx will resolve it&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So far that works. It took me a long time to figure that out and this is just for
the LAN. Exposing applications later will be another thing I likely misunderstand.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kubernetes #homelab #til
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>get private ssl cert</title><link>https://danielms.site/zet/2022/get-private-ssl-cert/</link><pubDate>Mon, 29 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/get-private-ssl-cert/</guid><description>&lt;h1 id="get-private-ssl-cert"&gt;get private ssl cert&lt;/h1&gt;
&lt;p&gt;This is how I get private SSL certs which is really useful when using
private gitlab and argocd&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;openssl s_client -showcerts &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-servername gitlab.homelab.com &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-connect gitlab.homelab.com:443 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/dev/null 2&amp;gt;/dev/null &lt;span class="p"&gt;|&lt;/span&gt; sed -n -e &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;/BEGIN\ CERTIFICATE/,/END\ CERTIFICATE/ p&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; /tmp/sslkey.pem
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; #openssl #ssl #argocd
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>microservice learning task</title><link>https://danielms.site/zet/2022/microservice-learning-task/</link><pubDate>Fri, 26 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/microservice-learning-task/</guid><description>&lt;h1 id="microservice-learning-task"&gt;microservice learning task&lt;/h1&gt;
&lt;p&gt;I am building out my Proxmox server and local kubernetes cluster. I don&amp;rsquo;t
have a huge workload to put on either, nor a sophisticated arrangement. So,
I figured this is a good opportunity to replicate the type of work I do
in my day job. Unfortunately, despite working on a distributed microservice
I don&amp;rsquo;t get to work on all parts so I have some knowledge gaps - this should
remediate that.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;m thinking:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Weather God&lt;/strong&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;a simple weather service&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It should:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;weather now for location x&lt;/li&gt;
&lt;li&gt;weather historical for location x&lt;/li&gt;
&lt;li&gt;snow falls AU&lt;/li&gt;
&lt;li&gt;barometric&lt;/li&gt;
&lt;li&gt;weather chart&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a very simple application but could contain many small microservices,
and/or functions.&lt;/p&gt;
&lt;p&gt;Spitballing;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;temperature&lt;/li&gt;
&lt;li&gt;barometric&lt;/li&gt;
&lt;li&gt;chart generation&lt;/li&gt;
&lt;li&gt;database&lt;/li&gt;
&lt;li&gt;event bus&lt;/li&gt;
&lt;li&gt;user interface&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All deployed on to my local cluster&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#project #kubernetes #microservices #proxmox
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>TIL: bash flags</title><link>https://danielms.site/zet/2022/til-bash-flags/</link><pubDate>Fri, 26 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/til-bash-flags/</guid><description>&lt;h1 id="til-bash-flags"&gt;TIL: bash flags&lt;/h1&gt;
&lt;p&gt;TIL how to create flags/ optional arguments in bash scripts. Previously
I just used &amp;ldquo;${1} ${2}&amp;rdquo; etc.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$arg0&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="nv"&gt;$*&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flags&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; -gt &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-s&lt;span class="p"&gt;|&lt;/span&gt;--source&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; error &lt;span class="s2"&gt;&amp;#34;No source directory specified&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;SOURCE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; shift&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-d&lt;span class="p"&gt;|&lt;/span&gt;--destination&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; error &lt;span class="s2"&gt;&amp;#34;No destination specified&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;DESTINATION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; shift&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-c&lt;span class="p"&gt;|&lt;/span&gt;--credentials&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; error &lt;span class="s2"&gt;&amp;#34;No credentials specified&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; shift&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-b&lt;span class="p"&gt;|&lt;/span&gt;--bandwidth&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; error &lt;span class="s2"&gt;&amp;#34;No bandwidth specified&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;BANDWIDTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; shift&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-t&lt;span class="p"&gt;|&lt;/span&gt;--timeout&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; error &lt;span class="s2"&gt;&amp;#34;No timeout specified&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; shift&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-p&lt;span class="p"&gt;|&lt;/span&gt;--port&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; error &lt;span class="s2"&gt;&amp;#34;No port specified&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; shift&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-l&lt;span class="p"&gt;|&lt;/span&gt;--compression-level&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;shift&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$#&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; error &lt;span class="s2"&gt;&amp;#34;No compression level specified&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;COMPRESS_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; shift&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;-h&lt;span class="p"&gt;|&lt;/span&gt;--help&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; help&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# (-V|--version)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# version_info;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;(&lt;/span&gt;*&lt;span class="o"&gt;)&lt;/span&gt; usage&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;flags &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#bash #til
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>k3s nginx ingress over traefik</title><link>https://danielms.site/zet/2022/k3s-nginx-ingress-over-traefik/</link><pubDate>Tue, 23 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/k3s-nginx-ingress-over-traefik/</guid><description>&lt;h1 id="k3s-nginx-ingress-over-traefik"&gt;k3s nginx ingress over traefik&lt;/h1&gt;
&lt;p&gt;I have elected to use nginx-ingress over the k3s standard of Traefik.
This was mainly driven because OpenFaaS doesn&amp;rsquo;t yet support it for
custom domain names. I also do not really use Traefik features so I am
unsure what the big trade-offs are yet.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kubernetes #traefik #openfaas
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>kubernetes tips</title><link>https://danielms.site/zet/2022/kubernetes-tips/</link><pubDate>Mon, 22 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/kubernetes-tips/</guid><description>&lt;h1 id="kubernetes-tips"&gt;kubernetes tips&lt;/h1&gt;
&lt;p&gt;Random helpers&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Create Namespace if not Exists&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl create namespace test --dry-run -o yaml | kubectl apply -f -&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Wait for deployment to rollout&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This will wait until the deployment has completed and very useful in
bash scripting.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;kubectl rollout status deployment argocd-repo-server -n argocd&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kubernetes #bash #kubectl
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Getting into the back country</title><link>https://danielms.site/zet/2022/getting-into-the-back-country/</link><pubDate>Tue, 16 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/getting-into-the-back-country/</guid><description>&lt;h1 id="getting-into-the-back-country"&gt;Getting into the back country&lt;/h1&gt;
&lt;p&gt;Over the last weekend I undertook a snow survival course in the back country
near Threadbo. What a great weekend, and an awakening. I think I should
do some more stuff down there and maybe even get some skiing in.&lt;/p&gt;
&lt;p&gt;In the meantime here are the things I need to buy, and learn.&lt;/p&gt;
&lt;h2 id="buy"&gt;Buy&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;z-fold mat&lt;/li&gt;
&lt;li&gt;better hard shell (pants and jacket)&lt;/li&gt;
&lt;li&gt;synthetic jacket pref with water repellent outer&lt;/li&gt;
&lt;li&gt;75L+ pack&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="learn"&gt;Learn&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;weather systems&lt;/li&gt;
&lt;li&gt;layering&lt;/li&gt;
&lt;li&gt;climbing knots&lt;/li&gt;
&lt;li&gt;climbing anchors&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>k3s custom resolv.conf</title><link>https://danielms.site/zet/2022/k3s-custom-resolv.conf/</link><pubDate>Wed, 03 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/k3s-custom-resolv.conf/</guid><description>&lt;h1 id="k3s-custom-resolvconf"&gt;k3s custom resolv.conf&lt;/h1&gt;
&lt;p&gt;Setting up a local k3s just taught me a valuable lesson in networking.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&amp;rsquo;s always DNS&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And my own networking knowledge gaps. So I wrongly assumed that k3s was
using my &lt;code&gt;/etc/resolv.conf&lt;/code&gt; - wrong. It uses &lt;code&gt;core-dns&lt;/code&gt; defaults (AFAIK)
which means my pfSense DNS server which has host overrides for my local cluster
was not being respected.&lt;/p&gt;
&lt;p&gt;This resulted in my CI (drone and woodpecker, in testing) not being able
to communicate with my git server. This makes sense, it cannot resolve
the address.&lt;/p&gt;
&lt;p&gt;To rectify, I used this process. It is probably a bandaid when I should
be using something else. But, it works for now.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;nameserver 192.168.1.1&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sudo tee /etc/k3s-resolv.conf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;kubelet-arg:&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sudo tee -a /etc/rancher/k3s/config.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;- &amp;#34;resolv-conf=/etc/k3s-resolv.conf&amp;#34;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sudo tee -a /etc/rancher/k3s/config.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo systemctl restart k3s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then kill the all the &lt;code&gt;coredns&lt;/code&gt; pods so that they reload with the new
&lt;code&gt;resolv.conf&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pod -n kube-system -l k8s-app&lt;span class="o"&gt;=&lt;/span&gt;kube-dns --no-headers &lt;span class="p"&gt;|&lt;/span&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; xargs -I&lt;span class="o"&gt;{}&lt;/span&gt; kubectl delete pod -n kube-system &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#kubernetes #coredns #dns
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>thisisunsafe Chrome ssl bypass</title><link>https://danielms.site/zet/2022/thisisunsafe-chrome-ssl-bypass/</link><pubDate>Wed, 03 Aug 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/thisisunsafe-chrome-ssl-bypass/</guid><description>&lt;h1 id="thisisunsafe-chrome-ssl-bypass"&gt;thisisunsafe Chrome ssl bypass&lt;/h1&gt;
&lt;p&gt;Note to self.&lt;/p&gt;
&lt;p&gt;I wasted over an hour on this shit.&lt;/p&gt;
&lt;p&gt;Firefox lets me bypass self-signed certificates by clicking on a button.&lt;/p&gt;
&lt;p&gt;Chrome/Opera do not. Instead I have to type &lt;em&gt;thisisunsage&lt;/em&gt; like a fucking
dickhead to get past self-signed certs.&lt;/p&gt;
&lt;p&gt;Honestly, what a joke.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #chromesucks
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Razors worth remembering</title><link>https://danielms.site/zet/2022/razors-worth-remembering/</link><pubDate>Sun, 31 Jul 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/razors-worth-remembering/</guid><description>&lt;h1 id="razors-worth-remembering"&gt;Razors worth remembering&lt;/h1&gt;
&lt;p&gt;Here is a few really useful &lt;em&gt;razors&lt;/em&gt; worth remembering.&lt;/p&gt;
&lt;h2 id="luck"&gt;Luck&lt;/h2&gt;
&lt;p&gt;When choosing between two paths, choose the path with the largest luck surface area&lt;/p&gt;
&lt;h2 id="arena"&gt;Arena&lt;/h2&gt;
&lt;p&gt;When faced with two options, choose the one that puts you in the arena.&lt;/p&gt;
&lt;p&gt;Or, more simply, put yourself out there and be prepared to fail in public. And never
listen to anyone who is on the side lines&lt;/p&gt;
&lt;h2 id="rooms"&gt;Rooms&lt;/h2&gt;
&lt;p&gt;If you have the choice between two rooms, choose the room in which you are the dumbest.&lt;/p&gt;
&lt;p&gt;This means, stretch yourself by sacrificing ego in the short term and reap long term growth.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#life #advice
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-07-29</title><link>https://danielms.site/zet/2022/wgd-fri-2022-07-29/</link><pubDate>Sun, 31 Jul 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-07-29/</guid><description>&lt;h1 id="wgd-fri-2022-07-29"&gt;WGD Fri 2022-07-29&lt;/h1&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Sent out a number of emails to clients who have been having difficulties getting devices
onboarded to get feedback&lt;/li&gt;
&lt;li&gt;Updated some of the documentation&lt;/li&gt;
&lt;li&gt;Ruled out an error I was seeing in testing as the cause of onboarding issues&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="misc"&gt;Misc&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Applied to renew my contract, which is actually a &lt;em&gt;new&lt;/em&gt; contract so I will have to interview
for it.&lt;/li&gt;
&lt;li&gt;Helped out a mate build a &lt;a href="https://wails.dev"&gt;wails&lt;/a&gt; application using
&lt;a href="https://svelte.dev"&gt;Svelte&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Continuing to get more and more deep into kubernetes at work. I totally understand why
the CNCF landscape has a market cap of 10T USD. The thing is a massive pain in the arse
but insanely useful.&lt;/p&gt;</description></item><item><title>python Protocols are Go interfaces</title><link>https://danielms.site/zet/2022/python-protocols-are-go-interfaces/</link><pubDate>Fri, 22 Jul 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/python-protocols-are-go-interfaces/</guid><description>&lt;h1 id="python-protocols-are-go-interfaces"&gt;python Protocols are Go interfaces&lt;/h1&gt;
&lt;p&gt;This week I successfully used python&amp;rsquo;s &lt;a href="https://peps.python.org/pep-0544/"&gt;Protocol&lt;/a&gt;
to fake a dependency on Hashicorp&amp;rsquo;s &lt;a href="https://www.vaultproject.io"&gt;Vault&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Background:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we need Vault access to save some data&lt;/li&gt;
&lt;li&gt;init and sidecar must be running for the pod to be created&lt;/li&gt;
&lt;li&gt;in development we don&amp;rsquo;t need access to the real vault&lt;/li&gt;
&lt;li&gt;in development access to the real vault crosses network boundaries via indirection&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead, we have decided to use a &lt;em&gt;verified fake&lt;/em&gt; and build an interface which
we can use to swap out the real and fake Vault when required.&lt;/p&gt;
&lt;p&gt;After a suggestion from a colleague about Protocol, and some light reading,
this process was super simple. It works almost exactly the same as a Go interface
whereby you define the interface and other classes implement it.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecretStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VaultStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;vault store save&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FailStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;no_save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;not implemented&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Saver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SecretStore&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;saver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Saver&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;saver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;VaultStore&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;saver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;do_save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FailStore&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;-- AttributeError: BrokenStore object has no attribute &amp;#39;save&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#python #interfaces #programming
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>wails as a standalone app</title><link>https://danielms.site/zet/2022/wails-as-a-standalone-app/</link><pubDate>Fri, 22 Jul 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wails-as-a-standalone-app/</guid><description>&lt;h1 id="wails-as-a-standalone-app"&gt;wails as a standalone app&lt;/h1&gt;
&lt;p&gt;The other day a friend suggested we build a tool but he wanted it to be
a standalone GUI application. It needed to be a single binary and usable
across Windows, MacOS and Linux.&lt;/p&gt;
&lt;p&gt;After some research, including C++, Java and Kotlin, we stumbled upon
&lt;a href="https://wails.dev"&gt;wails&lt;/a&gt;. After about an hours of playing around it was
obvious it met all those requirements, and it was familiar to web development.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://github.com/danielmichaels/wails-demo"&gt;demo&lt;/a&gt; is bare bones
example of hitting a 3rd party API with it. It uses &lt;a href="https://svelte.dev"&gt;svelte&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;On a side note, this is first and only svelte app I&amp;rsquo;ve ever developed and
it was very easy to use with great documentation. I see big things for
svelte in the future.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#wails #svelte #gui
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-07-22</title><link>https://danielms.site/zet/2022/wgd-fri-2022-07-22/</link><pubDate>Fri, 22 Jul 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-07-22/</guid><description>&lt;h1 id="wgd-fri-2022-07-22"&gt;WGD Fri 2022-07-22&lt;/h1&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;p&gt;Released my &lt;em&gt;Multiple Accounts&lt;/em&gt; feature that I&amp;rsquo;ve been working on for over two months!
It is now in production but the documentation and official announcement
emails are yet to go out. I actually deployed it to production sooner than
I initially planned. Nonetheless, all is well so far and can&amp;rsquo;t wait to get
some feedback.&lt;/p&gt;
&lt;p&gt;I think I&amp;rsquo;ve also enabled the right level of logging to identify why only
some customers are having issues installing the API on their device. In my
testing I noticed a CSRF error which caused the install to fail. No workaround
yet but keen to see if its the same thing others are experiencing.&lt;/p&gt;
&lt;h2 id="misc"&gt;Misc&lt;/h2&gt;
&lt;p&gt;Played around with &lt;a href="https://wails.dev"&gt;Wails&lt;/a&gt; and got a little
&lt;a href="https://github.com/danielmichaels/wais-demo"&gt;demo&lt;/a&gt; done up
using &lt;a href="https://svelte.dev"&gt;svelte&lt;/a&gt;.
So far, its basically the same process as building a typical SPA except that its self-contained
and no network latency between requests.
That&amp;rsquo;s actually doing it a massive injustice, it is a great tool to build standalone
applications especially if you have web development experience.&lt;/p&gt;
&lt;p&gt;The last few weeks have been pretty hectic both in office hours, at home and then
the ongoing Mudmap dev. Keeping my WGD streak fell away as I prioritised other things.
Feeling back on track now and kind of missed cataloging what I&amp;rsquo;ve been up to.&lt;/p&gt;</description></item><item><title>June 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-june-2022/</link><pubDate>Wed, 06 Jul 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-june-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Moved house and still haven&amp;rsquo;t quite finished my one goal from last month.&lt;/p&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2022/retrospective-may-2022/"&gt;May&amp;rsquo;s Retrospective&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="deploy-the-multi-account-feature"&gt;Deploy the multi-account feature&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Some hiccups slowed development&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this month&amp;rsquo;s Mudmap development was dedicated to multi-account feature.
Unfortunately, that work is still ongoing but at least right now its down to fleshing out
the user interface. So far, users can invite members to join their organisation, join other
organisations, remove members from their own, update org details, and manage devices within
their organisation based on simple permissions.&lt;/p&gt;
&lt;p&gt;At the beginning of the month I spent a lot of time building on top of the &lt;a href="https://auth0.com/docs/api/authorization-extension"&gt;Auth0 Authorization&lt;/a&gt;
extension. This worked well in theory as it matched much of my vision for what I wanted to achieve.
Unfortunately, it did not fit well into the application of that vision; simple actions require many
API calls. For instance, I could find no easy method to get the &lt;code&gt;_id&lt;/code&gt; of a &lt;code&gt;group&lt;/code&gt; without iterating
over every &lt;code&gt;group&lt;/code&gt; and searching for its mutable &lt;code&gt;name&lt;/code&gt;. To do most operations against the &lt;code&gt;group&lt;/code&gt;
you need this &lt;code&gt;_id&lt;/code&gt;, meaning lots of API calls and then loops to find the &lt;code&gt;_id&lt;/code&gt;. This means
latency but also requires a fair bit of error handling. In all, it &lt;em&gt;felt bad&lt;/em&gt; and without
storing that data in the database was slow.&lt;/p&gt;
&lt;p&gt;Luckily, I went back to the much simpler and easier to use &lt;a href="https://auth0.com/docs/api/management/v2"&gt;Auth0 Management&lt;/a&gt; API. This in hindsight
does everything I wanted the &lt;a href="https://auth0.com/docs/api/authorization-extension"&gt;Auth0 authorization&lt;/a&gt; extension to do but with 1/10th the complexity.
Building, integrating, and testing the authorization extension took me about a week of evenings but
replacing it with the management API took about three hours, and it removed a significant amount
of code.&lt;/p&gt;
&lt;p&gt;In hindsight, the toil and wasted effort building out the organisations using Auth0&amp;rsquo;s
authorization extension wasn&amp;rsquo;t wasted. I think it actually helped to design a much simpler and
easier to maintain product. The effort helped shape the &lt;em&gt;how&lt;/em&gt; and &lt;em&gt;what&lt;/em&gt; of its current form. Still,
it definitely slowed the deployment of the feature.&lt;/p&gt;
&lt;p&gt;I also started on a side project to service my own needs - &lt;a href="https://github.com/danielmichaels/storeman"&gt;storeman&lt;/a&gt; - which cut into the feature
development.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If you can afford it, get removalists to lift and shift your house. My hubris and tightwad-ism
meant
I moved my entire house on my own minus a mate who helped lift the real heavy stuff. The entire
process absolutely cooked my lower back, and I&amp;rsquo;m only now starting to recover.&lt;/li&gt;
&lt;li&gt;Peaky Blinders, the latest season. I watch about 30-60 minutes of television a day so I&amp;rsquo;m picky
about what to tune into on the idiot box. This season of Peaky Blinders was the best by far,
really hoping they make another season.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;This is the second month when I&amp;rsquo;ve bombed on my targets. Still, I&amp;rsquo;m happy to be continually
making progress on my objectives. It might be costing me some business in the short term, as
&lt;em&gt;user-facing&lt;/em&gt; features aren&amp;rsquo;t being developed quick enough. There isn&amp;rsquo;t much more time I can steal
from my days to get more done, but I think I could be smarter in my planning - hindsight, of course.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plan more, code less - meaning, write more documentation of what I want to achieve before I try to achieve it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accomplished a lot in terms of functionality especially when I lost a number of days due to the move.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deploy the organisations feature&lt;/li&gt;
&lt;li&gt;Allow users to back up their device from within Mudmap&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-06-01"
 let end_date = "2022-06-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2022-06-01"
 let end_date = "2022-06-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-06-01"
 let end_date = "2022-06-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>WGD Mon 2022-07-01</title><link>https://danielms.site/zet/2022/wgd-mon-2022-07-01/</link><pubDate>Sun, 03 Jul 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-mon-2022-07-01/</guid><description>&lt;h1 id="wgd-mon-2022-07-01"&gt;WGD Mon 2022-07-01&lt;/h1&gt;
&lt;h2 id="mudmap"&gt;[Mudmap]&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;After a month of working with the Auth0 Authorization extension, I ripped it out
Instead, I&amp;rsquo;ve replaced it with the much simpler Management API and have achieved the same thing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="misc"&gt;Misc&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Moved house&lt;/li&gt;
&lt;li&gt;Started seeing my EP twice a week&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A light week in terms of &lt;em&gt;work&lt;/em&gt; but very busy packing, moving and unpacking for the move.
Moving is such a painful experience but on the whole we&amp;rsquo;re really happy we did it. This house
is much nicer.&lt;/p&gt;</description></item><item><title>Ansible check exists with register</title><link>https://danielms.site/zet/2022/ansible-check-exists-with-register/</link><pubDate>Mon, 20 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/ansible-check-exists-with-register/</guid><description>&lt;h1 id="ansible-check-exists-with-register"&gt;Ansible check exists with register&lt;/h1&gt;
&lt;p&gt;In building my Ubuntu disaster recovery ansible script, I discovered the
&lt;code&gt;register&lt;/code&gt; condition.&lt;/p&gt;
&lt;p&gt;To prevent needless tasks from running, for instance, &lt;code&gt;shell&lt;/code&gt; commands
which would be redundant. It is possible to check a condition exists
and only execute the task based on the response.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Download GH cli install script&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gh_exists&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install gh cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gh_exists.stat.exists == False&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.get_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://raw.githubusercontent.com/danielmichaels/dot/master/installers/install-gh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/tmp/gh.sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;productivity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install GH Cli&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gh_exists.stat.exists == False&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cat /tmp/gh.sh | sh -s -- -y&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;productivity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above code block installs the &lt;code&gt;gh&lt;/code&gt; CLI tool. It executes a script
from my github &lt;code&gt;dot&lt;/code&gt; repo only if the &lt;code&gt;gh&lt;/code&gt; binary &lt;strong&gt;does not&lt;/strong&gt; exist in
the &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I also achieved the same thing for installing Go. Using this method I
didn&amp;rsquo;t need to install a &lt;code&gt;ansible-galaxy&lt;/code&gt; third party role.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# vars:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# go_version: &amp;#34;1.18.3&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Check go exists and version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/usr/local/go/bin/go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;register&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;go_exists&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Remove existing /usr/local/go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;go_exists.stat.exists == False&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/usr/local/go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;absent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install Golang&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;go_exists.stat.exists == False&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;become&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible.builtin.unarchive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://go.dev/dl/go{{ go_version }}.linux-amd64.tar.gz&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/usr/local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;remote_src&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;yes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It might not be elegant but it works well for me.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ansible #productivity
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Kubernetes local development musings: work edition</title><link>https://danielms.site/zet/2022/kubernetes-local-development-musings-work-edition/</link><pubDate>Mon, 20 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/kubernetes-local-development-musings-work-edition/</guid><description>&lt;h1 id="kubernetes-local-development-musings-work-edition"&gt;Kubernetes local development musings: work edition&lt;/h1&gt;
&lt;p&gt;At work we use K8s for development, staging and production. We have to,
our external dependencies are inaccessible without it.&lt;/p&gt;
&lt;p&gt;This makes local development painful. We need to &lt;em&gt;shift left&lt;/em&gt; and move
more of the stack closer to our local systems.&lt;/p&gt;
&lt;p&gt;Possible ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;minikube as the local cluster&lt;/li&gt;
&lt;li&gt;k3s as a local cluster&lt;/li&gt;
&lt;li&gt;kind/k3d as a dockerised local cluster&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TBC&lt;/p&gt;</description></item><item><title>Performance review notes Q1 2022</title><link>https://danielms.site/zet/2022/performance-review-notes-q1-2022/</link><pubDate>Sat, 18 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/performance-review-notes-q1-2022/</guid><description>&lt;h1 id="performance-review-notes-q1-2022"&gt;Performance review notes Q1 2022&lt;/h1&gt;
&lt;p&gt;Keeping track of my last performance review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skill Levels:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Level: 4&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;P4&lt;/li&gt;
&lt;li&gt;S2&lt;/li&gt;
&lt;li&gt;U3&lt;/li&gt;
&lt;li&gt;H2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Comments:&lt;/p&gt;
&lt;p&gt;Dan has exceeded expectations. He is a high performing member whose contribution
has been critical to the delivery of XXXX and XXX&amp;rsquo;s ability to meet its
strategic objectives. He has, without doubt, developed and implemented
complex programs that have achieved an excellently engineered result.
He built the persistence functionality for XXXX and drove this essential
requirement through to production. Additionally, he took over the development
of another highly specialised software component for the project at short
notice. He not only delivered the highly specialised features and integrations,
but improved upon the original design.&lt;/p&gt;
&lt;p&gt;Dan has consistently contributed to XXX&amp;rsquo;s software engineering processes and
mentored members of XXX&amp;rsquo;s XXXX team in integrating and streamlining code to
enable XXXX. He has taken on work to professionalise the XXX XXX structure
and permissions. He has enabled a self-service process which automates the
creation and launching of resources without the need for manual intervention
by XXX staff. He has been responsible for running user acceptance testing and
XXXX familiarisation sessions for stakeholders. Dan is a very resonpsive and
valued member of XXX and an asset to the section.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#performance #appraisal #brag
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-06-17</title><link>https://danielms.site/zet/2022/wgd-fri-2022-06-17/</link><pubDate>Sat, 18 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-06-17/</guid><description>&lt;h1 id="wgd-fri-2022-06-17"&gt;WGD Fri 2022-06-17&lt;/h1&gt;
&lt;h2 id="storeman"&gt;Storeman&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Finished the basic endpoints for Containers and Items, meaning users
can add a container and then put items in it. QR code and image support WIP&lt;/li&gt;
&lt;li&gt;Started on the QR code support branch&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Admin users can now invite users by email to join their Organisation&lt;/li&gt;
&lt;li&gt;Started work on the invite token storage and endpoints on the backend&lt;/li&gt;
&lt;li&gt;Fielded some questions about pricing for non-for-profit foundations - something I had not
considered.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="misc"&gt;Misc&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Preparations for moving house continue&lt;/li&gt;
&lt;li&gt;Signed up for a three day adventure training course in the alpine regions
where we&amp;rsquo;ll learn some basic snow camping and survival skills.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Mudmap org and new user registration</title><link>https://danielms.site/zet/2022/mudmap-org-and-new-user-registration/</link><pubDate>Thu, 16 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/mudmap-org-and-new-user-registration/</guid><description>&lt;h1 id="mudmap-org-and-new-user-registration"&gt;Mudmap org and new user registration&lt;/h1&gt;
&lt;p&gt;The rough registration process for adding a user to an &lt;code&gt;Organisation&lt;/code&gt;.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Admin account enters email into Org settings tab of user they wish to invite&lt;/li&gt;
&lt;li&gt;Email is sent to them with a short lived key&lt;/li&gt;
&lt;li&gt;User opens email and either signs up or logs into Mudmap via Auth0&lt;/li&gt;
&lt;li&gt;User navigates to Settings -&amp;gt; Organisation and clicks &lt;em&gt;Join Org&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;User enters key from email&lt;/li&gt;
&lt;li&gt;If key is expired, alert user and admin&lt;/li&gt;
&lt;li&gt;Otherwise, add user to Org and remove them from existing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Scope of work (no particular order)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Invite form&lt;/li&gt;
&lt;li&gt;Invite Email (sendgrid template &amp;amp; Go code)&lt;/li&gt;
&lt;li&gt;Registration flow documentation with video&lt;/li&gt;
&lt;li&gt;Token/Key table for tracking purposes, auth&amp;rsquo;ing Invites on backend&lt;/li&gt;
&lt;li&gt;Token auth endpoint for checking validity&lt;/li&gt;
&lt;li&gt;Endpoint ^ add user to Org in DB, and in Auth0 Groups&lt;/li&gt;
&lt;li&gt;Email alerting Admins user has joined&lt;/li&gt;
&lt;li&gt;Email alerting Admins user attempted to join but token was out of date?&lt;/li&gt;
&lt;li&gt;Or, just allow user to get new token?&lt;/li&gt;
&lt;li&gt;Email welcoming User to Org&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#mudmap #architecture
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go database and sql tips</title><link>https://danielms.site/zet/2022/go-database-and-sql-tips/</link><pubDate>Tue, 14 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/go-database-and-sql-tips/</guid><description>&lt;h1 id="go-database-and-sql-tips"&gt;Go database and sql tips&lt;/h1&gt;
&lt;p&gt;Note to self.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ItemUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	UPDATE items
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	SET
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		name = $1,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		description = $2,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		image = $3,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		updated_at = datetime(&amp;#39;now&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	WHERE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;		id = $4
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;	RETURNING id`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cancel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryRowContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ErrNoRows&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;item does not exist&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code works but this &lt;strong&gt;really&lt;/strong&gt; tripped me up&amp;hellip;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;stmt&lt;/code&gt; block uses &lt;code&gt;$1&lt;/code&gt; numbering, instead of the usual &lt;code&gt;?&lt;/code&gt;. I stupidly
thought the &lt;code&gt;$n&lt;/code&gt; numbering meant something when executing the &lt;code&gt;args...&lt;/code&gt; inside
the &lt;code&gt;QueryRowContext&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It does not&lt;/strong&gt;. It is simply there for you, to help count the number of
variable statements inside the query, AFAIK. This cost me an hour or so because
I set the &lt;code&gt;id = $1&lt;/code&gt; so as to map the &lt;code&gt;ItemUpdate&lt;/code&gt; parameters with the query.&lt;/p&gt;
&lt;p&gt;I hope I remember this in the future! I don&amp;rsquo;t like ORM&amp;rsquo;s but this definitely would
not have tripped me up.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #database #failure
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go template iterators</title><link>https://danielms.site/zet/2022/go-template-iterators/</link><pubDate>Fri, 10 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/go-template-iterators/</guid><description>&lt;h1 id="go-template-iterators"&gt;Go template iterators&lt;/h1&gt;
&lt;p&gt;How to use Go templates &lt;code&gt;range&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;range&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This loops over the length of a struct setting the dot value to element
being looped over.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you need to access a value via the &lt;code&gt;.&lt;/code&gt; notation and that value is outside
the current iteration, you need to use &lt;code&gt;$.&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// example &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{{.&lt;/span&gt;&lt;span class="nx"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// range over an Item struct to build a URL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// the $ is required so that the template can inspect global state&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// not just the current iteration. Item does not have a .Container.ID &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// field and will fail without $&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/containers/{{$.Container.ID}}/items/{{.ID}}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #templates
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-06-10</title><link>https://danielms.site/zet/2022/wgd-fri-2022-06-10/</link><pubDate>Wed, 08 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-06-10/</guid><description>&lt;h1 id="wgd-fri-2022-06-10"&gt;WGD Fri 2022-06-10&lt;/h1&gt;
&lt;h2 id="mudmap"&gt;[Mudmap]&lt;/h2&gt;
&lt;p&gt;Work continues with the multi-account setup.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UI changes to the Settings page which is now broken into smaller subsections:
&lt;ul&gt;
&lt;li&gt;User&lt;/li&gt;
&lt;li&gt;Organisation&lt;/li&gt;
&lt;li&gt;Billing&lt;/li&gt;
&lt;li&gt;Membership&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Had some user reported errors which I suspect are being caused by &lt;code&gt;sshguard&lt;/code&gt; being triggered.
&lt;code&gt;sshguard&lt;/code&gt;, as it sounds, is an application which blacklists SSH brute forcing.
When users attempt to set their initial password wrong too many times,
&lt;code&gt;sshguard&lt;/code&gt; might be blacklisting Mudmap&amp;rsquo;s server IP addresses. This
can lead to unexplained errors, things work then suddenly don&amp;rsquo;t but after
a period time it resolves. I&amp;rsquo;ve added &lt;a href="https://docs.mudmap.io/preparing-devices#ssh-setup-in-pfsense"&gt;instructions&lt;/a&gt; to the the documentation
with a fix, which entails whitelisting Mudmap&amp;rsquo;s IP&amp;rsquo;s from &lt;code&gt;sshguard&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Also, I realise that I am having issues with devices more often than I like
but have no metrics on potential issues. I have started investigating a way
to log device details so I can determine if issues are more common for
certain setup&amp;rsquo;s, say VM versus physical hardware.&lt;/p&gt;
&lt;h2 id="storeman"&gt;&lt;a href="https://github.com/danielmichaels/storeman"&gt;Storeman&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Started on a project for cataloguing my many storage boxes and their contents using QR codes.&lt;/p&gt;
&lt;p&gt;Its a pretty simple premise, instead of writing or using your memory, you&amp;rsquo;ll put a QR code
on a storage container. The QR links to a page with a list of its contents including
images for future reference. I&amp;rsquo;m writing it in Go using basic templates and a little
bit of &lt;a href="https://alpinejs.dev"&gt;Alpine.js&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve moved across three states and four homes in the last four years. Moving sucks but
not knowing what&amp;rsquo;s in the many boxes we have laying around the garage sucks even more.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scaffolded basic structure&lt;/li&gt;
&lt;li&gt;Created database layer and migrations&lt;/li&gt;
&lt;li&gt;Views for home and viewing containers done at a basic level&lt;/li&gt;
&lt;li&gt;Templating and form helpers integrated into the Server struct&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Authentication and session management is up next&lt;/p&gt;
&lt;h2 id="misc"&gt;Misc&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Landed a new lease, moving in at the end of the month.&lt;/li&gt;
&lt;li&gt;Bought a nice second hand car to replace our old one - makes getting to work a lot easier&lt;/li&gt;
&lt;li&gt;Deployed my own &lt;a href="https://github.com/mtlynch/picoshare"&gt;PicoShare&lt;/a&gt; instance at &lt;a href="https://share.danielms.site"&gt;share.danielms.site&lt;/a&gt; using &lt;a href="https://fly.io"&gt;fly&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Published May&amp;rsquo;s &lt;a href="https://danielms.site/retrospectives/2022/retrospective-may-2022/"&gt;retro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Breadcrumbs for Go templates</title><link>https://danielms.site/zet/2022/breadcrumbs-for-go-templates/</link><pubDate>Tue, 07 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/breadcrumbs-for-go-templates/</guid><description>&lt;h1 id="breadcrumbs-for-go-templates"&gt;Breadcrumbs for Go templates&lt;/h1&gt;
&lt;p&gt;How I am dealing with breadcrumbs for Go templates.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;BreadCrumb&lt;/code&gt; struct&lt;/li&gt;
&lt;li&gt;Attach the &lt;code&gt;BreadCrumb&lt;/code&gt; to the template, in my case I use a &lt;code&gt;TemplateData&lt;/code&gt; struct in every
template.&lt;/li&gt;
&lt;li&gt;Pass the &lt;code&gt;BreadCrumb&lt;/code&gt; to the view and then reference it within the template.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BreadCrumb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Href&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TemplateData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;BreadCrumbs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;BreadCrumb&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... trunc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handleHome&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HandlerFunc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// trunc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;crumbs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;BreadCrumb&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Page One&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/one&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Page Two&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/two&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;home.tmpl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TemplateData&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;BreadCrumbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;crumbs&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then reference that in templates with&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{ if .BreadCrumbs }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{range .BreadCrumbs }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;flex items-center&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;flex-shrink-0 h-5 w-5 text-gray-400&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;xmlns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;http://www.w3.org/2000/svg&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;viewBox&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0 0 20 20&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;currentColor&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;aria-hidden&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt; &lt;span class="na"&gt;fill-rule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;evenodd&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;clip-rule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;evenodd&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;svg&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{.Href}}&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;ml-4 text-sm font-medium text-gray-500 hover:text-gray-700&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{{.Name}}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{end}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{{end}}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #templates
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>fly.io CNAME setup</title><link>https://danielms.site/zet/2022/fly.io-cname-setup/</link><pubDate>Tue, 07 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/fly.io-cname-setup/</guid><description>&lt;h1 id="flyio-cname-setup"&gt;fly.io CNAME setup&lt;/h1&gt;
&lt;p&gt;I started using &lt;a href="https://fly.io"&gt;fly.io&lt;/a&gt; today and so far it is pretty
awesome.&lt;/p&gt;
&lt;p&gt;tl;dr &lt;a href="https://share.danielms.site"&gt;share.danielms.site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I setup everything without much issue but coming from Render, setting up
the CNAME was not as easy (in hindsight, its super simple).&lt;/p&gt;
&lt;p&gt;After deploying my &lt;a href="https://github.com/mtlynch/picoshare"&gt;PicoShare&lt;/a&gt; app
using the fantastic &lt;code&gt;flyctl&lt;/code&gt; CLI, I could easily access it from its &lt;a href="https://picoshare-danielms.fly.dev"&gt;fly
domain&lt;/a&gt; but I wanted to put it on my
personal website as a subdomain.&lt;/p&gt;
&lt;p&gt;Netlify is my DNS provider, so I setup a CNAME over there but it would
not resolve - OpenSSL error. Turns out you need to manually generate
the certificate for the subdomain.&lt;/p&gt;
&lt;p&gt;To do that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fly certs create share.danielms.site
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; The certificate for share.danielms.site has not been issued yet.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; Hostname = share.danielms.site
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; DNS Provider = nsone
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; Certificate Authority = Let&amp;#39;s Encrypt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; Issued =
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; Added to App = 35 seconds ago
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; Source = fly
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; Your certificate for share.danielms.site is being issued. Status is Awaiting certificates. Make sure to create another certificate for www.share.danielms.site when the current certificate is issued.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Check the status with &lt;code&gt;fly certs show share.danielms.site&lt;/code&gt;. It only takes
a few seconds to deploy anyway but this will show if there are any errors.&lt;/p&gt;
&lt;p&gt;Viola &lt;a href="https://share.danielms.site"&gt;share.danielms.site&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#fly #dns
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>May 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-may-2022/</link><pubDate>Sat, 04 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-may-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2022/retrospective-april-2022/"&gt;April&amp;rsquo;s Retrospective&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="deploy-pfsense-capable-mudmap"&gt;Deploy pfSense+ capable Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Did not roll this out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: D&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of this month was dedicated to multi-account feature. This is still
a great feature to roll out by I decided early on in the month that it would come after.&lt;/p&gt;
&lt;p&gt;There are some compatibility issues remaining with the API&amp;rsquo;s support for
pfSense+, namely with ARM devices. This has only become apparent in the
last few days, and is not something I can test either. So, in hindsight
waiting this one has saved Mudmap from some potentially unhappy customers.
Hoping a fix is released for this in due course. When this does release it will have to come with that caveat.&lt;/p&gt;
&lt;h3 id="get-started-on-multi-account-support-a-big-customer-request"&gt;Get started on multi-account support (a big customer request)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Still ongoing but should be finished this month.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is not finished yet, but at least it is work I am excited to do and release for my customers.
I&amp;rsquo;ve given this a high priority as numerous people have requested it. They
have staff who they want to manage devices but its currently not possible without sharing a single account. The sub-accounts must also have some permission settings, like payment details for instance.&lt;/p&gt;
&lt;p&gt;So, I&amp;rsquo;ve settled on the following structure;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Organisation
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ---------
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Users Devices
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each user has a &lt;code&gt;permission&lt;/code&gt; applied which will grant them access to
certain views and API operations. Permissions haven&amp;rsquo;t been started yet.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had to migrate Users to Organisations along with their subscriptions and Devices.&lt;/p&gt;
&lt;p&gt;Considering the changes I&amp;rsquo;m probably going to do an outage window over a weekend, rather than roll it out during a weekday and potentially catch someone in a weird state.
Not ready for that yet but these are thing considerations I&amp;rsquo;ve been thinking about along with writing the code.&lt;/p&gt;
&lt;p&gt;I am still working on the user interface, which as always, is the most tedious and slow part for me.
The other hard part, is managing how users add new members to their Organisation.
To be a member they have to sign up via Auth0 - to get a valid id - and then
they can be added to the Org. So far, I&amp;rsquo;ve settled on admin users being able
to invite members by email. Then they sign up and enter a code to be assigned
to the Org. It feels a little contrived and suboptimal but works for now.&lt;/p&gt;
&lt;p&gt;I feel like I accomplished a lot towards achieving this goal.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;If you want an excellent security tool that scans and identifies everything on your network,
check out &lt;a href="https://rumble.run"&gt;Rumble&lt;/a&gt;. It is a company that was started by &lt;a href="https://en.wikipedia.org/wiki/H._D._Moore"&gt;H.D Moore&lt;/a&gt;
who created the &lt;a href="https://en.wikipedia.org/wiki/Metasploit_Project"&gt;Metasploit&lt;/a&gt; project. I ran it
to see what is on my network but also to see how it identifies my many pfSense VM&amp;rsquo;s. It found
all of my assets with very detailed info within 3 minutes across two /24 networks. Definitely worth
a look, and you can run it from a raspberry Pi.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;A great month from a productivity standpoint. I&amp;rsquo;ve managed to work out a decent balance for
work and out-of-work work, in addition to my life. Much of last year I was burning the candle
from both ends; late night and early hours. Honestly, I got &lt;em&gt;more&lt;/em&gt; done, but it wasn&amp;rsquo;t as good as
the work I&amp;rsquo;m doing now. I&amp;rsquo;ve settled into a sleep in until 6:30-7 schedule, and I feel much more
rested with more clarity of thought. In the past, I was up at 5, and it took me 30 minutes just to
wake up!&lt;/p&gt;
&lt;p&gt;We also got out for an overnight camping trip before it starts to get cold. Camped right next to
a river watching the fire is a luxury I don&amp;rsquo;t take for granted. What I take for granted is how
quickly time (i.e. our life) flies by between such adventures. Keeping grounded and getting away
from the house has been another good move over the last couple of months - if I&amp;rsquo;m at home, I
can&amp;rsquo;t help but be &lt;em&gt;plugged&lt;/em&gt; in.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I need to get in front of issues sooner, and have those hard conversations with people more often. At times I&amp;rsquo;m too passive or indifferent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Took several days to design and proof-of-concept the multi-account architecture and it made the development so much faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deploy the multi-account feature&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-05-01"
 let end_date = "2022-05-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2022-05-01"
 let end_date = "2022-05-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-05-01"
 let end_date = "2022-05-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>WGD Fri 2022-06-03</title><link>https://danielms.site/zet/2022/wgd-fri-2022-06-03/</link><pubDate>Fri, 03 Jun 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-06-03/</guid><description>&lt;h1 id="wgd-fri-2022-06-03"&gt;WGD Fri 2022-06-03&lt;/h1&gt;
&lt;h2 id="mudmap"&gt;[Mudmap]&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Refactored codebase to make better use of the &lt;code&gt;internal&lt;/code&gt; package.
&lt;a href="https://dave.cheney.net/2019/10/06/use-internal-packages-to-reduce-your-public-api-surface"&gt;1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Started work on the frontend; Organisations and Users are now displayed in the settings pages.
These pages are restricted according to the user privileges.&lt;/li&gt;
&lt;li&gt;Added gzip supported middleware. I haven&amp;rsquo;t profiled it but the content size is significantly
reduced.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="misc"&gt;Misc&lt;/h2&gt;
&lt;p&gt;Watched Top Gun 2. It&amp;rsquo;s an entertaining movie with some cool action shots. Definitely worth
watching if you enjoyed the first one.&lt;/p&gt;
&lt;p&gt;Saw a really cool idea whilst at bunnings over the weekend. Side note, I fixed my tallboys draw runners like a
real man. It was a QR code system for notating what&amp;rsquo;s in storage containers. You slap a QR stick
on it, scan it and add its contents to the web page.&lt;/p&gt;
&lt;p&gt;As a developer, I immediately thought I could write that. Which, I actually might but as a self
hosted open source project. You could deploy it to heroku or fly and never pay a cent. How often
and how fast does something like this need to be, especially when you are the only user.&lt;/p&gt;
&lt;p&gt;Also, I got to start on a Go project at work. It&amp;rsquo;s replacing a often used compliance tool which
scans the contents of packages written in Java. The only caveat, it&amp;rsquo;s a &lt;em&gt;side project&lt;/em&gt; and can&amp;rsquo;t
get in the way of my day to day work. I&amp;rsquo;ll take it though as I get to work on an interesting
problem in Go and get paid handsomely for it.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#wgd #mudmap
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go features repo for future reference</title><link>https://danielms.site/zet/2022/go-features-repo-for-future-reference/</link><pubDate>Mon, 30 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/go-features-repo-for-future-reference/</guid><description>&lt;h1 id="go-features-repo-for-future-reference"&gt;Go features repo for future reference&lt;/h1&gt;
&lt;p&gt;I need to write a simple web app that exposes some of the more useful
Go features such as Mutexes, gorotinues, &lt;code&gt;select&lt;/code&gt; and &lt;code&gt;NewRequestWithContext&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rough outline:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;webserver&lt;/li&gt;
&lt;li&gt;each endpoint with a single &lt;em&gt;feature&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This way I, or anyone else can pull down the repo and run the server.
Using curl they can hit each endpoint to see how it works whilst reading
over the code. Each endpoint should have good logging to showcase what
is happening.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#golang #development
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Sat 2022-05-28</title><link>https://danielms.site/zet/2022/wgd-sat-2022-05-28/</link><pubDate>Sat, 28 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-sat-2022-05-28/</guid><description>&lt;h1 id="wgd-sat-2022-05-28"&gt;WGD Sat 2022-05-28&lt;/h1&gt;
&lt;h2 id="mudmap"&gt;&lt;a href="https://mudmap.io"&gt;Mudmap&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Continuing the &lt;em&gt;Mudmap Organisations&lt;/em&gt; feature&lt;/li&gt;
&lt;li&gt;Migrating users to organisations is done&lt;/li&gt;
&lt;li&gt;Users in the same organisation now share devices and can create, read, update, and delete devices.&lt;/li&gt;
&lt;li&gt;Payment webhooks is still being ported over but is about half way done&lt;/li&gt;
&lt;li&gt;Finished the Auth0 Management API client, allowing Mudmap to add to &lt;code&gt;app_metadata&lt;/code&gt; fields. This
allows Mudmap to store &lt;em&gt;some&lt;/em&gt; data in the JWT which is then used in the request context.&lt;/li&gt;
&lt;li&gt;Working race conditions where the Auth0 data is out of sync with the database, leaving mixed
state. This only happens on state change, i.e. a user joins a new organisation.&lt;/li&gt;
&lt;li&gt;Investigated why my logs have disappeared from New Relic. Nothing has changed on my end, so I
suspect its &lt;a href="https://render.com"&gt;Render&lt;/a&gt; not forwarding the application logs correctly. They
appear to be coming through again - again, nothing changed on my end.&lt;/li&gt;
&lt;li&gt;Still toying with the idea of ripping out Postgres for Litestream and have been researching and
reading over the many HN threads about it lately. It works for
&lt;a href="https://tailscale.com"&gt;tailscale&lt;/a&gt;, so it&amp;rsquo;ll probably work for this app.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="misc"&gt;Misc&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Sold one of our cars this week, leaving us with only one until we can find a new one.&lt;/li&gt;
&lt;li&gt;Getting kicked out of our rental because the owners are selling so we&amp;rsquo;re scrambling to find a new
house within the next 6 weeks. Canberra rent has exploded so we&amp;rsquo;re looking at an extra ~100 per
week for a comparable house.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Ansible localhost setup</title><link>https://danielms.site/zet/2022/ansible-localhost-setup/</link><pubDate>Wed, 25 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/ansible-localhost-setup/</guid><description>&lt;h1 id="ansible-localhost-setup"&gt;Ansible localhost setup&lt;/h1&gt;
&lt;p&gt;As I&amp;rsquo;ve now committed myself to Ubuntu, setting up an Ansible playbook
for my personal machine is probably a good idea. I already have several
several playbooks for my cloud VM&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;Things which will need automating:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dotfiles&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ds&lt;/code&gt;, &lt;code&gt;zet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;github repo&amp;rsquo;s (mudmap, zet, ds, dot)&lt;/li&gt;
&lt;li&gt;oh-my-zsh&lt;/li&gt;
&lt;li&gt;starship&lt;/li&gt;
&lt;li&gt;i3 (i3status-rust) &amp;amp; xandr&lt;/li&gt;
&lt;li&gt;opera, vivaldi&lt;/li&gt;
&lt;li&gt;pycharm, golang, webstorm&lt;/li&gt;
&lt;li&gt;curlie, exa, air&lt;/li&gt;
&lt;li&gt;Go (latest)&lt;/li&gt;
&lt;li&gt;Docker (podman, too)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Apt packages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;psql&lt;/li&gt;
&lt;li&gt;vim&lt;/li&gt;
&lt;li&gt;zsh&lt;/li&gt;
&lt;li&gt;curl&lt;/li&gt;
&lt;li&gt;python3&lt;/li&gt;
&lt;li&gt;npm, yarn, node&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#ansible #development
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>zerolog Objects</title><link>https://danielms.site/zet/2022/zerolog-objects/</link><pubDate>Wed, 25 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/zerolog-objects/</guid><description>&lt;h1 id="zerolog-objects"&gt;zerolog Objects&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://github.com/rs/zerolog"&gt;zerolog&lt;/a&gt; is so good. Today I learnt that
it can use marshal structs into an object for logging. All you need to do
is add the &lt;code&gt;MarshalZerologObject&lt;/code&gt; method to the struct. Then it will be
accessible on the logger via the &lt;code&gt;.Object&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// user.go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;name&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;email&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;MarshalZerologObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;zerolog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// where its called&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;test@test.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Info&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;user logged&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// outputs:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// INF user logged user={&amp;#34;name&amp;#34;:&amp;#34;test&amp;#34;,&amp;#34;email&amp;#34;:&amp;#34;test@test.com&amp;#34;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note the above example output is using a ConsoleLogger, not the JSONLogger
which you should use in production.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #zerolog
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD 2022-05-20</title><link>https://danielms.site/zet/2022/wgd-2022-05-20/</link><pubDate>Fri, 20 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-2022-05-20/</guid><description>&lt;h1 id="wgd-2022-05-20"&gt;WGD 2022-05-20&lt;/h1&gt;
&lt;p&gt;Still working on Mudmap&amp;rsquo;s switch from single user accounts to multiple
accounts per organisation.&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m making a lot of progress on the transition from the one user one
account model. I&amp;rsquo;ve had to create extra tables, migrate users and devices
between them whilst ensuring backwards compatibility. I think the changes
to the database models are done for this section of changes - permissions
will come next.&lt;/p&gt;
&lt;p&gt;I learned how to write Postgres subqueries this week and it&amp;rsquo;s pretty
cool seeing a complex query execute repeatedly without issue.&lt;/p&gt;
&lt;p&gt;User and organisation (what I&amp;rsquo;m calling &lt;em&gt;accounts&lt;/em&gt;) data is stored in
the database but a couple of items are being used in &lt;code&gt;context.Values&lt;/code&gt;.
Doing this prevents Mudmap from having to do queries for user or organisation
data when accessing other related data. I understand that it is a slight
anti-pattern but I feel it&amp;rsquo;s actually a good use case for it.
Using &lt;code&gt;context&lt;/code&gt; also allows for storing certain keys in the authentication
token. Again, it saves extra queries.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been using &lt;a href="https://github.com/go-resty/resty"&gt;go-resty&lt;/a&gt; for making
and receiving HTTP requests inside Mudmap. In the past I&amp;rsquo;ve written HTTP
clients using just the standard lib but &lt;code&gt;resty&lt;/code&gt; provides a few things I
need almost out of the box. Namely, retries and &lt;em&gt;interceptors&lt;/em&gt;. When
a token expires, &lt;code&gt;resty&lt;/code&gt; is configured to automatically re-authenticate
by sending a POST to the auth backend. I could write that myself but I&amp;rsquo;m
running a business and don&amp;rsquo;t have time for that!&lt;/p&gt;
&lt;p&gt;Also got to play with some Go Mutex&amp;rsquo;s this week too. Have not really
had a need to implement any until now, and it&amp;rsquo;s pretty easy. Three lines
of code will get you pretty far.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// contrived example&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Mutex&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unlock&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="work"&gt;Work&lt;/h2&gt;
&lt;p&gt;Had some huge wins at work this week. Our team got a lot of kudo&amp;rsquo;s for
the work we&amp;rsquo;ve been putting it too. No rest though, straight from one
fire to the next. So far, I&amp;rsquo;ve worked on Django, Angular and Android
this week alone. Its enough to scatter a man&amp;rsquo;s brain.&lt;/p&gt;</description></item><item><title>Voyager-1 an engineering feat</title><link>https://danielms.site/zet/2022/voyager-1-an-engineering-feat/</link><pubDate>Thu, 19 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/voyager-1-an-engineering-feat/</guid><description>&lt;h1 id="voyager-1-an-engineering-feat"&gt;Voyager-1 an engineering feat&lt;/h1&gt;
&lt;p&gt;I am blown away that the Voyager-1 spacecraft is nearly 45 years old
and still functional. Engineers are still administering this craft, most
of whom were not born when it launched! We have multi-billion dollar
companies experiencing days of downtime with more engineers than you can
shake a stick at, and then we have this beast.&lt;/p&gt;
&lt;p&gt;It takes 20 hours to receive data from the vessel! It is also the &lt;strong&gt;only&lt;/strong&gt;
spacecraft we have in deep space.&lt;/p&gt;
&lt;p&gt;What an absolute marvel of &lt;strong&gt;modern&lt;/strong&gt; engineering.&lt;/p&gt;
&lt;p&gt;Related:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://archive.today/7MIV1"&gt;Engineers Investigating NASA’s Voyager 1 Telemetry Data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#space #engineering #nasa
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go-resty: A Simple HTTP Client</title><link>https://danielms.site/zet/2022/go-resty-a-simple-http-client/</link><pubDate>Wed, 18 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/go-resty-a-simple-http-client/</guid><description>&lt;h1 id="go-resty-a-simple-http-client"&gt;Go-resty: A Simple HTTP Client&lt;/h1&gt;
&lt;p&gt;When you need a decent HTTP client and don&amp;rsquo;t want to craft your own, then
go-resty is a good choice. It is great when you need retries or an way
to send requests based on the response, i.e. token expiration and renewal.&lt;/p&gt;
&lt;p&gt;How I implement a JWT re-authentication check before each request.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/go-resty/resty/v2&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;github.com/form3tech-oss/jwt-go&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;NewAuth0&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Auth0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnBeforeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;resty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;IsTokenValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AccessToken&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Auth0&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;Groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GroupApiStruct&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RestClient&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;IsTokenValid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nb"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StandardClaims&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Parser&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ParseUnverified&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Valid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppConfig&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AuthResponse&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;R&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;SetHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;SetBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthPayload&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;GrantType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;client_credentials&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;ClientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Auth0ApiClientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;ClientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Auth0ApiClientSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;Audience&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Auth0ApiClientAudience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;SetResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nf"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://mudmap.au.auth0.com/oauth/token&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AuthResponse&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AuthResponse&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;AccessToken&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;access_token,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ExpiresIn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;expires_in,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;TokenType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;token_type,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;scope,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;AuthPayload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;GrantType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;grant_type,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ClientId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;client_id,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;ClientSecret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;client_secret,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Audience&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;`json:&amp;#34;audience,omitempty&amp;#34;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This lets me check that the JWT is valid and if not, to request a new
one from the service.&lt;/p&gt;
&lt;p&gt;Coding this myself using just the standard library while possible is not
a pragmatic use of my time. I vendor my code so even if &lt;code&gt;resty&lt;/code&gt; went away
tomorrow, it&amp;rsquo;ll keep working long enough to replace it.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#go #rest
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>People don't listen and I don't have time for them</title><link>https://danielms.site/zet/2022/people-dont-listen-and-i-dont-have-time-for-them/</link><pubDate>Sat, 14 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/people-dont-listen-and-i-dont-have-time-for-them/</guid><description>&lt;h1 id="people-dont-listen-and-i-dont-have-time-for-them"&gt;People don&amp;rsquo;t listen and I don&amp;rsquo;t have time for them&lt;/h1&gt;
&lt;p&gt;Last night I was at a house warming and did some light mingling with people
who I mostly don&amp;rsquo;t know. Only two people, the host and another bloke,
actually conversed with me. Everyone else, was having a one-way conversation
where they were talking at me or waiting for their chance to speak. It
is a common thread in my interactions with people these days.&lt;/p&gt;
&lt;p&gt;Honestly, they are doing me a favour by exposing themselves quickly as
someone to not waste any more time with.&lt;/p&gt;
&lt;p&gt;Life is too short for these kind of interactions.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #people #courtesy
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-05-13</title><link>https://danielms.site/zet/2022/wgd-fri-2022-05-13/</link><pubDate>Fri, 13 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-05-13/</guid><description>&lt;h1 id="wgd-fri-2022-05-13"&gt;WGD Fri 2022-05-13&lt;/h1&gt;
&lt;p&gt;This week has been all about research, proof-of-concept&amp;rsquo;s and database migrations.&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;p&gt;My biggest customer has been asking for Mudmap to support multiple users under a single
account. Last week, they really put a fire under my feet saying they need it, and won&amp;rsquo;t be
pushing the rest of their devices onto the platform until its done. In terms of numbers, it
equates to about a 900% increase in billable units. So, it is worth dropping everything else to
make this a reality.&lt;/p&gt;
&lt;p&gt;I spent this week testing my assumptions, writing documents (mostly fleshing out my ideas) and
some of the migration files needed. This also includes the initial models files on the backend.&lt;/p&gt;
&lt;h2 id="zet-cmd"&gt;zet-cmd&lt;/h2&gt;
&lt;p&gt;This week I pushed some updates and fixes to my zettelkasten CLI tool. I typically read the zet&amp;rsquo;s
using vim but sometimes it is nice to format them to the terminal nicely instead. As a regular
user of &lt;a href="https://github.com/cli/cli"&gt;gh&lt;/a&gt;, I noticed their markdown renderer was quite
beautiful. So, I looked through the &lt;code&gt;go.mod&lt;/code&gt; and found the package by &lt;a href="https://github.com/charmbracelet"&gt;charmbracelet&lt;/a&gt; called
&lt;a href="https://github.com/charmbracelet/glamour"&gt;glamour&lt;/a&gt;. The &lt;a href="https://github.com/danielmichaels/zet-cmd/pull/22"&gt;pull request&lt;/a&gt; was pretty simple. I also &lt;a href="https://github.com/danielmichaels/zet-cmd/pull/21"&gt;tidied&lt;/a&gt; up some things that after
some use made &lt;code&gt;zet-cmd&lt;/code&gt; feel unnatural.&lt;/p&gt;
&lt;h2 id="old-friends"&gt;Old friends&lt;/h2&gt;
&lt;p&gt;Also caught up with a mate I&amp;rsquo;ve not seen in a couple of years. Now that the travel restrictions
are lifting, more and more people I know are filtering through Canberra. It was a great evening
reminiscing.&lt;/p&gt;
&lt;h2 id="backups-to-cloud"&gt;Backups to Cloud&lt;/h2&gt;
&lt;p&gt;I had over 250GB of files which have been sitting on a single external HDD just waiting to get
corrupted. I&amp;rsquo;ve wanted to push them to my cloud storage for a while but lacked a good way of doing
a. This week I found &lt;a href="https://rclone.org"&gt;rclone&lt;/a&gt;. What an easy and fool-proof experience. It took
a couple of days to sync but was largely set and forget. It was cool showing my girlfriend GoPro
video of use jumping out of planes into the ocean too. I did that stuff for nearly ten years and she
still has really no idea of what I did day to day.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#wgd
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Developer Experience is Everything</title><link>https://danielms.site/zet/2022/developer-experience-is-everything/</link><pubDate>Wed, 11 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/developer-experience-is-everything/</guid><description>&lt;h1 id="developer-experience-is-everything"&gt;Developer Experience is Everything&lt;/h1&gt;
&lt;p&gt;In my day job, our development velocity is a crawl. One year ago we were
a high performing team knocking out updates and smashing sprints. Today,
due to corporate desire to &lt;em&gt;streamlime&lt;/em&gt; multiple development teams we are
effectively useless. They wasted millions of dollars building a system
which &lt;em&gt;will&lt;/em&gt; never meet our needs - it literally can&amp;rsquo;t. The sad part?
The new platform is actually really slick (OpenShift).&lt;/p&gt;
&lt;p&gt;For any future job, developer experience must be spoken about during the
interview. It&amp;rsquo;s a tough thing to do because everyone lies during interviews
or they don&amp;rsquo;t talk about it because of non-disclosure. I.e. everything is
falling apart but we need to bluff this bloke in to taking this code monkey
job, and for cheap.&lt;/p&gt;
&lt;p&gt;If you cannot develop on your machine (either on device or remotely into
container etc) and see your changes in real-time, the developer environment
is broken. The business will atrophy good developers because of it, meaning
the people who can fix it will leave. Life is far too short for that sort
of corporate bullshit.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #development
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Old skills, new skills</title><link>https://danielms.site/zet/2022/old-skills-new-skills/</link><pubDate>Wed, 11 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/old-skills-new-skills/</guid><description>&lt;h1 id="old-skills-new-skills"&gt;Old skills, new skills&lt;/h1&gt;
&lt;p&gt;Where I&amp;rsquo;ve been is not where I am today. I&amp;rsquo;ve been uploading some old
photos from a hard drive to the cloud. Looking over them with some
nostalgia and a level of pride about how I transitioned from one life
to the next. For a guy that never finished high school and was told I&amp;rsquo;d
never amount to anything, I&amp;rsquo;ve done alright.&lt;/p&gt;
&lt;p&gt;Old Skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sniper&lt;/li&gt;
&lt;li&gt;MOE&lt;/li&gt;
&lt;li&gt;Diver&lt;/li&gt;
&lt;li&gt;Driver&lt;/li&gt;
&lt;li&gt;Parachuting&lt;/li&gt;
&lt;li&gt;FOFO&lt;/li&gt;
&lt;li&gt;Submarines&lt;/li&gt;
&lt;li&gt;Improvised Explosives&lt;/li&gt;
&lt;li&gt;Clandestine MOE&lt;/li&gt;
&lt;li&gt;Surveillance&lt;/li&gt;
&lt;li&gt;Surveillance Detection&lt;/li&gt;
&lt;li&gt;Navigation&lt;/li&gt;
&lt;li&gt;CQB Instructor/TL&lt;/li&gt;
&lt;li&gt;Water Ops Supervisor&lt;/li&gt;
&lt;li&gt;Boating&lt;/li&gt;
&lt;li&gt;Visual Tracker&lt;/li&gt;
&lt;li&gt;Roping&lt;/li&gt;
&lt;li&gt;PAFA&lt;/li&gt;
&lt;li&gt;Survival&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;New Skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Software Dev&lt;/li&gt;
&lt;li&gt;Networks&lt;/li&gt;
&lt;li&gt;k8s (beginner)&lt;/li&gt;
&lt;li&gt;Python, Go, JS&lt;/li&gt;
&lt;li&gt;Shell Scripting&lt;/li&gt;
&lt;li&gt;Linux Admin&lt;/li&gt;
&lt;li&gt;CI Pipelines&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;li&gt;PR/Code reviews&lt;/li&gt;
&lt;li&gt;SaaS Owner&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#life #skills
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Mudmap Org Register Proposed Flow</title><link>https://danielms.site/zet/2022/mudmap-org-register-proposed-flow/</link><pubDate>Mon, 09 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/mudmap-org-register-proposed-flow/</guid><description>&lt;h1 id="mudmap-org-register-proposed-flow"&gt;Mudmap Org Register Proposed Flow&lt;/h1&gt;
&lt;p&gt;The biggest issue I&amp;rsquo;ve come to experience with the new multi-account
design is re-arranging how Users fit.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve decided &lt;code&gt;organisation&lt;/code&gt; is a better name than &lt;code&gt;account&lt;/code&gt; or &lt;code&gt;business&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="flow"&gt;Flow&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;User signs ups (entered in DB)&lt;/li&gt;
&lt;li&gt;Org is created, User assigned to Org as Admin (Org name is user.email)&lt;/li&gt;
&lt;li&gt;Users are still tracked in DB using their &lt;code&gt;user_id&lt;/code&gt; from Auth0&lt;/li&gt;
&lt;li&gt;Org&amp;rsquo;s will now get the Stripe &lt;code&gt;sub_id&lt;/code&gt; (&lt;strong&gt;Not&lt;/strong&gt; the User)&lt;/li&gt;
&lt;li&gt;User&amp;rsquo;s are added to Org&amp;rsquo;s by invitation&lt;/li&gt;
&lt;li&gt;User&amp;rsquo;s must accept the invite, triggering the move.&lt;/li&gt;
&lt;li&gt;Devices belong to Org&amp;rsquo;s&lt;/li&gt;
&lt;li&gt;All members of an Org can &lt;em&gt;see&lt;/em&gt; Devices&lt;/li&gt;
&lt;li&gt;Existing devices will be moved to each User&amp;rsquo;s (new) Org&lt;/li&gt;
&lt;li&gt;Org delete will CASCADE&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Everything is tracked in the database but &amp;ldquo;synced&amp;rdquo; with Auth0 using the
Authorization Extension. This allows Mudmap to review a User&amp;rsquo;s access
to an Org and Device (permissions are omitted for now) by inspecting
the JWT. If the JWT has some limitation that I&amp;rsquo;ve not accounted for then
each request will require a DB lookup. For latency reasons I&amp;rsquo;d prefer
not to do that. If, that is the case it&amp;rsquo;s probably a prime reason to
transition to Litestream (assuming replication is working well).&lt;/p&gt;
&lt;p&gt;This is a ten thousand foot view and still needs a few things ironed out.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#mudmap #research #planning
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Twitch streams I follow</title><link>https://danielms.site/zet/2022/twitch-streams-i-follow/</link><pubDate>Mon, 09 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/twitch-streams-i-follow/</guid><description>&lt;h1 id="twitch-streams-i-follow"&gt;Twitch streams I follow&lt;/h1&gt;
&lt;p&gt;I watch Twitch at work but not logged in so I sometimes forget which
accounts I like to view.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/rwxrob"&gt;rwxrob&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/waylonwalker"&gt;waylongwalker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/mrdonbrown"&gt;mrdonbrown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/anthonywritescode"&gt;anthonywritescode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/krisnova"&gt;krisnova&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/het_tanis"&gt;het_tanis&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#twitch
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Cheatsheet: psql and pg_dump</title><link>https://danielms.site/zet/2022/cheatsheet-psql-and-pg_dump/</link><pubDate>Sat, 07 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/cheatsheet-psql-and-pg_dump/</guid><description>&lt;h1 id="cheatsheet-psql-and-pg_dump"&gt;Cheatsheet: psql and pg_dump&lt;/h1&gt;
&lt;p&gt;In my day job I use MariaDB so I often forget the syntax for Postgres and
I&amp;rsquo;m trying not to leverage graphical DB explorers as often. When jumping
on to containers from a k8s orchestration platform, you don&amp;rsquo;t often have
access to anything but the CLI.&lt;/p&gt;
&lt;h2 id="commands"&gt;Commands&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Login&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the container: &lt;code&gt;psql -U &amp;lt;username&amp;gt; -d &amp;lt;database&amp;gt;&lt;/code&gt;
On local host to db: &lt;code&gt;psql -h localhost -p 5432 -U dbuser -d db&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Not specifying the database will cause an error, unlike MariaDB which
just wants a username and password.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Show Tables&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;\dt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Desc Table&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;\d &amp;lt;table-name&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dump DB into File&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pg_dump -U &amp;lt;user&amp;gt; -d &amp;lt;database&amp;gt; &amp;gt;&amp;gt; sqldump.sql&lt;/code&gt; then run &lt;code&gt;docker cp&lt;/code&gt;
if its inside a container.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Restore DB from file&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;psql -f sqlfile.sql -U dbuser -p 5432 -h localhost -d db&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;**Install &lt;code&gt;psql&lt;/code&gt; without Postgres&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo apt install postgresql-client&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="still-stuck"&gt;Still stuck?&lt;/h2&gt;
&lt;p&gt;Instead of reaching for google, run &lt;code&gt;curl cht.sh/psql&lt;/code&gt; it is fantastic.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#database #psql #postgres
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Mudmap sub-account overview</title><link>https://danielms.site/zet/2022/mudmap-sub-account-overview/</link><pubDate>Sat, 07 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/mudmap-sub-account-overview/</guid><description>&lt;h1 id="mudmap-sub-account-overview"&gt;Mudmap sub-account overview&lt;/h1&gt;
&lt;p&gt;A rough idea of what this change should achieve.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;organisation&lt;/code&gt; is has a collection of users&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users&lt;/code&gt; has a relation to &lt;code&gt;permissions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;device&lt;/code&gt; belong to &lt;code&gt;organisation&lt;/code&gt;, not &lt;code&gt;users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Existing &lt;code&gt;users&lt;/code&gt;&amp;rsquo;s must be attached to a &lt;code&gt;organisation&lt;/code&gt; (probably by creating a &lt;code&gt;organisation&lt;/code&gt; using the
&lt;code&gt;user.Name&lt;/code&gt; field as &lt;code&gt;group.Name&lt;/code&gt; and adding them as a &lt;code&gt;users&lt;/code&gt; to that group)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;organisation&lt;/code&gt; owners can update &lt;code&gt;organisation&lt;/code&gt; info&lt;/li&gt;
&lt;li&gt;&lt;code&gt;permissions&lt;/code&gt; should be a Many-to-Many. &lt;code&gt;users&lt;/code&gt; can have many &lt;code&gt;permissions&lt;/code&gt; and the same
permission can belong to many &lt;code&gt;users&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Auth0 should also be aware of &lt;code&gt;users&lt;/code&gt; and &lt;code&gt;organisation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Auth0 should also be aware of permissions&lt;/li&gt;
&lt;li&gt;Auth0 should place &lt;code&gt;permissions&lt;/code&gt; within the token (if possible)&lt;/li&gt;
&lt;li&gt;Updates to the &lt;code&gt;permisions&lt;/code&gt; table must also be reflected in Auth0&lt;/li&gt;
&lt;li&gt;Updates to the &lt;code&gt;organisation&lt;/code&gt; table must be reflected in Auth0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Next Steps&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Test assumptions with Auth0&amp;rsquo;s Authorization and Group tooling&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;permissions&lt;/code&gt; and &lt;code&gt;organisation&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;Update &lt;code&gt;users&lt;/code&gt; table to have relation with &lt;code&gt;organisation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Create change &lt;code&gt;devices&lt;/code&gt; relation from &lt;code&gt;users&lt;/code&gt; to &lt;code&gt;organisation&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Write sync methods so that the DB and Auth0 remain in sync&lt;/li&gt;
&lt;li&gt;Middleware for checking a user can access a device based on JWT token fields&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a decent sized change with a few moving parts. I can possibly
remove the Auth0 lock-in but I see no good way of doing this without
forcing a DB lookup for each request (check permission and group). If
I use Auth0 I can hopefully keep it sync&amp;rsquo;d with the DB and embed that
data in the &lt;code&gt;app_metadata&lt;/code&gt; field of the token. A assumption I need to
test fully before too much investment in code.&lt;/p&gt;
&lt;p&gt;Related:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://danielms.site/20220505024039/"&gt;20220505024039 Mudmap sub-accounts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Updates&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve added &lt;code&gt;app_metadata&lt;/code&gt; to the JWT. This can now be plucked off on the
backend.&lt;/p&gt;
&lt;p&gt;Its possible to use M2M API key to make changes to the Group,
Permissions and Roles of Users and organisation. However, the free-tier only
gives you 1000 calls per month (from what I can tell)&lt;/p&gt;
&lt;p&gt;API Docs: &lt;a href="https://auth0.com/docs/api/authorization-extension"&gt;https://auth0.com/docs/api/authorization-extension&lt;/a&gt;
Getting a token: &lt;a href="https://auth0.com/docs/api/authorization-extension?shell#get-an-access-token"&gt;https://auth0.com/docs/api/authorization-extension?shell#get-an-access-token&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Some calls examples:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# get all groups&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curlie https://mudmap.au12.webtask.io/&amp;lt;URL&amp;gt;/api/groups -H &lt;span class="s2"&gt;&amp;#34;authorization: Bearer &amp;lt;TOKEN&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Returns&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;groups&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;8931fb3a-8163-4c8a-8534-b5cc43381172&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Test-Group&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;For Testing Only&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;members&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;auth0|61de4f3e8e3c6000710b8c0d&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;mappings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;test-description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;new-test&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0fdf83fe-32db-463c-b362-4e29c0817781&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;mappings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;members&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;total&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#mudmap #planning
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-05-06</title><link>https://danielms.site/zet/2022/wgd-fri-2022-05-06/</link><pubDate>Fri, 06 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-05-06/</guid><description>&lt;h1 id="wgd-fri-2022-05-06"&gt;WGD Fri 2022-05-06&lt;/h1&gt;
&lt;p&gt;Preparing Mudmap for a big change and researching account grouping functionality.&lt;/p&gt;
&lt;h2 id="mudmap"&gt;Mudmap&lt;/h2&gt;
&lt;p&gt;Mudmap&amp;rsquo;s underlying API has seen a big update and I spent a fair chunk of time testing it to make sure no breaking changes are introduced.&lt;/p&gt;
&lt;p&gt;I can now create a Go client from the &lt;code&gt;openapi.json&lt;/code&gt; the API provides. But, I find the auto generated code that gets created really hard to follow. I guess that is the point, its a contract between the API and the client. Nonetheless, I&amp;rsquo;m not going to use the tools I&amp;rsquo;ve tried but instead just use it to influence how &lt;em&gt;I&lt;/em&gt; build each endpoint.&lt;/p&gt;
&lt;p&gt;Also, I started researching and thinking through how I am going to create an account hierarchy within Mudmap. Users want to have &lt;em&gt;teams&lt;/em&gt; or &lt;em&gt;groups&lt;/em&gt; with RBAC policies. Since I use Auth0 as my authentication provider, I am somewhat pigeon holed to use their methods. From what I can tell so far, their Organisations product is not what I want. It is also too expensive. Instead I am looking at creating some custom wrapping code to extend their &lt;em&gt;groups&lt;/em&gt; extension with my own database. Research continues as there are some considerations such a latency here.&lt;/p&gt;
&lt;p&gt;Updated Mudmap&amp;rsquo;s &lt;code&gt;goreleaser&lt;/code&gt; builds to use 1.18 as I&amp;rsquo;ve started using some generics where it makes it easier to grok that piece of code.&lt;/p&gt;
&lt;p&gt;Noticed that Mudmap has a very small memory leak somewhere. Over the course of a week the memory increases about 5mb which is nothing. Still, I hate that it is there but have decided put it on the backburner while I go after more critical items.&lt;/p&gt;
&lt;p&gt;Related:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/danielmichaels/zet/tree/main/20220505024039"&gt;Mudmap Sub-Accounts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wrote the April retrospective but haven&amp;rsquo;t published it yet. Still need to proof it and make sure the standard grammar issues are fixed up.&lt;/p&gt;
&lt;p&gt;Released a number of small updates for my &lt;a href="https://github.com/danielmichaels/zet-cmd"&gt;zet-cmd&lt;/a&gt; package. Really enjoying working on it &lt;em&gt;and&lt;/em&gt; using it to catalogue my thoughts. People can freely read it which is fine but I feel zero regrets from the poor wording or brain spew that gets placed there.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Worked nearly 50 hours this week on a big deployment. It&amp;rsquo;s bloody frustrating working in the constrained environment I call work. Aside from that, it has been a good pairing with a much more senior engineer during the process.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#whatgotdone
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Mudmap Org Permissions</title><link>https://danielms.site/zet/2022/mudmap-org-permissions/</link><pubDate>Thu, 05 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/mudmap-org-permissions/</guid><description>&lt;h1 id="mudmap-org-permissions"&gt;Mudmap Org Permissions&lt;/h1&gt;
&lt;p&gt;Mudmap must be able to provide user accounts under an account root. This
account root will be called an &lt;code&gt;organisation&lt;/code&gt;. All users will belong to
an &lt;code&gt;organisation&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;RBAC policies are applied per-user according to their level of access.&lt;/p&gt;
&lt;p&gt;As a general guide, a &lt;code&gt;root&lt;/code&gt; account is created from which all accounts
for that company or group are then underneath. This account is the
&lt;code&gt;organisation&lt;/code&gt; owner for lack of better wording. It might be prudent to
allow multiple &lt;code&gt;root&lt;/code&gt; account holders.&lt;/p&gt;
&lt;p&gt;This means devices are attached to an account or at least an account user. Users
associated to that user can see any device in the hierarchy. At the moment, only
a single user account can have and see its own devices. This will need to be radically
altered to accommodate these changes.&lt;/p&gt;
&lt;h2 id="rbac-policies"&gt;RBAC Policies&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Account Type&lt;/th&gt;
					&lt;th&gt;Payments&lt;/th&gt;
					&lt;th&gt;Privileges&lt;/th&gt;
					&lt;th&gt;Read&lt;/th&gt;
					&lt;th&gt;Write&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Root&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Privileged&lt;/td&gt;
					&lt;td&gt;n&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Manager&lt;/td&gt;
					&lt;td&gt;n&lt;/td&gt;
					&lt;td&gt;n&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;User&lt;/td&gt;
					&lt;td&gt;n&lt;/td&gt;
					&lt;td&gt;n&lt;/td&gt;
					&lt;td&gt;y&lt;/td&gt;
					&lt;td&gt;n&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Custom&lt;/td&gt;
					&lt;td&gt;?&lt;/td&gt;
					&lt;td&gt;?&lt;/td&gt;
					&lt;td&gt;?&lt;/td&gt;
					&lt;td&gt;?&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;managers cannot elevate privileges (only privileged account or root)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="questions"&gt;Questions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;How to create this type of account setup with Auth0? Do I need to hack something together&lt;/li&gt;
&lt;li&gt;How to grant RBAC - JWT or DB look up on each request? JWT best case, DB worse but either way RBAC should be stored in a DB for reference in admin panels&lt;/li&gt;
&lt;li&gt;How to associate users to the root account programmatically with backward compat&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#mudmap #research
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>April 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-april-2022/</link><pubDate>Wed, 04 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-april-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Recharged after a wonderful holiday spent visiting family and friends who we haven&amp;rsquo;t seen since
Covid started.&lt;/p&gt;
&lt;h2 id="highlight"&gt;Highlight&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Went on a relaxing holiday in &lt;a href="https://en.wikipedia.org/wiki/Alice_Springs"&gt;Alice Springs&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2022/retrospective-march-2022/"&gt;March&amp;rsquo;s Retrospective&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="push-two-mudmap-features-to-production"&gt;Push two Mudmap features to production&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: I did push some very small fixes but nothing substantial&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C+&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From the outset, I over committed myself this month. Nearly ten days of this month was spent on
holiday where I did very little active development on Mudmap. Although, I did resolve some customer
inquiries, analyse bug reports and speak with customers.&lt;/p&gt;
&lt;p&gt;A substantial update was released for the API that is installed on the firewalls. It was officially
released at the end of the month but has been available in beta for several months. In
preparation for this, I&amp;rsquo;ve been getting some things in order to make the transition easy.
Included in the release is support for pfSense+ &lt;code&gt;v22.01&lt;/code&gt; - something I did not expect to make it
out of &lt;code&gt;beta&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In prep for this, I&amp;rsquo;ve been working on building a development environment and acquiring the
authorizations to deploy pfSense+ testing devices. This includes local virtual machines and
cloud instances.&lt;/p&gt;
&lt;p&gt;Other than that, I made some fixes to the user interface such as typo&amp;rsquo;s on buttons. Mudmap&amp;rsquo;s
frontend will also ask users to refresh their page if a newer version is available whilst they
are actively using it. Though I have found that caching on Next.js seems to intelligent enough
to detect changes to the assets pretty quickly.&lt;/p&gt;
&lt;h3 id="walk-to-alice-springs-telegraph-station"&gt;Walk to &lt;a href="https://en.wikipedia.org/wiki/Alice_Springs"&gt;Alice Springs&lt;/a&gt; telegraph station&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Easy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A silly goal but something I set purely to ensure I took my little girl on a decent walk through
some of Australia&amp;rsquo;s most beautiful country.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had a few little holiday breaks here and there over the last year or so. None of them hold
a candle to how relaxed and re-charged I feel after this one. Alice, is a remarkable little
place of only about 25,000 people, and stands in the centre of Australia. The nearest cities
are &lt;a href="https://www.google.com/maps/dir/alice+springs/Darwin,+Northern+Territory/@-18.0133453,128.0649485,6z/data=!3m1!4b1!4m14!4m13!1m5!1m1!1s0x2b321944be8f1331:0x50217a82a254fd0!2m2!1d133.8807471!2d-23.698042!1m5!1m1!1s0x2cc0a0fc9f59043f:0x30217a82a247c20!2m2!1d130.8444446!2d-12.4637333!3e0"&gt;Darwin&lt;/a&gt; and &lt;a href="https://www.google.com/maps/dir/alice+springs/Adelaide+SA/@-29.2375669,131.3978583,6z/data=!3m1!4b1!4m14!4m13!1m5!1m1!1s0x2b321944be8f1331:0x50217a82a254fd0!2m2!1d133.8807471!2d-23.698042!1m5!1m1!1s0x6ab735c7c526b33f:0x4033654628ec640!2m2!1d138.6007456!2d-34.9284989!3e0"&gt;Adelaide&lt;/a&gt; both being about a 15-hour drive (click the links).
Australia&amp;rsquo;s vastness is hard to contemplate for many - driving 15 hours in Europe, Asia or the
America&amp;rsquo;s will have you passing by many large cities. In &lt;a href="https://en.wikipedia.org/wiki/Coober_Pedy"&gt;Coober Pedy&lt;/a&gt;, on the way to
Adelaide, people live underground as it&amp;rsquo;s literally too bloody hot on the surface!&lt;/p&gt;
&lt;p&gt;The picture below is from a short overnighter out bush - my daughters first night in a swag. She
was absolutely thrilled to be sleeping under the stars and to wake up as the sun rises. In sum,
it was a fantastic holiday with friends and family.&lt;/p&gt;
&lt;p&gt;&lt;img src="alice.jpg" alt=""&gt;&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Learn Golang&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a developer and looking to pick up a new language then you should have a
look at Go. I had toyed around with it on and off for a while but started writing a lot more
stuff in it towards the end of last year. Now, I reach for it whenever I&amp;rsquo;m starting something
that is more than a &lt;em&gt;simple&lt;/em&gt; bash script. There is a lot to love but for me, it&amp;rsquo;s Go&amp;rsquo;s ability
to be compiled to any operating system and how easy it is to bundle assets (such as HTML) into
the binary. Go is truly portable and after years with Python and Javascript, it is such a weight
off my shoulders. Understanding the language can also be really helpful when figuring out errors
with cloud tools too. After all, basically every cloud project these days is written in it.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;A quiet month on the &lt;em&gt;retrospective&lt;/em&gt; front but a big month for me personally. Looking forward to
writing a more lengthy post in June.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be more time aware when making plans, i.e. I did not really factor in how little development work would get done one Mudmap during my holiday.&lt;/li&gt;
&lt;li&gt;Update the roadmap more frequently as this is something users are asking about often.&lt;/li&gt;
&lt;li&gt;Release the monthly retro in a more timely manner.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Actually took a proper amount of time off and feel recharged&lt;/li&gt;
&lt;li&gt;Spent sometime exploring a few other programming projects&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deploy pfSense+ capable Mudmap&lt;/li&gt;
&lt;li&gt;Get started on multi-account support (a big customer request)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-04-01"
 let end_date = "2022-04-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2022-04-01"
 let end_date = "2022-04-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-04-01"
 let end_date = "2022-04-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>Microservice contracts can be coupled</title><link>https://danielms.site/zet/2022/microservice-contracts-can-be-coupled/</link><pubDate>Mon, 02 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/microservice-contracts-can-be-coupled/</guid><description>&lt;h1 id="microservice-contracts-can-be-coupled"&gt;Microservice contracts can be coupled&lt;/h1&gt;
&lt;p&gt;Microservices aren&amp;rsquo;t evil or the saviour, but they are often
touted as the solution to a lot of problems. One of the biggest
microservice misconceptions is &lt;em&gt;decoupling&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A service which sends stuff to a queue to be consumed by
another service is not magically decoupled. It takes work
and consistency to do that. Lately, I&amp;rsquo;ve been seeing a lot
of service code with references to things that are only a
concern for the consuming services. I.e. they&amp;rsquo;re coupled.&lt;/p&gt;
&lt;p&gt;Further, if service A needs more data from another service, now I need
to touch two code bases to make that change. If I deploy one without
updating the other, it could result in a crash (though not always).&lt;/p&gt;
&lt;p&gt;I like writing services when it makes sense but I do not agree its the
panacea of all coding. For a lot of things, monolith&amp;rsquo;s or large
&lt;em&gt;services&lt;/em&gt; are more efficient. This holds especially true if your team
size is small.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#rant #microservices #design
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Go test -race requires CGO_ENABLED=1</title><link>https://danielms.site/zet/2022/go-test--race-requires-cgo_enabled1/</link><pubDate>Sun, 01 May 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/go-test--race-requires-cgo_enabled1/</guid><description>&lt;h1 id="go-test--race-requires-cgo_enabled1"&gt;Go test -race requires CGO_ENABLED=1&lt;/h1&gt;
&lt;p&gt;To run &lt;code&gt;go test -race&lt;/code&gt; &lt;code&gt;CGO_ENABLED=1&lt;/code&gt; must be set. I do not use &lt;code&gt;CGO&lt;/code&gt;
as I do not want any linking.&lt;/p&gt;
&lt;p&gt;The workaround is simple, export it for that command.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;race&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#golang #cgo #testing
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>pfSense-api v1.4.0</title><link>https://danielms.site/zet/2022/pfsense-api-v1.4.0/</link><pubDate>Sat, 30 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/pfsense-api-v1.4.0/</guid><description>&lt;h1 id="pfsense-api-v140"&gt;pfSense-api v1.4.0&lt;/h1&gt;
&lt;p&gt;Some excellent news, &lt;code&gt;pfsense-api&lt;/code&gt; has been updated to &lt;code&gt;v1.4.0&lt;/code&gt; which brings
several fantastic changes.&lt;/p&gt;
&lt;p&gt;Highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support for pfSense+ (this could be a catalyst for many new customers)&lt;/li&gt;
&lt;li&gt;Basic Auth instead of sending in the body* (see notes)&lt;/li&gt;
&lt;li&gt;OpenAPI swagger endpoint (previously it was a markdown doc)&lt;/li&gt;
&lt;li&gt;API IP white listing (might make HTTP access more palatable)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Notes&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&lt;strong&gt;Basic Auth&lt;/strong&gt;&lt;/em&gt; is better than munging a body to pass authentication but I
cannot switch completely for backward compatibility reasons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pfsense #mudmap
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>WGD Fri 2022-04-29</title><link>https://danielms.site/zet/2022/wgd-fri-2022-04-29/</link><pubDate>Fri, 29 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/wgd-fri-2022-04-29/</guid><description>&lt;h1 id="wgd-fri-2022-04-29"&gt;WGD Fri 2022-04-29&lt;/h1&gt;
&lt;h2 id="mudmap"&gt;&lt;a href="https://mudmap.io"&gt;Mudmap&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I added some extra notifications, namely webhooks, whenever a user
increments or decrements their device numbers in Stripe. I.e. when
a payment intent is sent for a subscription.&lt;/p&gt;
&lt;p&gt;Also, spent some time investigating firewall connection issues which
a user notified me about. Ultimately, I could not replicate the issue.
What they provided me works but I look forward to their response as
it could be affecting other users as most don&amp;rsquo;t report issues.&lt;/p&gt;
&lt;p&gt;I started mulling over the idea of switching to using HTTP, or
providing it in addition to SSH for Mudmap connections. I understand
that this means exposing the firewall&amp;rsquo;s user interface to the internet
but it would be locked down to a few IP&amp;rsquo;s. Which is what happens with
SSH currently - though SSH is more secure than a web server. Needs
more thought but what makes this even more appealing is the API client
I use is soon releasing a OpenAPI document so I should be able to
auto-generate HTTP endpoints.&lt;/p&gt;
&lt;h2 id="ds"&gt;&lt;a href="https://github.com/danielmichaels/ds"&gt;ds&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I integrated &lt;a href="https://github.com/zet-cmd"&gt;zet-cmd&lt;/a&gt; into &lt;a href="https://github.com/danielmichaels/ds"&gt;ds&lt;/a&gt; and updated the underlying dependencies.&lt;/p&gt;
&lt;h2 id="zet-cmd"&gt;&lt;a href="https://github.com/zet-cmd"&gt;zet-cmd&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have too many notebooks. My writings and scribbles go into a book
and get lost, forever gone and never looked upon again. So I am
&lt;em&gt;kinda&lt;/em&gt; re-inventing the wheel yet again and trying something I
have failed at before, taking my notes online.&lt;/p&gt;
&lt;p&gt;So, I decieded to create a &lt;a href="https://luhmann.surge.sh"&gt;zettelkasten&lt;/a&gt; tool for cataloguing my
various thoughts in a way that is publicly accessible. Unlike my
&lt;a href="https://danielms.site"&gt;blog&lt;/a&gt; which I tend to take a little more
time curating, these entries should be more raw with less care for
grammar or correctness.&lt;/p&gt;
&lt;p&gt;And, because I&amp;rsquo;m a developer, I created a tool to do this. It uses
&lt;a href="https://github.com/rwxrob/bonzai"&gt;bonzai&lt;/a&gt; to create a simple (easy to use for me) CLI tool for writing
markdown files. This is written in my zet repo and copy pasted into
&lt;a href="https://whatgotdone.com"&gt;What Got Done&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So far, &lt;a href="https://github.com/zet-cmd"&gt;zet-cmd&lt;/a&gt; can do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create new zet&amp;rsquo;s&lt;/li&gt;
&lt;li&gt;edit zet&amp;rsquo;s&lt;/li&gt;
&lt;li&gt;search by title and tags&lt;/li&gt;
&lt;li&gt;commit and push to GitHub&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Work has been pretty interesting and fun this week. I&amp;rsquo;ve been pulling
longer hours but the days have been good. A lot of k8s work which I
really enjoy.&lt;/p&gt;</description></item><item><title>Golang URL Params</title><link>https://danielms.site/zet/2022/golang-url-params/</link><pubDate>Thu, 28 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/golang-url-params/</guid><description>&lt;h1 id="golang-url-params"&gt;Golang URL Params&lt;/h1&gt;
&lt;p&gt;To create URL encoded parameters on a URL in Go.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// example from a Zulip webhook&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Values&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;to&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;stream-name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;topic&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;topic&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;stream&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;here is the content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://example.com?%s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will create the URL as var &lt;code&gt;u&lt;/code&gt; with all the parameters
correctly encoded. This could then be used in &lt;code&gt;http.NewRequest&lt;/code&gt;
like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-golang" data-lang="golang"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// skip err handling for brevity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#golang #web
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Initial Zettelkasten</title><link>https://danielms.site/zet/2022/initial-zettelkasten/</link><pubDate>Tue, 26 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/initial-zettelkasten/</guid><description>&lt;h1 id="initial-zettelkasten"&gt;Initial Zettelkasten&lt;/h1&gt;
&lt;p&gt;This marks the very first Zettelkasten entry in this repo. To do this I&amp;rsquo;ve created my own
&lt;code&gt;Zet&lt;/code&gt; tool called &lt;a href="https://github.com/danielmichaels/zet-cmd"&gt;zet-cmd&lt;/a&gt; written in Golang. It uses &lt;a href="https://github.com/rwxrob/bonzai"&gt;Bonzai&lt;/a&gt; by &lt;a href="https://github.com/rwxrob"&gt;rwxrob&lt;/a&gt; and is heavily
inspired by his own &lt;a href="https://github.com/rwxrob/zet"&gt;zet&lt;/a&gt; repo.&lt;/p&gt;
&lt;p&gt;I needed a way to capture small, digestible nuggets of wisdom and other thoughts or learnings
in a manner that let&amp;rsquo;s me do it without needing a full blog post. It also needs to be
searchable publicly and using GitHub makes that pretty simple.&lt;/p&gt;
&lt;p&gt;So far &lt;a href="https://github.com/danielmichaels/zet-cmd"&gt;zet-cmd&lt;/a&gt; can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create new Zet&amp;rsquo;s&lt;/li&gt;
&lt;li&gt;Edit existing ones&lt;/li&gt;
&lt;li&gt;Search across all Zet&amp;rsquo;s&lt;/li&gt;
&lt;li&gt;Generate a url with a search term for the repo on GitHub&lt;/li&gt;
&lt;li&gt;Retrieve the latest zet for easier editing&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the future more commands will be added which make life easier. More helper commands to make
piping data into Vim will be added in the future.&lt;/p&gt;
&lt;p&gt;Some example usages:&lt;/p&gt;
&lt;p&gt;Creating a GitHub.com search query with &lt;code&gt;zet q &amp;quot;search term&amp;quot;&lt;/code&gt; would return:
&lt;a href="https://github.com/danielmichaels/zet/search?q=search+term"&gt;https://github.com/danielmichaels/zet/search?q=search+term&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#zettelkasten #golang #bonzai #rwxrob
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Vim Filters</title><link>https://danielms.site/zet/2022/vim-filters/</link><pubDate>Tue, 26 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/vim-filters/</guid><description>&lt;h1 id="vim-filters"&gt;Vim Filters&lt;/h1&gt;
&lt;p&gt;Today I learnt about (and showed some vim-hater colleagues) vim filters.&lt;/p&gt;
&lt;p&gt;I am still at the point of consciously thinking about them. So, I&amp;rsquo;ve already noticed
a few spots where I could of used a filter but instead dropped back to what I know.&lt;/p&gt;
&lt;p&gt;A few contrived and simple examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the first zet with a #golang tag&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ds&lt;/span&gt; &lt;span class="nx"&gt;zet&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="nx"&gt;golang&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;awk&lt;/span&gt; &amp;#39;{&lt;span class="nx"&gt;print&lt;/span&gt; $&lt;span class="m"&gt;1&lt;/span&gt;} &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;head&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# &lt;span class="m"&gt;20220424000235&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Get unix time&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ds&lt;/span&gt; &lt;span class="nx"&gt;uniq&lt;/span&gt; &lt;span class="nx"&gt;second&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# &lt;span class="m"&gt;1650971184&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Line count for this document&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-vim" data-lang="vim"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wc&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; #
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="nx"&gt;zet&lt;/span&gt;&lt;span class="sr"&gt;/20220426105644/&lt;/span&gt;&lt;span class="nx"&gt;README&lt;/span&gt;.&lt;span class="nx"&gt;md&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These are so basic its not funny but gaining an awareness is the start. Conscious thought when
writing leads to the mental leaps which turn things like this into &lt;em&gt;muscle memory&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#vim
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>ANZAC Day COVID Bullshit</title><link>https://danielms.site/zet/2022/anzac-day-covid-bullshit/</link><pubDate>Mon, 25 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/anzac-day-covid-bullshit/</guid><description>&lt;h1 id="anzac-day-covid-bullshit"&gt;ANZAC Day COVID Bullshit&lt;/h1&gt;
&lt;p&gt;ANZAC Day Dawn Service in Perth 2022 is invite only with a maximum
attendance capacity of 500 due to COVID restrictions.&lt;/p&gt;
&lt;p&gt;The local football team - The West Coast Eagles - home attendance&amp;rsquo;s
for 2022:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Round 1: 20,932&lt;/li&gt;
&lt;li&gt;Round 3: 38,920&lt;/li&gt;
&lt;li&gt;Round 5: 42,888&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What a load of absolute bullshit. We all know why. There is no
money made from a memorial service but football, cha-ching.&lt;/p&gt;
&lt;p&gt;Risk versus Reward in capitalist society will forsaken those who made
the ultimate sacrifice to pursue a few Shekels.&lt;/p&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#covid #rant
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>How to check Go module versions</title><link>https://danielms.site/zet/2022/how-to-check-go-module-versions/</link><pubDate>Sun, 24 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/zet/2022/how-to-check-go-module-versions/</guid><description>&lt;h1 id="how-to-check-go-module-versions"&gt;How to check Go module versions&lt;/h1&gt;
&lt;p&gt;When creating &lt;code&gt;zet-cmd&lt;/code&gt; and using &lt;code&gt;go install&lt;/code&gt; to pull the latest version,
I was having some issues where it would not grab the most recent version.&lt;/p&gt;
&lt;p&gt;Here are some methods to find and then retrieve the version you want.&lt;/p&gt;
&lt;p&gt;To check all existing versions:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;go list -m -version github.com/danielmichaels/zet-cmd&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A list of compatible operators when using &lt;code&gt;go install&lt;/code&gt; or &lt;code&gt;go get&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specific version: &lt;code&gt;@v.1.3.4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Specific commit: &lt;code&gt;@abcdefgh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Specific branch: &lt;code&gt;@main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Version prefix: &lt;code&gt;@v2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Comparison: &lt;code&gt;@&amp;gt;=2.1.4&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Latest: &lt;code&gt;@latest&lt;/code&gt; (most common)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tags:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#golang #modules
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Github Vigilant Mode Setup</title><link>https://danielms.site/blog/github-vigilant-mode-setup/</link><pubDate>Sun, 17 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/github-vigilant-mode-setup/</guid><description>&lt;h2 id="setting-up-githubs-vigilant-mode"&gt;Setting up Github&amp;rsquo;s vigilant mode&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;create GPG key&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gpg --full-generate-key&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Pick &lt;code&gt;RSA&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enter a key of at least &lt;code&gt;4096&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Default expiration&lt;/li&gt;
&lt;li&gt;Enter &lt;code&gt;git config --global user.name&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;Enter &lt;code&gt;git config --global user.email&lt;/code&gt; value&lt;/li&gt;
&lt;li&gt;Add a comment describing it as &lt;code&gt;Github&lt;/code&gt; or similar&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Add the key to Github&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Retrieve the key ID and copy it into the clipboard.&lt;/li&gt;
&lt;li&gt;Run the follwing and grab the key ID on the &lt;code&gt;ssb&lt;/code&gt; line.
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❯ gpg --list-secret-keys --keyid-format&lt;span class="o"&gt;=&lt;/span&gt;long dan@danielms.site
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sec rsa4096/XXXXXXXXXXXXXXXX 2022-04-15 &lt;span class="o"&gt;[&lt;/span&gt;SC&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uid &lt;span class="o"&gt;[&lt;/span&gt;ultimate&lt;span class="o"&gt;]&lt;/span&gt; Daniel Michaels &amp;lt;dan@danielms.site&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssb rsa4096/XXXXXXXXXXXXXX8B 2022-04-15 &lt;span class="o"&gt;[&lt;/span&gt;E&lt;span class="o"&gt;]&lt;/span&gt; &amp;lt;-- copy after rsa4096/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Github expects the Private key file so next retrieve it using &lt;code&gt;gpg --armor --export XXXXXXXXXXXXXX8B&lt;/code&gt;. Copy the output to the clipboard.&lt;/p&gt;
&lt;p&gt;Paste it into the GPG keys page inside Github using their
&lt;a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-new-gpg-key-to-your-github-account"&gt;guide&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure your commits (and tags) are signed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The easiest way is to setup every commit as signed lest you forget to sign a commit.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git config --global commit.gpgsign true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git config --global user.signingkey ABCDEF01&lt;/code&gt; (where ABCDEF01 is the fingerprint of the key to use)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git config --global alias.logs &amp;quot;log --show-signature&amp;quot;&lt;/code&gt; (now available as $ git logs)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="copying-gpg-key-between-devices"&gt;Copying GPG key between devices&lt;/h2&gt;
&lt;p&gt;If you have more than one device and do not want to have several keys but instead use the same
key across all devices, there are a few steps.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gpg --list-secret-keys user@example.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gpg --list-secret-keys user@example.com &amp;gt; private.key&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Copy the key to the device using &lt;code&gt;scp&lt;/code&gt; or similar and then install it into the &lt;code&gt;gpg&lt;/code&gt; keychain.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gpg --import private.key&lt;/code&gt; is touted to work however this did not work for me on Ubuntu
20.04.Instead, I found &lt;code&gt;gpg --batch --import private.key&lt;/code&gt; worked as expected.&lt;/p&gt;
&lt;h2 id="deleting-tags"&gt;Deleting tags&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re like me and push tags without signing them first (fixed by following the above guide)
how do you delete them?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# delete local tag &amp;#39;12345&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git tag -d &lt;span class="m"&gt;12345&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# delete remote tag &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push --delete origin tagName
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a quick down and dirty on setting up GPG keys, git and GitHub.&lt;/p&gt;</description></item><item><title>March 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-march-2022/</link><pubDate>Wed, 06 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-march-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;After taking some time off to have fun and experiment, I got back into the swing of Mudmap
development.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Released several updates to Mudmap&lt;/li&gt;
&lt;li&gt;Got Covid (not really a highlight but noteworthy)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2022/retrospective-feb-2022/"&gt;Feb&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I did not set any goals for this month. My partner has been quite unwell, and I decided to take
some pressure off and be more available for her.&lt;/p&gt;
&lt;h2 id="month-overview"&gt;Month overview&lt;/h2&gt;
&lt;p&gt;I discovered the project &lt;a href="https://alpinejs.dev"&gt;Alpine.js&lt;/a&gt; this month and was really intrigued by how
user-friendly it is. I am pretty comfortable using React these days but the added complexity of
running a separate frontend can get a little annoying, especially for simple app&amp;rsquo;s. Setting up
Alpine is a &lt;a href="https://danielms.site/blog/alpine.js-and-tailwind-html-setup/"&gt;cinch&lt;/a&gt; and can easily be integrated into any web application by either dropping in
a link to the CDN, or by creating a bundle for it and linking it directly.&lt;/p&gt;
&lt;p&gt;Where &lt;a href="https://alpinejs.dev"&gt;Alpine&lt;/a&gt; shines is when coupled with traditional web applications such as Django,
or Go templates. To get more familiar with it, I refactored an existing app of mine - &lt;a href="https://tars.run?ref=wgd"&gt;tars.run&lt;/a&gt;.
It was originally built with a Next.js frontend but was overkill for what it does. Now, instead
of needing two applications, the entire thing including database runs inside a single binary. It
is refreshingly easy building app&amp;rsquo;s this way.&lt;/p&gt;
&lt;h2 id="mudmap-updates"&gt;Mudmap updates&lt;/h2&gt;
&lt;p&gt;I have received a number of requests from customers asking Mudmap to support devices which do
not have static IP addresses. I finally added this feature and it was so simple that I regret
not doing it sooner.&lt;/p&gt;
&lt;p&gt;Mudmap now shows each device&amp;rsquo;s DHCP lease table too. This feature is not fully finished as
you cannot update any of the leases, only view them. I chose to release it in stages so
that customers can start using it straight away, rather than having to wait for all aspects of
that feature to be completed first. This is probably a theme I will continue with, and it allows
for a faster capture of feedback too.&lt;/p&gt;
&lt;p&gt;The application user interface also received a bit of love. Most of the tables now support
pagination by default, have smaller margins and where appropriate have tool-tips to reduce
ambiguity. Many of the table buttons are now smaller, or represented by a more familiar icon - the
play button for example. These small fixes do a lot to make the user interface feel more polished.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;Re-watch Seinfeld. It&amp;rsquo;s been such a brilliant way to decompress before bed and even after 30 years
it is still hilariously relevant. I am about half-way through the entire show after nearly a
month of watching an episode or four each evening. So good.&lt;/p&gt;
&lt;p&gt;I have completely moved from Arch (actually, Manjaro) to Ubuntu and couldn&amp;rsquo;t be happier.
Honestly, I&amp;rsquo;m not sure if I&amp;rsquo;m missing anything from the move - any package that I had on Arch I
probably have on Ubuntu. And, I actually like &lt;a href="https://snapcraft.io"&gt;snap&lt;/a&gt; packages - I said it! I&amp;rsquo;ve had a couple of
little issues, but they&amp;rsquo;ve all been super easy fixes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;fighting words&lt;/strong&gt;: Ubuntu is the closest thing we have to linux desktop, and I&amp;rsquo;m
now a &lt;em&gt;recommender&lt;/em&gt; of the distro.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;I expected this month to unproductive but even after getting Covid, it still ended up being quite
good. I was able to use my time to experiment with things and just have some fun which gave me
the break I needed to get back into Mudmap development. It was also good just coming home and
relaxing some nights, without the guilt I&amp;rsquo;ve put on myself to keep working on things like Mudmap.
This has been especially important as my day job&amp;rsquo;s workload has increased. In all, March was a
far better month than February.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write in my journal every day, even if only a couple of words&lt;/li&gt;
&lt;li&gt;Read more often before bed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I Wrote two short blog posts this month&lt;/li&gt;
&lt;li&gt;Didn&amp;rsquo;t take myself too seriously!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Push two Mudmap features to production, no matter how small&lt;/li&gt;
&lt;li&gt;Walk with my daughter to the &lt;a href="https://en.wikipedia.org/wiki/Alice_Springs_Telegraph_Station"&gt;telegraph station&lt;/a&gt; whilst we&amp;rsquo;re in &lt;a href="https://en.wikipedia.org/wiki/Alice_Springs"&gt;Alice Springs&lt;/a&gt; in April&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-03-01"
 let end_date = "2022-03-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2022-03-01"
 let end_date = "2022-03-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-03-01"
 let end_date = "2022-03-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>Citrix Workspaces SSL Error on Linux Fix</title><link>https://danielms.site/blog/citrix-workspaces-ssl-error-on-linux-fix/</link><pubDate>Fri, 01 Apr 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/citrix-workspaces-ssl-error-on-linux-fix/</guid><description>&lt;p&gt;TIL: Citrix on linux does not work out of the box.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t know why it&amp;rsquo;s like this and honestly don&amp;rsquo;t really care what the excuse it - it feels like a
perfect example of how much care Citrix puts into its products.&lt;/p&gt;
&lt;p&gt;This quick post goes over how to get it working.&lt;/p&gt;
&lt;h2 id="overview"&gt;Overview&lt;/h2&gt;
&lt;p&gt;You need to download Citrix&amp;rsquo;s certificate and put it in the ICACClient&amp;rsquo;s keystore - which is
ridiculous.&lt;/p&gt;
&lt;h2 id="download-the-certificate"&gt;Download the Certificate&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Head to &lt;a href="https://www.citrix.com/"&gt;Citrix&lt;/a&gt;&amp;rsquo;s website.&lt;/li&gt;
&lt;li&gt;In your browser&amp;rsquo;s navigation bar there is likely a &lt;em&gt;lock&lt;/em&gt; symbol. Clicking on this should give
some info about the certificate. Click around here until you end up at a page with the cert&amp;rsquo;s
information.
&lt;img src="https://danielms.site/images/cert-details.png" alt="" title="view certificate image"&gt;&lt;/li&gt;
&lt;li&gt;In the certificate&amp;rsquo;s &lt;em&gt;about&lt;/em&gt; page, scroll down until you hit a PEM download button and click it.
&lt;img src="https://danielms.site/images/pem-dl.png" alt="" title="PEM file download"&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="place-certificate-in-citrix-keystore"&gt;Place certificate in Citrix keystore&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Change the PEM file to a &lt;code&gt;.crt&lt;/code&gt; using &lt;code&gt;cp cert.pem cert.crt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo&lt;/code&gt; up using &lt;code&gt;sudo su&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Change the permission to 644 with &lt;code&gt;chmod 644 cert.crt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Make owner root with &lt;code&gt;chown root:root cert.crt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Move this file into the keystore by running &lt;code&gt;cp cert.crt /opt/Citrix/ICAClient/keystore/cacerts/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/opt/Citrix/ICAClient/util/ctx_rehash&lt;/code&gt; to make Citrix recognise the new cert&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Doing these steps should resolve the SSL error and allow you to use Citrix as expected.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; These steps assume Ubuntu 20.04 and &lt;code&gt;icaclient_22.3.0.24_amd64.deb&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://support.citrix.com/article/CTX231524"&gt;https://support.citrix.com/article/CTX231524&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Alpine.js and Tailwind HTML setup</title><link>https://danielms.site/blog/alpine.js-and-tailwind-html-setup/</link><pubDate>Sat, 12 Mar 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/alpine.js-and-tailwind-html-setup/</guid><description>&lt;h1 id="plain-html-with-alpine-and-tailwind-css"&gt;Plain HTML with Alpine and Tailwind CSS&lt;/h1&gt;
&lt;p&gt;Today I set up a Go project using &lt;a href="https://tailwindcss.com"&gt;Tailwind&lt;/a&gt; and &lt;a href="https://alpinejs.dev"&gt;Alpine.js&lt;/a&gt;. This web application uses Go
templates so there was no need for any React or other javascript frontend. I did want dropdowns
and to use tailwind without a CDN. This meant I needed to install some NPM packages and include
some javascript to facilitate the dropdowns.&lt;/p&gt;
&lt;p&gt;This is the minimum requirement to get a basic Tailwind and Alpine system set up for a simple Go
web application - it could extend to any other language but is not tested.&lt;/p&gt;
&lt;h2 id="pre-amble"&gt;Pre-amble&lt;/h2&gt;
&lt;p&gt;This project is laid out with the static files and html inside a directory named &lt;code&gt;ui&lt;/code&gt; within
their own directories.&lt;/p&gt;
&lt;p&gt;Before the setup and initialisation of Alpine and Tailwind, the directory looks something like
this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ui
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── base.layout.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── footer.partial.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── home.page.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── navbar.partial.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── static
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── css
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── main.css
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── ui.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="installing-and-compiling-tailwind"&gt;Installing and compiling Tailwind&lt;/h2&gt;
&lt;p&gt;First thing to do is install Tailwind. I use &lt;code&gt;yarn&lt;/code&gt; but I&amp;rsquo;ve used &lt;code&gt;npm&lt;/code&gt; here because its more
widely used.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -D tailwindcss 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# I also installed these tailwind plugins which I needed for the layout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#npm install -D @tailwindcss/forms @tailwind/aspect-ration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx tailwindcss init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This creates a &lt;code&gt;tailwind.config.js&lt;/code&gt; file which needs to be updated to reflect our directory
structure and template file types.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./ui/**/*tmpl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="nx"&gt;THIS&lt;/span&gt; &lt;span class="nx"&gt;IS&lt;/span&gt; &lt;span class="nx"&gt;IMPORTANT&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@tailwindcss/aspect-ratio&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;@tailwindcss/forms&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What tripped me up was the &lt;code&gt;content: []&lt;/code&gt; block. My Go templates use the following naming
convention; &lt;code&gt;&amp;lt;name&amp;gt;.&amp;lt;type&amp;gt;.tmpl&lt;/code&gt;. The types are &lt;code&gt;layout&lt;/code&gt;,&lt;code&gt;page&lt;/code&gt; and &lt;code&gt;partial&lt;/code&gt; but it always ends
with &lt;code&gt;tmpl&lt;/code&gt;. Initially, I was using &lt;code&gt;.html&lt;/code&gt; because I wasn&amp;rsquo;t paying attention and copying other
guides. This time I am catching all &lt;code&gt;tmpl&lt;/code&gt; files inside the &lt;code&gt;ui&lt;/code&gt; folder.&lt;/p&gt;
&lt;p&gt;I then created a &lt;code&gt;npm script&lt;/code&gt; block entry into the &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;build-css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tailwindcss -i ui/static/css/main.css -o ui/static/css/theme.css -m&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;devDependencies&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;@tailwindcss/aspect-ratio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^0.4.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;@tailwindcss/forms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^0.5.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;build-css&lt;/code&gt; will create a file called &lt;code&gt;theme.css&lt;/code&gt; in the &lt;code&gt;ui/static/css&lt;/code&gt; directory which is then
referenced inside my &lt;code&gt;base.layout.tmpl&lt;/code&gt; template. The &lt;code&gt;-m&lt;/code&gt; minifies it, which I do for prod and
dev because this file is never touched.&lt;/p&gt;
&lt;p&gt;The above step is predicated on the existence of a file called &lt;code&gt;main.css&lt;/code&gt; inside the
&lt;code&gt;ui/static/css&lt;/code&gt; directory. For clarity my &lt;code&gt;main.css&lt;/code&gt; looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-css" data-lang="css"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&amp;#34;&lt;/span&gt; &lt;span class="nt"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="nt"&gt;tailwindcss&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;docs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nt"&gt;installation&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;Step&lt;/span&gt; &lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;tailwind&lt;/span&gt; &lt;span class="nt"&gt;base&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;tailwind&lt;/span&gt; &lt;span class="nt"&gt;components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;tailwind&lt;/span&gt; &lt;span class="nt"&gt;utilities&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alternatively, you could run just run the compiler (&lt;code&gt;build-css&lt;/code&gt;) in a bash script, CI pipeline
or Dockerfile.&lt;/p&gt;
&lt;h2 id="alpinejs-set-up"&gt;Alpine.js Set up&lt;/h2&gt;
&lt;p&gt;Next, bundle up Alpine inside the application without needed to use the CDN.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install alpinejs esbuild
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alpine needs to be instantiated, so I created an &lt;code&gt;index.js&lt;/code&gt; at the root of the directory and
called Alpine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Alpine&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;alpinejs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Alpine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Alpine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Alpine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I add an extra script to the &lt;code&gt;package.json&lt;/code&gt; which will compile Alpine using &lt;a href="https://github.com/evanw/esbuild"&gt;esbuild&lt;/a&gt; and
drop it into the &lt;code&gt;ui/static/js&lt;/code&gt; directory. Alpine, like tailwind is minified because I am not
altering it in any way.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;build-css&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tailwindcss -i ui/static/css/main.css -o ui/static/css/theme.css -m&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;alpine&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;npx esbuild index.js --outfile=ui/static/js/bundle.js --bundle --minify&amp;#34;&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;devDependencies&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;@tailwindcss/aspect-ratio&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^0.4.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;@tailwindcss/forms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^0.5.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tailwindcss&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^3.0.23&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;dependencies&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;alpinejs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^3.9.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;esbuild&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^0.14.25&amp;#34;&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="err"&gt;New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The final directory structure should look something like this&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ui
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── base.layout.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── footer.partial.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── home.page.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── navbar.partial.tmpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── static
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   ├── css
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   ├── main.css
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   │   └── theme.css &lt;span class="c1"&gt;# New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│   └── bundle.js &lt;span class="c1"&gt;# New&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── ui.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="testing-it-out"&gt;Testing it out&lt;/h2&gt;
&lt;p&gt;Remove any references to Tailwind or Alpine&amp;rsquo;s CDN and replace them with the location of &lt;code&gt;theme. css&lt;/code&gt; and &lt;code&gt;bundle.js&lt;/code&gt; respectively. Refreshing the application page should render everything
correctly and any Alpine widgets should work.&lt;/p&gt;
&lt;p&gt;The best part is the application is completely portable. For clarity, this application is using
Go &lt;a href="https://blog.carlmjohnson.net/post/2021/how-to-use-go-embed/"&gt;embedded files&lt;/a&gt; so that it is
self-contained. After running &lt;code&gt;go build&lt;/code&gt; you will be able to run it completely isolated and have
the CSS and JS work as expected.&lt;/p&gt;</description></item><item><title>TIL: Interview Questions Worth Thinking About</title><link>https://danielms.site/blog/til-interview-questions-worth-thinking-about/</link><pubDate>Fri, 11 Mar 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/til-interview-questions-worth-thinking-about/</guid><description>&lt;h1 id="interview-questions-worth-considering"&gt;Interview Questions Worth Considering&lt;/h1&gt;
&lt;p&gt;Today I stumbled across a job advert that had a section called &lt;strong&gt;The Chat&lt;/strong&gt;. Here is the snippet
of things they might ask you about during the interview.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Language&lt;/strong&gt;: Why? What do you like and dislike about it?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Domain Modelling&lt;/strong&gt;: What&amp;rsquo;s it good for?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Databases - Querying and Transactions&lt;/strong&gt;: what makes them challenging?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API Design&lt;/strong&gt;: What&amp;rsquo;s taken into consideration?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application Architecture&lt;/strong&gt;: Microservices, monoliths, event-driven, app servers and serverless; when and why?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing and Code Quality&lt;/strong&gt;: Where and how does it add value?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SCM&lt;/strong&gt;: Branching and merging flows - how do you like them?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging, Metrics, and Monitoring&lt;/strong&gt;: What&amp;rsquo;s the difference?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error handling and Debugging&lt;/strong&gt;: Describe your process and what&amp;rsquo;s got you stuck in the past?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SDLC and CI/CD&lt;/strong&gt;: Benefits and downfalls?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security&lt;/strong&gt;: What does that mean to you?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I like this because I immediately get the sense that this company &lt;em&gt;is&lt;/em&gt; thinking about these issues, and potentially looking to see how you could fit in.&lt;/p&gt;
&lt;p&gt;Most importantly, I realised that some of these points where weak spots, or rather I would need to collect my thoughts to answer effectively.
How much of what I know is based on previous training/conditioning versus good practices. There is no right or wrong but having a well thought out
response to these questions is valuable - not just in an interview but at any time.&lt;/p&gt;</description></item><item><title>Feb 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-feb-2022/</link><pubDate>Sun, 06 Mar 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-feb-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;A number of external influences impeded my ability to do get much work done this month. As
a result very little was achieved on Mudmap.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Small fixes for Mudmap installer&lt;/li&gt;
&lt;li&gt;Built a Cobra CLI for interacting with an Australia crypto exchange&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2022/retrospective-jan-2022/"&gt;January&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="deliver-the-interfaces-feature-for-mudmap"&gt;Deliver the Interfaces feature for Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Failed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: F&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I didn&amp;rsquo;t even get close to completing this.&lt;/p&gt;
&lt;p&gt;A bit of background. A number of things have been happening this month in my personal and work
life - which obviously share resources with my development time. My entire family got quite sick
though I suffered the worst, and the kicker, it was plain old flu. I actually had someone tell
me I am wrong here and that it &lt;em&gt;must&lt;/em&gt; have been COVID but just a false negative. Give it up
people, the cold and flu still exist!&lt;/p&gt;
&lt;p&gt;Additionally, I&amp;rsquo;ve been moved to a senior position in another team at work. The team is at the
point of complete atrophy with everyone jumping ship or taking jobs elsewhere, and unfortunately,
I am the &lt;em&gt;best&lt;/em&gt; candidate to help out. I cannot begin to get started on how badly everything has
fallen apart over the last 6 months - I feel like I&amp;rsquo;m working at Parts Unlimited from the
&lt;a href="https://www.amazon.com.au/Phoenix-Project-DevOps-Helping-Business/dp/0988262592"&gt;Phoenix Project&lt;/a&gt;.
We even have our own Brent!&lt;/p&gt;
&lt;p&gt;Needless to say, my mind has been quite occupied over the last few weeks meaning time for Mudmap
has been low.&lt;/p&gt;
&lt;h3 id="write-at-least-one-blog-post-for-mudmap"&gt;Write at least one blog post for Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Failed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: F&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For the same reasons above I have had no time to do this task either.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;I am about two years late to the party but &lt;a href="https://en.wikipedia.org/wiki/Ted_Lasso"&gt;Ted Lasso&lt;/a&gt; has to be the most wholesome and
feel-good show I&amp;rsquo;ve watched in a long time. Some of it really hits home for me, in the past I
have been in a high performance team with high stakes. I&amp;rsquo;d deal with the team dynamics of highly
talented, motivated and sometimes abrasive individuals, and it is tough sometimes. So this
series made me happy, sad, reflective and proud all at the same time. The soundbite that I&amp;rsquo;ll
take away from it is &lt;em&gt;be curious, not judgemental&lt;/em&gt;. It is something I need to remind myself daily.&lt;/p&gt;
&lt;p&gt;After 6 weeks of training I competed in my first open water swim. It was a tough two kilometers
in some decent swell; 3m at 10 second intervals. But, it feels like I&amp;rsquo;ve accomplished something
worth being proud of and most importantly it aligns with my true self. The points in my life that I
look back upon with the most fondness where also the most painful and required a lot of
commitment and dedication.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“In some ways suffering ceases to be suffering at the moment it finds a meaning, such as the
meaning of a sacrifice.” - Viktor Frankl&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I recommend doing something hard and painful every now and again. Get out of your chair, don&amp;rsquo;t
take the umbrella and feel rain on your skin or challenge yourself by going for a &lt;a href="https://www.parkrun.com"&gt;Park Run&lt;/a&gt;.
Just do something where all you can think about for a few minutes is survival - most problems
aren&amp;rsquo;t that bad when you have such a contrast.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;A short retro after a month of very little in the way of accomplishments. I did create a small
&lt;a href="https://github.com/cupscanteen/swyftx-cli"&gt;Go CLI&lt;/a&gt; using Cobra for a popular Australian crypto exchange (still a WIP). This was
mostly to enable my friend&amp;rsquo;s analysis of trade conditions but also to get more acquainted with
writing Go CLI&amp;rsquo;s. This is an area where I believe Go really shines, especially with its ease of
producing cross-platform binaries. Whilst I may have accomplished little on Mudmap I did create
something useful for a friend - a silver lining for the month.&lt;/p&gt;
&lt;p&gt;In March, I hope to be a bit more productive on Mudmap, or if I am still unable to produce much
assess my ability to keep going with the project. This is that is weighing on my
mind, especially in light of Netgate&amp;rsquo;s recent announcements. Effectively, they are looking to
transition pfSense CE from development to maintenance mode. At least that is the feeling amongst the
community, and whilst I do understand their desire to move in this direction, I do see that as a
potential threat to Mudmap&amp;rsquo;s long term viability.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write my weekly &lt;a href="https://whatgotdone.com"&gt;What Got Done&lt;/a&gt; on Friday instead of delaying until the weekend or later&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;ve done a lot to help out a friend with their own project, mostly on the deployment side&lt;/li&gt;
&lt;li&gt;Allowed myself to take time off when needed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;p&gt;This month I&amp;rsquo;m setting no goal other than to help my sick partner recover. As much as it feels
cheap from a product standpoint family trumps penny stocks.&lt;/p&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-02-01"
 let end_date = "2022-02-28"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2022-02-01"
 let end_date = "2022-02-28"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-02-01"
 let end_date = "2022-02-28"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>Jan 2022 Retrospective</title><link>https://danielms.site/retrospectives/2022/retrospective-jan-2022/</link><pubDate>Tue, 01 Feb 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2022/retrospective-jan-2022/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;This month I deployed the new version of &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt; - a complete re-write using Golang.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deployed version 2 of Mudmap&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-dec-2021/"&gt;Decembers&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="push-mudmap-version-2-to-production"&gt;Push Mudmap version 2 to production&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Completed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A+&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After checking my git history, I was surprised to see that my first commit for the new version of
Mudmap was on the 5th of November. On January 10, I officially pushed v2 to production.
Considering I was maintaining an existing product &lt;em&gt;and&lt;/em&gt; rewriting it in another language - which
is still relatively new to me - this feels like quite an achievement.&lt;/p&gt;
&lt;h4 id="did-it-improve-performance-and-what-was-your-reasoning"&gt;Did it improve performance, and what was your reasoning?&lt;/h4&gt;
&lt;p&gt;Here is a &lt;a href="#tweets"&gt;twitter thread&lt;/a&gt; with visuals. But the tl;dr is &lt;strong&gt;yes&lt;/strong&gt;, memory consumption
alone is remarkable. With my Django app, even a small load on the server could run the risk of
pushing the application above the plan limits. Now, I am nowhere near it.&lt;/p&gt;
&lt;p&gt;The difference between container image sizes are ridiculous as well. Somewhere around 15mb for
the Go image and 1GB for Django. Build times feel days apart - from ~10 minutes using
python to under a minute using Go. A pet peeve of mine is how slow python image building is, and
I am glad to be rid of it. Another thing I&amp;rsquo;m happy with is the cost reduction in terms of the
number of services needed. I save at least $14 per month by not needing redis or celery. I&amp;rsquo;m
even considering removing Postgres for &lt;a href="https://litestream.io"&gt;Litestream&lt;/a&gt; as I now only store basic user and device data.&lt;/p&gt;
&lt;p&gt;There is a &lt;a href="https://mudmapio.notion.site/Version-2-d78ca9bd813541738f7c71cfb9c95c9e"&gt;notion document&lt;/a&gt; with some of my reasons for switching from Django to Go. Of course,
in almost any of those statements, a counter-point could be constructed. That is fair, but
regardless, they are &lt;strong&gt;my&lt;/strong&gt; reasons for such an undertaking.&lt;/p&gt;
&lt;h3 id="add-at-least-one-more-core-feature-to-mudmap"&gt;Add at least one more core feature to Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Completed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mudmap now supports three helpful but not &lt;em&gt;core&lt;/em&gt; features; shutdown, reboot and shell command
execution (see them in &lt;a href="https://docs.mudmap.io/videos/demo-diagnostics?ref=retro-jan-2022"&gt;action&lt;/a&gt;). Admittedly, these are useful, but I did not deliver the
bigger feature I&amp;rsquo;ve been working on; the ability to create, read, update and delete interfaces.&lt;/p&gt;
&lt;p&gt;I did update the documentation, landing page and recorded five YouTube &lt;a href="https://www.youtube.com/channel/UCtRlcQftzThqR5Q5iaOVYTA"&gt;explainer videos&lt;/a&gt;.
It is the first time I have recorded and uploaded anything to YouTube, and it shows in my
delivery. I opted to push what I recorded rather than waste time finessing and re-recording it
again and again. Reaching for pragmatism not perfection.&lt;/p&gt;
&lt;p&gt;As I&amp;rsquo;ve transitioned (but still support version 1) to version 2, the documentation needed
updating. This meant updating all the screenshots, links and explanations. I also added the
videos to the docs as some people prefer videos over a wall of text.&lt;/p&gt;
&lt;p&gt;Mudmap&amp;rsquo;s &lt;a href="https://mudmap.io?ref=danielms.site"&gt;landing page&lt;/a&gt; also got a little better. Previously, I was using a placeholder
image in the main section but replaced it with a screenshot of the dashboard. The &lt;a href="https://docs.mudmap.io/videos/overview-video"&gt;overview&lt;/a&gt;
video is now embedded into the page as well.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=ho22vkGFljg&amp;amp;ab_channel=MyFirstMillion"&gt;My First Million&lt;/a&gt; episode on &lt;a href="https://twitter.com/kevinvantrump"&gt;Kevin Van Trump&lt;/a&gt; and &lt;a href="https://www.farmcon.com/"&gt;FarmCon&lt;/a&gt; - its incredible. This guy
runs a &lt;strong&gt;daily&lt;/strong&gt; agricultural investing/analysis newsletter which brings in ~30M USD a year. He&amp;rsquo;s
been writing it for 17 years and now runs &lt;a href="https://www.farmcon.com/"&gt;FarmCon&lt;/a&gt;. I&amp;rsquo;ve been binge-watching all the videos on
YouTube and cannot praise this enough - talk about finger on the pulse.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://openfaas.com"&gt;OpenFaaS&lt;/a&gt; for serverless functions. I&amp;rsquo;ve never been much interested in serverless, mostly
because I don&amp;rsquo;t like using AWS (etc) for small workloads/one-off tasks. And, when I&amp;rsquo;ve looked
at it before it didn&amp;rsquo;t really gel with me - another skill to learn. Then I found &lt;a href="https://openfaas.com"&gt;OpenFaaS&lt;/a&gt; -
an open source serverless technology. Using OpenFaaS, I can write functions in almost any language with
my two preferred options being python (Flask) and Go. It also supports NET, C#, Node, Java and
more (I think). I am using it to render the &lt;a href="#analytics"&gt;analytics&lt;/a&gt; charts below. It supports
Kubernetes and a standalone binary called &lt;a href="https://docs.openfaas.com/deployment/faasd/"&gt;faasd&lt;/a&gt; for deployment to a small server - I run it on
a $5 droplet.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;A good start to the year. I took the Christmas and New Year period off and also took a
four-day long weekend at the end of this month. Only during the four-day break by the sea with
just my little family did I &lt;em&gt;start&lt;/em&gt; to feel rested. If I can, I will try to schedule more of
these little breaks to ward off burn out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Not get as distracted by other things; &lt;a href="https://openfaas.com"&gt;OpenFaaS&lt;/a&gt; took some time away from Mumdap this month&lt;/li&gt;
&lt;li&gt;I need to read more books and not look at my phone before bed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Integrated Stripe and deployed Mudmap&amp;rsquo;s new version early in the month&lt;/li&gt;
&lt;li&gt;Wrote several emails to customers, a blog post and newsletter announcing it&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deliver the Interfaces feature for Mudmap&lt;/li&gt;
&lt;li&gt;Write at least one blog post for Mudmap&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2022-01-01"
 let end_date = "2022-01-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2022-01-01"
 let end_date = "2022-01-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2022-01-01"
 let end_date = "2022-01-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;h3 id="tweets"&gt;Tweets&lt;/h3&gt;
&lt;blockquote class="twitter-tweet"&gt;
 &lt;a href="https://twitter.com/dansult/status/1480562848791105536"&gt;&lt;/a&gt;
&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;

</description></item><item><title>December 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-dec-2021/</link><pubDate>Sun, 02 Jan 2022 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-dec-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Mudmap is now completely rebuilt in Go (with extra features) and ready for official (re)launch in January 2022.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Mudmap version 2 exceeds version 1 in features&lt;/li&gt;
&lt;li&gt;New authentication backend makes life a lot simpler for me (and will allow MFA for users)&lt;/li&gt;
&lt;li&gt;After a big year, I&amp;rsquo;ve taken a couple of weeks off and feel great&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-nov-2021/"&gt;Novembers&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="plug-in-the-user-authentication-to-mudmap"&gt;Plug in the user authentication to Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Completed&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Mudmap&amp;rsquo;s current iteration uses third party packages and my own implementation to handle the login, logout, password
reset flow for the application. It took me a long time and added complexity, especially to the frontend. This
complexity meant that sometimes users could be booted from a session randomly, which isn&amp;rsquo;t a great user experience. It
also took a lot of my development time to create and maintain which is time better served elsewhere. Hindsight is
always crystal clear, and I knew that if I had my time again, I would simply use an existing third party service
instead. Enter &lt;a href="https://auth0.com"&gt;Auth0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Off the bat, it has been a breeze. Setting up the frontend to authenticate a user with Auth0 and then allow them
to access the application was very simple. Using a React based frontend (Next.js) has its perks, namely the first
class support &lt;em&gt;most&lt;/em&gt; big orgs provide for it. Auth0 was no exception here providing excellent libraries for both
Next.js and React.&lt;/p&gt;
&lt;p&gt;Where I did hit some issues was the secure communication between the (now authenticated) frontend and my backend
server. When a user gains access (logs in) via your frontend, that does not give them immediate access to anything
else, they need to then request an access token. That access token must then be sent as a header with each request
to the server and verified. The process is well understood and &lt;em&gt;should&lt;/em&gt; have been easy enough, but if you want to
use Auth0&amp;rsquo;s &lt;code&gt;auth0-react&lt;/code&gt; module &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;axios&lt;/code&gt; well you&amp;rsquo;re probably going to struggle a little.&lt;/p&gt;
&lt;p&gt;Why? The library expects you to request an access token from Auth0 via a react hook. Unfortunately, this
effectively means I needed to either create an axios request (object) per component, or use fetch.&lt;/p&gt;
&lt;p&gt;In the end I removed &lt;code&gt;axios&lt;/code&gt; completely and replaced it with fetch. On record, I much prefer &lt;code&gt;axios&lt;/code&gt; - fetch has
pitiful error handling.&lt;/p&gt;
&lt;p&gt;At the same time, my backend implementation also hit a problem causing a two pronged debugging session whereby I had
two errors but thought there was only one. Go is a typed language and when you say you&amp;rsquo;re only sending a &lt;code&gt;string&lt;/code&gt;
but instead send a &lt;code&gt;[]string&lt;/code&gt;, you&amp;rsquo;re going to get an error. What tripped me up the most was the documentations'
description of what should be getting send to the server, did not match what was actually getting sent to it. After
trawling the Auth0 forums and decoding the token I found the solution and implemented it. What I love here is that
it was a fix &lt;em&gt;I&lt;/em&gt; could easily implement into a third party module. In the past, I&amp;rsquo;ve found this process much more
difficult using python.&lt;/p&gt;
&lt;h3 id="email-customers-about-upcoming-changes-to-mudmap"&gt;Email customers about upcoming changes to Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: I emailed a few but not all (I actually forgot)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Embarrassingly, I wrote a big email to let customers know about the upcoming changes and forgot to send it before
Christmas. It&amp;rsquo;s going out this week.&lt;/p&gt;
&lt;p&gt;I did however write a &lt;a href="https://www.mudmap.io/blog/version-2"&gt;blog&lt;/a&gt; post stating much the same things which are in the email.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m giving myself a &lt;strong&gt;C&lt;/strong&gt; because I &lt;em&gt;did&lt;/em&gt; write it and I also wrote a blog post about it. Cutting myself some slack
because I just drank a litre of sangria with my wife at the beach tonight too.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re into mountaineering or other sports which push the limits of human potential, you will absolutely love the
movie &lt;a href="https://www.imdb.com/title/tt14079374/"&gt;14 Peaks&lt;/a&gt;. Two things I loved about it; the team is entirely nepalese,
and Nims is ex SB. Having served with some SB blokes over the years makes it feel closer to home. Also, I love to see
former warfighters do big things with their lives once they&amp;rsquo;ve left the service.&lt;/p&gt;
&lt;p&gt;Write your successes and failures throughout the year. I&amp;rsquo;ve now written a retrospective for each month of this year,
and reading back over them has been hugely rewarding for me. Each week, I write about what I did over at
&lt;a href="https://whatgotdone.com/dansult"&gt;What Got Done&lt;/a&gt;, too. Without doing these I really would not remember what I&amp;rsquo;ve been up to at all over the last
year. Cataloging my year is the single best thing I have to beat out imposter syndrome, and also to keep me accountable.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s been a big year with lots ups and downs, wasted efforts, and successes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remember to send emails after I&amp;rsquo;ve drafted them up!&lt;/li&gt;
&lt;li&gt;Step away from the keyboard on those really nice days and just take the family out somewhere instead - the keyboard won&amp;rsquo;t miss me&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;ve learnt a lot, and have been able to implement things now that only 12 months ago would have taken me months - keep growing and stay the course&lt;/li&gt;
&lt;li&gt;Exercising - I&amp;rsquo;ve done a lot more this month, especially since signing up for a 2k ocean swim in Feb&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Push Mudmap version 2 to production&lt;/li&gt;
&lt;li&gt;Add at least one more core feature to Mudmap&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-12-01"
 let end_date = "2021-12-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-12-01"
 let end_date = "2021-12-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-12-01"
 let end_date = "2021-12-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;&lt;img src="twit-nov-retro.png" alt="" title="@dansult twitter stats for December 2021"&gt;&lt;/p&gt;</description></item><item><title>November 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-nov-2021/</link><pubDate>Tue, 07 Dec 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-nov-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Screw it, I&amp;rsquo;m rebuilding Mudmap&amp;rsquo;s backend.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;80% feature parity between Mudmap version 1 and now&lt;/li&gt;
&lt;li&gt;Decided to withdraw from MSFT interviews&lt;/li&gt;
&lt;li&gt;Started planning new user interface enhancements&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-oct-2021/"&gt;October&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="mudmap-feature-proof-of-concept"&gt;Mudmap feature proof-of-concept&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: A PoC turned into a rewrite, and it&amp;rsquo;s working out great&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Firstly, I want to acknowledge two things.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Everyone says don&amp;rsquo;t rewrite, it is a time sink or that it&amp;rsquo;s chasing waterfalls&lt;/li&gt;
&lt;li&gt;There is nothing wrong with Django - it&amp;rsquo;s great&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That out of the way, let&amp;rsquo;s continue. This started as a prototype to see if I could use Go for
sending SSH traffic more efficiently. It pretty quickly devolved into a bunch
of HTTP handlers with SSH connections inside them.&lt;/p&gt;
&lt;p&gt;Somewhere within the &lt;a href="https://whatgotdone.com/dansult/2021-11-12"&gt;first half of the month&lt;/a&gt;, I started to feel a lot more productive and in
control by re-implementing sections of Mudmap in Go. It was at this point that I realised it&amp;rsquo;s
time to commit and just do it. Despite this, I did have a number of self-conscious
thoughts about undertaking this task. But, ultimately they all boiled down to me &lt;em&gt;caring about what
others think&lt;/em&gt; which is an indicator that you&amp;rsquo;re doing, or not doing, something for the &lt;em&gt;wrong&lt;/em&gt;
reasons.&lt;/p&gt;
&lt;p&gt;After a month of work, Mudmap V2 is about 80% feature parity with my in production V1 codebase.
It also has more tests and better coverage than the current application and some components
even have additional features. Something I&amp;rsquo;m really pleased with is how easy it is to add new
features using Go - its type system and interfaces are great for this.&lt;/p&gt;
&lt;p&gt;This is a personal thing and not slight on Django, but I&amp;rsquo;ve found that Go has forced me to think
more critically about how the application should be built. Django&amp;rsquo;s beauty can also be its curse
as there is a lot of &lt;em&gt;magic&lt;/em&gt;. For a CRUD application (and especially one without DRF), it&amp;rsquo;s
wonderful. For me, some of that magic didn&amp;rsquo;t work well for what I&amp;rsquo;m trying to achieve with
Mudmap. For instance, my endpoints make calls to another API using SSH. This is not something
Django expects and takes away the brevity of &lt;a href="https://docs.djangoproject.com/en/3.2/topics/class-based-views/intro/"&gt;Class Based Views&lt;/a&gt;. I&amp;rsquo;m also not a huge fan of
the serializers used by DRF (which may as well be a Django core module these days), whereas I am
a big champion of Pydantic/FastAPI&amp;rsquo;s type system. Go feels like a step above both options but
it&amp;rsquo;s a subject comparison.&lt;/p&gt;
&lt;p&gt;In hindsight, Go feels like the better option for a couple of other reasons which are mostly
related to the developer experience. It &lt;em&gt;has&lt;/em&gt; made me think more critically about what I am
trying to accomplish at every step along the way. How? Firstly, in Go, a little copying
is better than a little dependency. This has made me do a lot more research where in the
past I may have just &lt;code&gt;pip install&lt;/code&gt;&amp;rsquo;d something, and it has an added benefit of making me read a
lot more source code, a powerful educator. I also love seeing how changing a functions&amp;rsquo; signature
can ripple across the entire app, which has also made me plan further ahead. Lastly, Go is a
concurrent language. Mudmap sends emails and executes things in background workers without Redis
or Celery, and it can handle a boatload of requests. I&amp;rsquo;m still coming to grip with the fact
that I don&amp;rsquo;t need Gunicorn, its dev server is its prod server, and it&amp;rsquo;s fast.&lt;/p&gt;
&lt;p&gt;In all, I think this has been a good move and feel more confident about feature development and
maintaining this codebase than I do with the current app.&lt;/p&gt;
&lt;h3 id="study-at-least-2-pomodoros-each-day"&gt;Study at least 2 pomodoro’s each day&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: This or rewrite Mudmap. This lost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This spawned from interviewing with Microsoft for a position within Azure. I put in a number of
hours at the start of the month, working through the typical developer interview type questions.
After about 10 days, I gave it away to spend more time on Mudmap but in the process did brush up
on some of the basics.&lt;/p&gt;
&lt;p&gt;I guess, this is a two-part reason as to why I stopped studying; I don&amp;rsquo;t really want that job,
mostly because they want me to stay local and work in the office. That&amp;rsquo;s not something my family,
or I want anymore. We&amp;rsquo;re keen to head back to the coast where we lived for ten years before coming
here. Also, that crap is utterly boring and saps my energy - if it means I never work for a Big
Corp, so be it.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;We started watching &lt;a href="https://en.wikipedia.org/wiki/The_Expanse_(TV_series)"&gt;The Expanse&lt;/a&gt; on Prime and after three seasons am totally hooked. If you
like space, it&amp;rsquo;s actually really good.&lt;/p&gt;
&lt;p&gt;I like to listen to music when working but get distracted by certain genre&amp;rsquo;s. It means I have to
pick music that is rather mellow. As much as I love 90&amp;rsquo;s/2000&amp;rsquo;s hip hop, it is too stimulating
for me and makes it hard to concentrate. This month I found a really &lt;a href="https://open.spotify.com/playlist/35fMnNReBETCnQ0CH5CHug"&gt;chill playlist&lt;/a&gt; which
has replaced my &lt;a href="https://open.spotify.com/album/3B61kSKTxlY36cYgzvf3cP"&gt;Interstellar&lt;/a&gt; and &lt;a href="https://open.spotify.com/album/3AMXFnwHWXCvNr5NCCpLZI"&gt;Tron&lt;/a&gt; soundtracks.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;I feel like I am back this month after a sluggish August-September-October. It might be the end of
lockdown, the
increasingly nicer weather or just because I took some time to slow down but whatever it is I
feel much more motivated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;More exercise&lt;/li&gt;
&lt;li&gt;More engagement with customers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Focused on completing one task well before starting the next&lt;/li&gt;
&lt;li&gt;Prioritised what matters and brushed off meaningless tasks&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Plug in the user authentication to Mudmap&lt;/li&gt;
&lt;li&gt;Email customers about upcoming changes to Mudmap&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-11-01"
 let end_date = "2021-11-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-11-01"
 let end_date = "2021-11-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-11-01"
 let end_date = "2021-11-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="twit-nov-retro.png" alt="" title="@dansult twitter stats for November 2021"&gt;&lt;/p&gt;</description></item><item><title>October 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-oct-2021/</link><pubDate>Fri, 05 Nov 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-oct-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;A month of learning and exploring, free from self-imposed pressure.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Focused on learning Go&lt;/li&gt;
&lt;li&gt;Deployed my first Go app&lt;/li&gt;
&lt;li&gt;Interviewed with Microsoft&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A snippet from last month&amp;rsquo;s goal setting in the &lt;a href="https://danielms.site/retrospectives/2021/retrospective-sept-2021/"&gt;September retro&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No goals, or rather I am taking the month to freestyle the next thing to do. Not setting goals
is setting a goal to fail, or something like that, but I need a month to relax my mind. My wife
has had some health issues, COVID garbage is constant and working from home whilst homeschooling
ain’t easy. But, that should all ease off towards the end of the month, so I’m taking until then
to just experiment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Instead of reviewing and assessing my goals, I&amp;rsquo;ll cover off on what I have done.&lt;/p&gt;
&lt;h3 id="focused-on-learning-go"&gt;Focused on learning Go&lt;/h3&gt;
&lt;p&gt;After several on and off attempts at learning Go over the years, I&amp;rsquo;ve actually committed to it
over the last month or so. When it comes to learning new things - regardless of topic - I like
to use the &lt;a href="https://en.wikipedia.org/wiki/Four_stages_of_competence"&gt;four stages of competence model&lt;/a&gt; as my frame of reference.&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Stage&lt;/th&gt;
					&lt;th&gt;Description&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Unconscious incompetence&lt;/td&gt;
					&lt;td&gt;The individual does not understand or know how to do something and does not necessarily recognize the deficit. They may deny the usefulness of the skill. The individual must recognize their own incompetence, and the value of the new skill, before moving on to the next stage. The length of time an individual spends in this stage depends on the strength of the stimulus to learn.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Conscious incompetence&lt;/td&gt;
					&lt;td&gt;Though the individual does not understand or know how to do something, they recognize the deficit, as well as the value of a new skill in addressing the deficit. The making of mistakes can be integral to the learning process at this stage.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Conscious competence&lt;/td&gt;
					&lt;td&gt;The individual understands or knows how to do something. However, demonstrating the skill or knowledge requires concentration. It may be broken down into steps, and there is heavy conscious involvement in executing the new skill.&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Unconscious competence&lt;/td&gt;
					&lt;td&gt;The individual has had so much practice with a skill that it has become &amp;ldquo;second nature&amp;rdquo; and can be performed easily. As a result, the skill can be performed while executing another task. The individual may be able to teach it to others, depending upon how and when it was learned.&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;sourced from &lt;a href="https://en.wikipedia.org/wiki/Four_stages_of_competence"&gt;wikipedia&lt;/a&gt; as it much more eloquent than anything I could write.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Initially, I set about the fumbling and foolish unconscious incompetent phase. This is when I
just start and know that anything I write will be bad, of poor quality and embarrassing in the
future. In the past, I&amp;rsquo;ve spent way too long researching the &lt;em&gt;best&lt;/em&gt; way and otherwise convincing
myself that I could prevent this by just reading more. You can&amp;rsquo;t, just like you can&amp;rsquo;t theorise
yourself into an NBA contract - you have to do the work.&lt;/p&gt;
&lt;p&gt;When I trained new guys on their way into the special forces, you&amp;rsquo;d see all kinds of wacky stuff.
The old way of teaching was to punish them for silly mistakes. Thankfully, times are changing
and we (eventually) came to realise that learning is a curve. Something akin to 60% of time
spent in a learning continuum is spent in this first phase, with the next stages accelerating
off the back of a solid foundation. This tangent is a poor attempt to say; embrace the &lt;em&gt;wacky&lt;/em&gt;
and uncomfortable start because beginners &lt;em&gt;are allowed&lt;/em&gt; to make mistakes.&lt;/p&gt;
&lt;p&gt;Eventually, I started to hit some rough spots where experience taught me that I was missing some
fundamental Go building blocks. Instead of searching around, I just pulled the trigger on a book
by &lt;a href="https://twitter.com/ajmedwards?lang=en"&gt;Alex Edwards&lt;/a&gt; called &lt;a href="https://lets-go-further.alexedwards.net/"&gt;Let&amp;rsquo;s Go Further&lt;/a&gt;. It has been a wonderful resource which really
helped to teach me how to set out my projects and make them extensible.&lt;/p&gt;
&lt;p&gt;I think I am halfway between consciously incompetent and competent now. So I know when I&amp;rsquo;m
making the right decision about 50 percent of the time now.&lt;/p&gt;
&lt;h3 id="deployed-my-first-go-app"&gt;Deployed my first Go app&lt;/h3&gt;
&lt;p&gt;Off the back of Alex&amp;rsquo;s &lt;a href="https://lets-go-further.alexedwards.net/"&gt;book&lt;/a&gt;, I was able to refactor my URL shortener application that I
had been writing in Go. I decided to not only refactor it but also deploy it to my Caprover
instance. This way I&amp;rsquo;d get the experience of deploying a dockerized Go app with a next frontend
(hosted by Vercel). In addition to that, I tried out &lt;a href="https://litestream.io/"&gt;Litestream&lt;/a&gt; as the SQL backend and was
able to replicate the database using S3 as the storage layer.&lt;/p&gt;
&lt;p&gt;I use Github for personal stuff, such as this but professionally use Gitlab (and Gitea in
the past). Github is moving with some serious speed these days but my familiarity and
productivity is firmly with Gitlab. This did trip me up during the deployment where I
retrospectively added a CI pipeline. I stumbled upon some issues with secrets and contributors
which I ultimately solved by refactoring &lt;em&gt;my&lt;/em&gt; code. Nonetheless, as I understand it, secret
management in public repo&amp;rsquo;s can be a security issue which is why I opted to refactor rather than
sort out GH actions.&lt;/p&gt;
&lt;p&gt;The process of building and deploying my first Go app was a breeze compared to &lt;em&gt;any&lt;/em&gt; python
project. I&amp;rsquo;m still coming to terms with Go having a webserver that&amp;rsquo;s production ready after
having spent a lot of time setting up &lt;code&gt;uvicorn&lt;/code&gt; and &lt;code&gt;gunicorn&lt;/code&gt; to serve python apps.&lt;/p&gt;
&lt;p&gt;Check it out at &lt;a href="https://tars.run"&gt;https://tars.run&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="interviewed-with-microsoft"&gt;Interviewed with Microsoft&lt;/h3&gt;
&lt;p&gt;During the month I was approached by a Microsoft recruiter about a couple of potential jobs they
&lt;em&gt;think&lt;/em&gt; I might be suitable for. Flattered, I took the call and ended up having a couple of
first round interviews. Quickly I discovered that my technical interview skills and knowledge
are not up to scratch. Despite being recommended to continue along the pipeline, I elected to
postpone any further interviews. In the meantime, I&amp;rsquo;ve decided to slowly brush up and in some
spots actually learn new skills.&lt;/p&gt;
&lt;p&gt;But, I&amp;rsquo;m an &lt;em&gt;Indie Hacker&lt;/em&gt; now right? Yes, and no. I work full-time for someone else and still
need to provide for my family. Until I can swing from one branch to the next without my kids
skipping dinner, I&amp;rsquo;ll need to be ready for new job opportunities. Getting slightly humbled by
some easy questions really put my future employability in the spotlight.&lt;/p&gt;
&lt;p&gt;I think this was a great wake-up call too. I&amp;rsquo;ve been ignoring the fact that knowing how solve
the types of problems used during these interviews will at worst make me learn some more
fundamentals and at best get me a job. I&amp;rsquo;ve nothing to lose by studying a little each day.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;I am seriously late to the party but I&amp;rsquo;ve started watching some &lt;a href="https://twitch.tv"&gt;Twitch&lt;/a&gt;
streamers. Here are a couple I enjoy listening to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/rwxrob"&gt;rwxrob&lt;/a&gt; for his Linux, Go and k8s rants&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/large__data__bank"&gt;Jordan Lewis&lt;/a&gt; for his cockroach labs Go programming&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.twitch.tv/anthonywritescode"&gt;Anthony Sottile&lt;/a&gt; because he writes python open source and sounds like a real person when streaming&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;This month was a great unwind and refresh from working on Mudmap. It gave me the intellectual
and emotional space to take stock of a few things.&lt;/p&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Mudmap feature proof-of-concept&lt;/li&gt;
&lt;li&gt;Study at least 2 pomo&amp;rsquo;s each day&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-10-01"
 let end_date = "2021-10-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-10-01"
 let end_date = "2021-10-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-10-01"
 let end_date = "2021-10-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I really haven&amp;rsquo;t been playing the Twitter game much lately. Struggle thinking of things to say!
&lt;img src="twit-oct.png" alt="" title="@dansult twitter stats for September 2021"&gt;&lt;/p&gt;</description></item><item><title>September 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-sept-2021/</link><pubDate>Mon, 04 Oct 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-sept-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Finally, I&amp;rsquo;ve released my firewall feature to Mudmap. It&amp;rsquo;s a small step towards having a more fully
featured application for pfSense users.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deployed a big feature to prod&lt;/li&gt;
&lt;li&gt;Took on a lot of learning for my day job - cutting into Mudmap&amp;rsquo;s time&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-aug-2021/"&gt;August&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="release-firewall-rules-page"&gt;Release firewall rules page&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: I&amp;rsquo;ve done it, finally!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After what feels like a lifetime I&amp;rsquo;ve pushed a big feature to &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;. Even saying that makes me
shudder, it&amp;rsquo;s a bad thing - features shouldn&amp;rsquo;t take so long to deploy. Regardless, I am happy
its out and glad to move on to the next thing. I keep asking myself why it took so long. I think
it comes down to two main things; time and complexity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Time&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is just me working on &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;, and I already work a full-time job. On one hand, it&amp;rsquo;s amazing
what you can accomplish over time as your small increments compound. On the other, if it was my
full-time job those increments would compound much faster. Acknowledging this fact is important
but it&amp;rsquo;s only half the battle. I also spend more time than I should on things which &lt;em&gt;do not&lt;/em&gt; add
value to the project. Battling with tooling issues or lack of foresight for issues that would
cost me precious time later, are especially damaging. As I won&amp;rsquo;t be quitting full-time employment
any time soon, time or the lack thereof will be a continued factor in the velocity of release.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;complexity&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I have definitely made things more complex than they need be in certain aspects. In hindsight,
it is obvious but only now do I possess such clarity. Further, pfSense is a complex beast in
itself and building an API plus management dashboard for it isn&amp;rsquo;t trivial. I cannot decrease the
system complexity of pfSense but I can reduce &lt;em&gt;my&lt;/em&gt; own systems. It starts with more time allotted
to design and proof of concept work instead of ploughing headfirst into things I &lt;em&gt;think&lt;/em&gt; can work.&lt;/p&gt;
&lt;p&gt;&lt;img src="name-of-image.png" alt=""&gt;&lt;/p&gt;
&lt;h3 id="write-a-mudmap-blog-post"&gt;Write a Mudmap blog post&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: I have it written just not published&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I can deploy what I have written to prod, but it feels half-baked or even nebulous in hindsight.
I&amp;rsquo;ve decided to sit on it and maybe leave that post as a forever draft instead. As a result,
this is unfinished.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;After years of pestering by Amazon, I finally trialled Prime. Before I mention the show I really
enjoyed I want to say two things about Prime Video which suck.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Paying for movies and shows (in addition to the yearly subscription)&lt;/li&gt;
&lt;li&gt;Search absolutely sucks&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When I searched for a show that my daughter was begging me to watch, all I could remember is
that it had &amp;ldquo;princess&amp;rdquo; in it. So I searched for &amp;ldquo;princess&amp;rdquo;. No results. Tears ensued. A week
later, my wife found it - &amp;ldquo;princess and the dragon&amp;rdquo;. Now what crappy search engine cannot return
&amp;ldquo;princess and the dragon&amp;rdquo; when searching &amp;ldquo;princess&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Onto my recommendation. &lt;a href="https://en.wikipedia.org/wiki/Clarkson%27s_Farm"&gt;Clarksons Farm&lt;/a&gt; is
hilarious and well worth a watch. I&amp;rsquo;d also recommend watching all episodes from season four of
&lt;a href="https://en.wikipedia.org/wiki/The_Grand_Tour"&gt;The Grand Tour&lt;/a&gt; - I haven&amp;rsquo;t laughed like that in
a while.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;The first half of September was me working really hard to push out the firewall feature with the
second half mostly devoted to things unrelated to it. My job - the one that pays my bills - is
still important to me and must take priority when needed. We&amp;rsquo;re moving to a new platform, have
several big features to produce and need to upskill all at the same time. For me personally, as
soon as I deliver my current scope of work, I could be seconded to another team to aid their
work on the container platform. So this has required me to commit some of my in and out-of-hours
work to Kubernetes education. In between that, time has also been spent increasing my
proficiency in Go for potential job opportunities that (have) and may come up in the future.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Plan and prove that features or ideas are going work/fail before jumping in. Patience&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deployed without issue a long awaited feature&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;p&gt;No goals, or rather I am taking the month to freestyle the next thing to do. Not setting goals
is setting a goal to fail, or something like that, but I need a month to relax my mind. My wife
has had some health issues, COVID garbage is constant and working from home whilst homeschooling
ain&amp;rsquo;t easy. But, that should all ease off towards the end of the month, so I&amp;rsquo;m taking until then
to just experiment.&lt;/p&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-09-01"
 let end_date = "2021-09-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-09-01"
 let end_date = "2021-09-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-09-01"
 let end_date = "2021-09-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I really haven&amp;rsquo;t been playing the Twitter game much lately. Struggle thinking of things to say!
&lt;img src="twit-sept-stats.png" alt="" title="@dansult twitter stats for September 2021"&gt;&lt;/p&gt;</description></item><item><title>August 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-aug-2021/</link><pubDate>Wed, 01 Sep 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-aug-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Productivity took a nosedive this month, as did my own mental health. Thankfully it&amp;rsquo;s rebounding
and I&amp;rsquo;m mostly back on my own schedule and feeling good.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Setup my own &lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt; server&lt;/li&gt;
&lt;li&gt;Enabled &lt;a href="https://status.mudmap.io/"&gt;Upptime&lt;/a&gt; monitoring for &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Took a week off to learn some Golang&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-july-2021/"&gt;July&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="release-firewall-rules-page"&gt;Release firewall rules page&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Did not complete this&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C (it&amp;rsquo;s looking great though&amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This page is really starting to take shape and works well but is still a work in progress. The
amount of work in creating nice forms was underestimated.&lt;/p&gt;
&lt;p&gt;An example of what the rules page overview will look like. The styling and fields for the table
are still in flux but the framework is there. The nice thing about building it in javascript is
how snappy it feels when jumping between interfaces.&lt;/p&gt;
&lt;p&gt;&lt;img src="firewall-overview-aug-retro.png" alt="" title="pfSense firewall rules overview page"&gt;&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;edit rule&lt;/em&gt; form is also coming along. The slowness has come mostly from me figuring out the
conditionals and &lt;code&gt;react-hook-form&lt;/code&gt;. For instance, if the source is &lt;em&gt;any&lt;/em&gt; then no source address
or CIDR ranges should be applied. However, a single host source address requires a source
address but &lt;em&gt;not&lt;/em&gt; a CIDR range, and a source network requires both an IP source address &lt;em&gt;and&lt;/em&gt;
CIDR. The form is littered with those sorts of distinctions and its sometimes a bit of grind
working through those and other edge cases - &lt;a href="https://www.iana.org/assignments/icmp-parameters/icmp-parameters.xhtml"&gt;ICMP&lt;/a&gt;, I&amp;rsquo;m looking at you.&lt;/p&gt;
&lt;p&gt;&lt;img src="firewall-form-aug-retro.png" alt="" title="pfSense edit rule form page"&gt;&lt;/p&gt;
&lt;h3 id="write-a-mudmap-blog-post"&gt;Write a Mudmap blog post&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Did not publish this&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: D&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The post is pretty big but not finished yet. I could release it over a few parts but would
prefer to make it a long-form post. Still a bit to do and finding time for it was hard this month.&lt;/p&gt;
&lt;h2 id="a-rough-patch"&gt;A rough patch&lt;/h2&gt;
&lt;p&gt;No denying it, this month was tough on me mentally. A number of things that have been&lt;br&gt;
background noise over the last year suddenly came to the forefront of my mind. It brought my
productivity to halt and put me in a bad place. This can happen to anyone for any reason in
their life, and as a solo developer, there isn&amp;rsquo;t anyone else to pick up the slack.&lt;/p&gt;
&lt;p&gt;The two biggest pieces that affected me were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Australia&amp;rsquo;s dip into oppression over Covid&lt;/li&gt;
&lt;li&gt;Afghanistan and the treatment of men like me&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="lockdown"&gt;Lockdown&lt;/h3&gt;
&lt;p&gt;The first couple of weeks of being reduced to one hour of outside activity hit me harder than
expected. Sounds easy at first but having young children and a relatively small house means
there is &lt;em&gt;no escape&lt;/em&gt; from the chaos (and joy) that kids bring. On paper, it seems fine, just a
routine; wake up, walk to the coffee shop as a family, walk home, find things to entertain a
child all day without going outside or anywhere. Except both parents also have full-time work.
After the second week we have settled into a bit of a routine - it&amp;rsquo;s not perfect, but it&amp;rsquo;s working.
Still, it&amp;rsquo;s not great for my girl who loves her friends and is very active - kids and adolescents
are paying a high price, so much that child suicide is now being normalised.&lt;/p&gt;
&lt;h3 id="afghanistan"&gt;Afghanistan&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;ve turned on the news (hopefully you didn&amp;rsquo;t watch it for too long! mind-rot that stuff)
you&amp;rsquo;ve likely seen the Afghan coverage. I did my service in the country and am proud of it -
perhaps the best times of my life were there. For a professional soldier, fighting is something
you &lt;em&gt;want&lt;/em&gt; to do, not something you &lt;em&gt;endure&lt;/em&gt;. The hardest pill to swallow with the withdrawal
has been the number of conversations with people who are overnight geopolitical experts. Asking
me things like &amp;ldquo;how does it feel to know it was all a waste of time&amp;rdquo; or &amp;ldquo;would you say that it
was worth it?&amp;rdquo;. These questions betray the asker&amp;rsquo;s naivety about the nature of combat and
soldiers or dare I say, warrior culture.&lt;/p&gt;
&lt;p&gt;The fall of Afghanistan was not if but when. Not only that but it &lt;em&gt;is&lt;/em&gt; what the media and public
have wanted since the late 2000s. Let them stand up on their own two feet and hand over power -
Hamid Karzai was inserted with the Green Berets in 2001 to start the removal and handover of
power. It was not unexpected yet the media went to town on the predicament, blissfully ignorant
to the many factors at play. As long as they get some clicks and outrage - the end justifies the
means, right?&lt;/p&gt;
&lt;p&gt;There is more to this story but that is all I am comfortable sharing today. At the end of
the day, a few things compounded over a couple of weeks that had a real effect on me, and it
shows up in this retrospective and my performance.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;A couple choice things I&amp;rsquo;ve discovered this month.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=bxABOiay6oA&amp;amp;ab_channel=MovingArt"&gt;Fantastic fungi&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A really interesting documentary - easy watching yet intriguing. Perhaps the most interesting to
me was the fusion of mushroom spore with termite insecticide. Maybe I need to increase my
variety of mushrooms instead of just the woolies button mushroom!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Dune_(novel)"&gt;Dune&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A book that everyone has heard of. After reading &lt;a href="https://en.wikipedia.org/wiki/The_Three-Body_Problem_(novel)"&gt;The Three Body Problem&lt;/a&gt;
I found that reading science fiction before bed was really relaxing and helped me to unwind. So,
I deceided to tick off a few of the &lt;em&gt;must read before you die&lt;/em&gt; type sci-fi titles. I&amp;rsquo;m about
half way through the first book and look forward to reading it every night. I&amp;rsquo;m unlikely to
watch the movie as every movie adaptation of a good book has been terrible, Ready Player One - I
want my money back please. But, I might have watched it if I hadn&amp;rsquo;t read it first and am glad I
finally got around to it.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;A bit of write off a month. Hopefully, this month is better!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be more grateful; &lt;em&gt;get want you want and want what you get&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I rebounded quite well at the backend of this month by letting go and venting with some friends over issues that plagued me. This release was more beneficial than I expected in hindsight.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next months goals&lt;/h2&gt;
&lt;p&gt;Repeating this month&amp;rsquo;s goals.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Release firewall rules page&lt;/li&gt;
&lt;li&gt;Write a Mudmap blog post&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-08-01"
 let end_date = "2021-08-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-08-01"
 let end_date = "2021-08-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-08-01"
 let end_date = "2021-08-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="twitter-aug.png" alt="" title="Mudmap plausible stats for August 2021"&gt;&lt;/p&gt;</description></item><item><title>July 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-july-2021/</link><pubDate>Wed, 04 Aug 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-july-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;This month really got away from me. Mudmap&amp;rsquo;s development has not slowed, but it has not been
hitting the goals I&amp;rsquo;ve set this month. Instead, I&amp;rsquo;ve adapted to a changing landscape and user needs.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Mudmap gets a free tier&lt;/li&gt;
&lt;li&gt;Static IP addresses are now standard for Mudmap&amp;rsquo;s servers&lt;/li&gt;
&lt;li&gt;A shift in focus and support for pfSense versions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-june-2021/"&gt;June&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="cold-email-at-least-15-people"&gt;Cold email at least 15 people&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Probably set the bar too low here; only a few replies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did this, and got probably the market expected percentage of responses. All the responses
mentioned a need for locking down their SSH port with some static IP addresses.&lt;/p&gt;
&lt;p&gt;It was from this feedback that I chose to prioritise the deployment of a proxy in front of
Mudmap. Setting the proxy allowed for a pair of static IP&amp;rsquo;s to be placed in front of Mudmap.
The proxy is completely transparent to users even with SSH tunnelling. By doing this, users can
now set source addresses for SSH - a huge security increase.&lt;/p&gt;
&lt;p&gt;Some might wonder why my servers don&amp;rsquo;t have a static IP already? The hosting platform,
&lt;a href="https://render.com"&gt;Render.com&lt;/a&gt;, does not provide static addresses for any of its containers.
A third party service, &lt;a href="https://quotaguard.com"&gt;QuotaGuard&lt;/a&gt; integrates well with Render and
hooking it into my application took about 2 hours. I am pretty happy with the service and whilst
they don&amp;rsquo;t offer a free tier, I feel the starter plan is quite generous.&lt;/p&gt;
&lt;h3 id="add-firewall-rules-to-mudmap"&gt;Add firewall rules to Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: C&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: I have built the read-only pages but paused development due to important issues that arose.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So far, Mudmap&amp;rsquo;s feature set is limited to a only a portion of pfSense&amp;rsquo;s. The vision is grand
and will eventually cover the majority of its features. Firewall rules, including the ability
to read, create, update and delete them is a high priority. Unfortunately, other issues came up
that forced this into the paused, or blocked state. I could deploy it as a read-only copy but
have chosen to instead deploy it once its finished properly.&lt;/p&gt;
&lt;p&gt;A work in progress look.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mudmapio.s3.us-west-2.amazonaws.com/public-images/marketing/mm-firewall-rules-wip.png" alt="" title="Mudmap firewall rules work-in-progress image"&gt;&lt;/p&gt;
&lt;h3 id="record-some-videos-for-onboarding-new-clients"&gt;Record some videos for onboarding new clients&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Did not start this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: F&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did not even get started on this.&lt;/p&gt;
&lt;p&gt;I did create a landing page for new users inside the application itself. Initially, when a user
register an account and logged in they were presented an empty table. That&amp;rsquo;s not very welcoming
and makes the assumption that users &lt;em&gt;know&lt;/em&gt; how to use Mudmap. Now they get presented with a few
helpful links to get started - this will eventually be upgraded again, likely with a welcome video.&lt;/p&gt;
&lt;p&gt;The welcome page. Unfortunately, I didn&amp;rsquo;t get screenshot of the empty tables as a comparison.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://mudmapio.s3.us-west-2.amazonaws.com/public-images/marketing/mm-no-devices.png" alt="" title="Mudmap firewall rules work-in-progress image"&gt;&lt;/p&gt;
&lt;h2 id="free-tier"&gt;Free Tier&lt;/h2&gt;
&lt;p&gt;Mudmap is new, lacks social proof and is chasing a market full of security minded folks.
Initially, to get started using the platform I was asking for a subscription. But, this was, as
you would expect, not working too well. Before someone could even evaluate the performance,
reliability and security of Mudmap they were asked for a payment method. I wanted to reduce
this friction and increase user uptake.&lt;/p&gt;
&lt;p&gt;So I decided to offer a &lt;em&gt;free tier&lt;/em&gt; for all users.&lt;/p&gt;
&lt;p&gt;Every user can now add their first &lt;strong&gt;two&lt;/strong&gt; devices for free. No credit card needed and no time
limit. It also lets small businesses or hobbyists who have a couple of devices test it out.&lt;/p&gt;
&lt;p&gt;In hindsight this should have been released from the start. I&amp;rsquo;m hoping this will bring in some
more feedback of the system too.&lt;/p&gt;
&lt;h2 id="social-media"&gt;Social Media&lt;/h2&gt;
&lt;p&gt;This month I spent some time upping Mudmap&amp;rsquo;s social media presence. You can now find it at:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://linkedin.com/company/mudmap"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/mudmapio"&gt;Twitter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://buttondown.email/mudmapio"&gt;Buttondown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;rsquo;s always that fine line between development of the product and marketing. The developer in me
always thinks it is not good enough to spruik yet. This, I feel, is a problem all developers have.&lt;/p&gt;
&lt;h2 id="a-problem-and-shift-in-mudmaps-offering"&gt;A problem and shift in Mudmap&amp;rsquo;s offering&lt;/h2&gt;
&lt;p&gt;Late this month, I started to see a uptick of users and a correlating spike of errors. The
investigation into this lead me to a resolution I always felt was probable but hopefully unlikely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; Mudmap is ceasing support for pfSense+&lt;/p&gt;
&lt;p&gt;At the start of this year, Netgate elected to split pfSense into a closed and open-source model.
The newer and closed source &lt;a href="https://www.netgate.com/blog/announcing-pfsense-plus"&gt;pfSense+&lt;/a&gt; is for the time being largely the same as pfSense
Community Edition (CE). Unfortunately for Mudmap (yet, fortunate for everyone else) they&amp;rsquo;ve
made changes that have effected the product.&lt;/p&gt;
&lt;p&gt;Initially, I attempted to reverse engineer a workaround to the most recent breaking change
between pfSense+ and Mudmap. But after careful consideration, I decided this course of action
is not in the best interested of customers.&lt;/p&gt;
&lt;p&gt;Why? Providing a reliable and safe platform from which customers can manage multiple
pfSense devices remotely is Mudmap&amp;rsquo;s core mission statement. This cannot be achieved without a
stable platform to build from. Providing an interface that could potentially cause disruption
or worse for customers is a risk I &lt;em&gt;will&lt;/em&gt; not take.&lt;/p&gt;
&lt;p&gt;Naturally, I am disappointed, and it will reduce Mudmap&amp;rsquo;s market appeal - possibly quite
significantly - but I&amp;rsquo;m not losing hope.&lt;/p&gt;
&lt;p&gt;So where to from here?&lt;/p&gt;
&lt;p&gt;Mudmap will continue to support pfSense CE and develop functionality to meet the needs of that
user base. I&amp;rsquo;ll also be keeping a close eye on the developments of pfSense+. I think it is
important to disclose how excited I am for it and that I fully support Netgate&amp;rsquo;s decision to
close source the project. I expect pfSense+ to be a cut above the rest when it re-launches
after its rewrite in &lt;a href="https://www.netgate.com/blog/pfsense-plus-pfsense-ce-dev-insights-direction"&gt;Golang&lt;/a&gt;. This should also deploy with an API very similar to &lt;a href="https://docs.netgate.com/tnsr/en/latest/api/"&gt;TNSR&lt;/a&gt;&amp;rsquo;s. As
both a user and developer, this makes me really happy. It should also allow Mudmap to push back
into the market of supporting pfSense+ clients as I will (hopefully) be able to hook into the
new API.&lt;/p&gt;
&lt;p&gt;I have written a page in the &lt;a href="https://docs.mudmap.io/pfsense-community-and-plus"&gt;documentation&lt;/a&gt; explaining the change in support, updated the
&lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;homepage&lt;/a&gt; and made a toggle in the application itself ensuring that users are aware of
this. I will be writing a blog post, newsletter and LinkedIn post in response as well.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Interesting topics&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Whilst I am not a pentester or bug bounty hunter, I thoroughly enjoy watching videos on the
subjects. Having binge watched most of &lt;a href="https://www.youtube.com/channel/UCa6eh7gCkpPo5XXUDfygQQA"&gt;ippsec&lt;/a&gt;&amp;rsquo;s videos, I recently started on &lt;a href="https://www.youtube.com/channel/UCVeW9qkBjo3zosnqUbG7CFw"&gt;John Hammond&lt;/a&gt;&amp;rsquo;s
stuff. If you&amp;rsquo;re a developer, there is a lot of information to glean from how these guys go
about exploiting &lt;em&gt;your&lt;/em&gt; servers.&lt;/p&gt;
&lt;p&gt;Worth a mention, &lt;a href="https://www.youtube.com/channel/UCa6eh7gCkpPo5XXUDfygQQA"&gt;ippsec&lt;/a&gt; has a handy &lt;a href="https://ippsec.rocks/?#"&gt;search page&lt;/a&gt; where you can filter topics
and find which video to watch based on it. For instance, if you searched &lt;code&gt;jwt&lt;/code&gt;, it will show
you a selection of boxes he&amp;rsquo;s pwned with JWT&amp;rsquo;s in there somewhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;React development cheat code&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you aren&amp;rsquo;t using &lt;code&gt;swr&lt;/code&gt; from &lt;a href="https://swr.vercel.app/"&gt;Vercel&lt;/a&gt;, maybe you should be! &lt;code&gt;react-query&lt;/code&gt; is apparently just
as good - and I believe it, it&amp;rsquo;s from &lt;a href="https://react-query.tanstack.com/"&gt;Tanner Linsley&lt;/a&gt;. I can only talk about &lt;code&gt;swr&lt;/code&gt; and say that
its made my Next.js app blazing &lt;em&gt;fast&lt;/em&gt; - it literally feels like a cheat code for client side
web. It integrates perfectly with &lt;code&gt;axios&lt;/code&gt; too. As a heavy user of &lt;code&gt;axios&lt;/code&gt; interceptors for JWT
refreshing, I initially saw all the docs using &lt;code&gt;fetch&lt;/code&gt; and thought maybe interceptors wouldn&amp;rsquo;t
work. Thankfully it worked flawlessly.&lt;/p&gt;
&lt;p&gt;Podcast of the month:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everything on the &lt;a href="https://httotw.com/"&gt;How to Take Over The World Podcast&lt;/a&gt;. My favourite is the Putin series and Caesar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;This month was a bit of test. It started quite well but sadly ended on a sour note. Nonetheless,
I&amp;rsquo;m looking forward to August and producing more content for users of Mudmap to enjoy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Market Mudmap&amp;rsquo;s potential rather than internally focus on what it&amp;rsquo;s not providing right now&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adapted to changing circumstances and took action when needed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next months goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Actually release the firewall rules pages&lt;/li&gt;
&lt;li&gt;Publish a Mudmap blog post&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-07-01"
 let end_date = "2021-07-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-07-01"
 let end_date = "2021-07-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-07-01"
 let end_date = "2021-07-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="twitter-stats.png" alt="" title="Mudmap plausible stats for July 2021"&gt;&lt;/p&gt;</description></item><item><title>June 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-june-2021/</link><pubDate>Fri, 02 Jul 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-june-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;I spent less time on Mudmap this month and relaunched an old project, &lt;a href="https://www.check-redirects.com/?utm_medium=social&amp;amp;utm_source=danielms&amp;amp;utm_campaign=retro-june-2021"&gt;Check-Redirects.com&lt;/a&gt;. Taking a breather from Mudmap and switching gears worked well. I feel much more motivated about the project and in the last few days have done a lot of solid work on it. Nothing comes for free and the price I paid was failing to fulfill one of my goals from last month.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Dropped a new product.&lt;/li&gt;
&lt;li&gt;Next.js and I are becoming good friends.&lt;/li&gt;
&lt;li&gt;Mudmap had a small spike of interest at the beginning of the month but it petered out.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last month&amp;rsquo;s three goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-may-2021/"&gt;May&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="add-firewall-rules-to-mudmap"&gt;Add firewall rules to Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Time got away from me and this didn&amp;rsquo;t get done&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: D&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I &lt;em&gt;did&lt;/em&gt; spend a couple of hours writing out some ideas of how this will get done. They were rough designs and scribbles so nothing of real substance. I&amp;rsquo;m not even sure I know where the sketches would be. Nonetheless, I do have an intuition of how it&amp;rsquo;s going to look and feel for the user.&lt;/p&gt;
&lt;p&gt;The amount of work needed to make this isn&amp;rsquo;t much and would offer another great feature for &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;. Prioritizing this for July.&lt;/p&gt;
&lt;h3 id="email-5-prospects"&gt;Email 5 prospects&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Emailed with some minor correspondence but mostly crickets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Several users have signed up for access to &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; though none have committed to paying for the service. I emailed every single sign-up with extra details and asking for feedback if they feel so inclined. It is a very small sample size but so far only one replied with a feature request.&lt;/p&gt;
&lt;p&gt;The feature is not unwarranted and something I will eventually offer all customers as standard; static IPs for SSH targets. Unfortunately, my hosting platform, &lt;a href="https://render.com"&gt;render.com&lt;/a&gt;, does not offer this, yet. The workaround is to place a &lt;a href="https://www.quotaguard.com/"&gt;load balancer&lt;/a&gt; in front of my server and point users at that. This option isn&amp;rsquo;t free, add latency, and I have not tested the effectiveness of SSH communications through it. More customer data is needed to ascertain how high of a priority it is before I drop other things to complete this.&lt;/p&gt;
&lt;p&gt;Although I&amp;rsquo;ve sent out a few emails those emails were to my warm network - anyone who has signed up has already found &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;. I need to go after a cold network and start planting Mudmap in people&amp;rsquo;s minds. So far, my lack of conversion from sign up to paid is because I&amp;rsquo;m not reaching out to more people.&lt;/p&gt;
&lt;h3 id="reinvigorate-an-old-project-that-i-plan-to-monetize"&gt;Reinvigorate an old project that I plan to monetize&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Successfully re-launched &lt;a href="https://www.check-redirects.com/?utm_medium=social&amp;amp;utm_source=danielms&amp;amp;utm_campaign=retro-june-2021"&gt;check-redirects.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ve come to terms with it. I like building products around network analysis and security. So after chatting with &lt;a href="https://twitter.com/Ruurtjan"&gt;@Ruurtjan&lt;/a&gt; and
&lt;a href="https://twitter.com/andreas_zettl"&gt;@andreas_zettl&lt;/a&gt; about our love for these tools, I decided to take a breather from Mudmap and refactor &lt;a href="https://www.check-redirects.com/?utm_medium=social&amp;amp;utm_source=danielms&amp;amp;utm_campaign=retro-june-2021"&gt;check-redirects.com&lt;/a&gt;. We have a discord for network tool builders - &lt;a href="https://twitter.com/dansult"&gt;DM&lt;/a&gt; or &lt;a href="mailto:dan@danielms.site"&gt;email&lt;/a&gt; me for the link.&lt;/p&gt;
&lt;p&gt;&lt;img src="cr.com-screen.png" alt="check-redirects.com" title="Check Redirects new landing page"&gt;&lt;/p&gt;
&lt;h3 id="what-is-it"&gt;What is it?&lt;/h3&gt;
&lt;p&gt;It is a tool that lets you plug in a URL and quickly get the final URL without having to traverse all the redirects. In doing that, you can analyze each hop along the path including the location data, headers, response body, and latency.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A few use cases:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Checking a shortened links legitimacy&lt;/li&gt;
&lt;li&gt;Bypassing ad networks&lt;/li&gt;
&lt;li&gt;Circumventing network security policies that prevent certain URL&amp;rsquo;s from being opened, for instance, trackers from newsletter sign up pages and the like&lt;/li&gt;
&lt;li&gt;Ascertaining how many redirects you&amp;rsquo;re introducing into your application (Google cares about too many 301&amp;rsquo;s for instance)&lt;/li&gt;
&lt;li&gt;Determining referral links, and if they&amp;rsquo;re working as expected&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The original site was built using Vuejs, though this time I opted to use &lt;a href="https://nextjs.com?ref=danielms.site"&gt;Next.js&lt;/a&gt; as I am now more familiar with React. I figured this was a good opportunity to build another application with it - I build Mudmap&amp;rsquo;s landing page with it last month. This app needed to interface with an API so it felt like a natural stair-step into bigger apps in the future.&lt;/p&gt;
&lt;p&gt;Honestly, so glad I used Next. It&amp;rsquo;s just a great platform and after about 6 weeks of playing around with it, I am convinced it&amp;rsquo;s better than &lt;em&gt;plain&lt;/em&gt; React. I grok the file-based router whereas &lt;code&gt;react-router-dom&lt;/code&gt; feels weird to me and got me with a couple of foot guns when writing Mudmap&amp;rsquo;s dashboard. As a solo developer, I value good &lt;em&gt;Developer Experience&lt;/em&gt; and Next has a great one. They also released Next11 whilst writing it. I upgraded from version 10 to 11 and got their awesome &lt;code&gt;Image&lt;/code&gt; updates without a hiccup, which for a javascript framework is often not the case.&lt;/p&gt;
&lt;h3 id="why-divert-time-from-mudmap"&gt;Why divert time from Mudmap?&lt;/h3&gt;
&lt;p&gt;I want to place multiple bets and explore many different avenues of income generation. I am still employed full-time so these projects are not my sole source of income, which gives me the space to try things and spread myself out a bit. I tend to get a little task fixated which can cause issues so switching to another objective for a short period lets my motivation banks recharge for the thing that&amp;rsquo;s not being worked on.&lt;/p&gt;
&lt;p&gt;Having two different projects on the go is also a means to explore two different models; subscription and affiliate/ad streams. Mudmap is a typical SaaS subscription-based application but &lt;a href="https://www.check-redirects.com/?utm_medium=social&amp;amp;utm_source=danielms&amp;amp;utm_campaign=retro-june-2021"&gt;check-redirects.com&lt;/a&gt; is not, letting me branch out creatively. This fits better with my self-image as I believe I am a generalist - not excelling at one particular thing but being familiar with many different modalities.&lt;/p&gt;
&lt;p&gt;This is something I embraced (but also at times disliked) about my old job in the army. We were across so many tasks but at any one moment not particularly great at any of them. You can&amp;rsquo;t be an expert diver, driver, sniper, demolitionist, parachutist, or whatever all at the same time. You have to re-train the skills when needed. The time between average to good decreases the more you practice this style of &lt;em&gt;rapid upskilling&lt;/em&gt;. I am a big believer in &lt;a href="https://en.wikipedia.org/wiki/T-shaped_skills"&gt;T-shaped skills&lt;/a&gt; but I also know that the leg of the T is in constant flux. At least for those that &lt;em&gt;actively&lt;/em&gt; adopt this approach to life.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;I like listening to podcasts and audiobooks on my commute to the office.&lt;/p&gt;
&lt;p&gt;This months book:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com.au/Company-One-Staying-Small-Business/dp/1328972356"&gt;Company of One&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Worth a listen if you&amp;rsquo;re aren&amp;rsquo;t looking to scale into a massive business. I couldn&amp;rsquo;t do it justice in a short paragraph but so far its an enjoyable listen.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Sidenote&lt;/em&gt;: I want to listen to Phil Jacksons&amp;rsquo; book, &lt;a href="https://www.amazon.com.au/Eleven-Rings-Success-Phil-Jackson/dp/0143125346"&gt;Eleven Rings&lt;/a&gt; on Audible. Unfortunately, it seems to be unavailable to Australian&amp;rsquo;s which kind of pisses me off.&lt;/p&gt;
&lt;p&gt;Podcast of the month:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://podcasts.apple.com/us/podcast/mfm-mini-the-way-to-be-confident/id1469759170?i=1000525326147"&gt;Confidence&lt;/a&gt; by &lt;a href="https://podcasts.apple.com/us/podcast/my-first-million/id1469759170"&gt;My First Million podcast&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;10 minutes or so by &lt;a href="https://twitter.com/theSamParr"&gt;@theSamParr&lt;/a&gt; on how he became so confident. Spoiler, it was a &lt;em&gt;choice&lt;/em&gt; - a learned behavior. I loved how he explained it and have been telling a lot of friends to give it a listen.&lt;/p&gt;
&lt;p&gt;Also, my mate just dropped a couple of clips on youtube from his holiday to New Zealand before our recent (silly) lockdowns here in Australia. He&amp;rsquo;s got a quirky style but makes me laugh and sometimes cringe. &lt;a href="https://www.youtube.com/watch?v=k0iBaniEHX0&amp;amp;ab_channel=PeskyPas"&gt;PeskyPas&lt;/a&gt; if you want to watch.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;I can&amp;rsquo;t believe its July already&lt;/em&gt; 🙃&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Get out of my developer comfort zone and engage with potential customers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If I don&amp;rsquo;t do this my product will die. That&amp;rsquo;s it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Stick to the month&amp;rsquo;s goals&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I can get carried away and miss the objective of the month as I&amp;rsquo;ve set it out here. To fix that, I&amp;rsquo;m just writing them down and sticking them to a post-it note on my monitor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Re-launched an old product and recharged the Mudmap motivation banks&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve been pretty good at listening to my body and mind this month.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I&amp;rsquo;ve needed extra rest, I&amp;rsquo;ve taken it. Also increased my exercise and committed to a gym by using money as a psychological weapon - I&amp;rsquo;m a tight arse and hate parting with money. So, knowing how much each day I don&amp;rsquo;t train costs me is a great motivator.&lt;/p&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I put this here for my future reference.&lt;/p&gt;
&lt;p&gt;&lt;img src="twit-june-2021.png" alt="my twitter june" title="Daniel Michaels twitter analytics for June 2021"&gt;&lt;/p&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-06-01"
 let end_date = "2021-06-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-06-01"
 let end_date = "2021-06-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-06-01"
 let end_date = "2021-06-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;h2 id="next-months-goals"&gt;Next months goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cold email at least 15 people&lt;/li&gt;
&lt;li&gt;Add Firewall rules to Mudmap&lt;/li&gt;
&lt;li&gt;Record some videos for onboarding new clients&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>May 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-may-2021/</link><pubDate>Sun, 06 Jun 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-may-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;A month of ups and downs, at least mentally and emotionally. At the start of May, I was &lt;em&gt;days&lt;/em&gt;
from launching &lt;a href="https://mudmap.io/?utm_campaign=retro-may-21&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;. That turned into a mental hell hole as I encountered several bugs that
weren&amp;rsquo;t apparent until in production - bad programmer, bad! Deployment woes with providers and
other frustrations. After a lot of persistence and some &lt;em&gt;back-to-the-drawing-board&lt;/em&gt; thinking, I
eventually deployed the app. The month ended on a bit of high with some things falling into
place outside of &lt;a href="https://mudmap.io/?utm_campaign=retro-may-21&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; which I might talk about in the next Retro.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deployed &lt;a href="https://mudmap.io/?utm_campaign=retro-may-21&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; Beta into the wild&lt;/li&gt;
&lt;li&gt;Discovered &lt;a href="https://render.com"&gt;Render&lt;/a&gt; and am now a convert&lt;/li&gt;
&lt;li&gt;Stood up the docs using &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt; and started flirting with &lt;a href="https://nextjs.org"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;A review of last months three goals. See &lt;a href="https://danielms.site/retrospectives/2021/retrospective-april-2021/"&gt;April&amp;rsquo;s Retrospective&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="launch-mudmap"&gt;Launch Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: It&amp;rsquo;s live but still got a few kinks to iron out&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think this has been on my list for the last two months. Perhaps, I was a
little hasty or overconfident in the beginning, but I&amp;rsquo;m happy to say its
now live. There are still some issues to sort out but users can sign up and
look around.&lt;/p&gt;
&lt;p&gt;A few issues presented during the deployment, which lead to a lot of trial
and error testing. My original plan of a digital ocean backend and netlify
frontend proved painful. This could be equated to my lack of skill in this
area though. Eventually, I tried out &lt;a href="https://render.com"&gt;Render&lt;/a&gt; after several recommendations.&lt;/p&gt;
&lt;p&gt;Almost immediately things improved. Having the ability to push new builds
and see the output is super helpful. My old method was to build on the VM
(using a CI Runner) after tests passed. It works, but it&amp;rsquo;s a lot of
setup, and it was this initial setup that was punishing. Cross-origin
requests were also beating me up as well.&lt;/p&gt;
&lt;p&gt;Eventually with &lt;a href="https://render.com"&gt;Render&lt;/a&gt; I was able to stand up all the services and static
pages, including domains inside a single yaml file. Render&amp;rsquo;s
Infrastructure as Code capability is really sweet and should help to
prevent redeploys missing some toggle that was clicked in a GUI and long since forgotten about.&lt;/p&gt;
&lt;p&gt;As always, if interested, check it out &lt;a href="https://mudmap.io/?utm_campaign=retro-may-21&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap here&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="start-the-documentation-pages"&gt;Start the documentation pages&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Live and being indexed by Algolia&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before the end of the month I was able to stand up Mudmap&amp;rsquo;s documentation
pages using &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt;. I highly recommend the framework, it&amp;rsquo;s the easiest
tool I have used by far for this type of thing. The setup took about an hour or
maybe two (optimistic memory aside) which for a javascript framework is really
good! Writing the detailed documentation with images, much longer.&lt;/p&gt;
&lt;p&gt;Mudmap&amp;rsquo;s documentation isn&amp;rsquo;t complete, but the most important pages are. I am
making an effort to write a portion of a page every day as I feel Mudmap
needs good docs. It&amp;rsquo;s a complex system with several moving parts for users to configure before they
get started using the application.&lt;/p&gt;
&lt;p&gt;You can read more &lt;a href="https://docs.mudmap.io/preparing-devices"&gt;here&lt;/a&gt; to see what I mean.&lt;/p&gt;
&lt;p&gt;It is also hosted on &lt;a href="https://render.com"&gt;Render&lt;/a&gt; as a static site for free. 💰&lt;/p&gt;
&lt;p&gt;Check them out at &lt;a href="https://docs.mudmap.io/"&gt;docs.mudmap.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If looking for a knowledge base tool that you can be hosted, I would
consider &lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt; one of the best. Really simple but elegant and has
support for search through either self-hosted or managed Algolia.&lt;/p&gt;
&lt;p&gt;I think it looks great 👇&lt;/p&gt;
&lt;p&gt;&lt;img src="docs-retro-may21.png" alt="docusaurus-mudmap" title="Mudmap's Documentation Pages"&gt;&lt;/p&gt;
&lt;h3 id="replace-the-pricing-page-and-if-possible-embed-it-in-hugo-as-a-react-component"&gt;Replace the pricing page, and if possible, embed it in Hugo as a React component&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: I&amp;rsquo;ll go one better! Replace the landing page&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C+&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I ended up looking at rewriting the entire landing page rather than trying to fit
React into Hugo. Now, I do love Hugo - this is a Hugo blog though most of
my work is between python and javascript. So I wanted to decrease any
possible mental context switching between languages and frameworks. It&amp;rsquo;s also a great
opportunity to make the site
more aesthetically pleasing.&lt;/p&gt;
&lt;p&gt;Technically, I didn&amp;rsquo;t complete this goal. I did find another way to achieve
a better result, so I&amp;rsquo;m being favourable to myself in the rating.&lt;/p&gt;
&lt;p&gt;End state, love Hugo but want to unify the language stack I am using, so
I&amp;rsquo;ll be switching to React/Next.js&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re on Discord and looking for a great Maker/Indie Hacker like community, I recommend
&lt;a href="https://www.lunadio.com/"&gt;Lunadio&lt;/a&gt;. I recently participated in one their weekly Mastermind calls and it was really
interesting and great to hear others in a similar position as me. The Discord channel is also
full of interesting people solving problems and sharing what they&amp;rsquo;ve learnt with everyone else.
Admittedly, I could be more active on there but even in a passive capacity (which I don&amp;rsquo;t
recommend; get involved 💪) its super useful.&lt;/p&gt;
&lt;h3 id="twitter"&gt;Twitter&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve really started to get involved in sharing my journey on Twitter. It&amp;rsquo;s been really helpful
for my personal development - you meet and talk to soo many great folks. Several times I&amp;rsquo;ve
reached out and asked questions of those around me, and it has ended up being more valuable to me
than I&amp;rsquo;d ever expected.&lt;/p&gt;
&lt;p&gt;My &amp;ldquo;&lt;em&gt;stats&lt;/em&gt;&amp;rdquo; are down in everything but followers, though I don&amp;rsquo;t really care because I&amp;rsquo;m having
fun and making friends.&lt;/p&gt;
&lt;p&gt;&lt;img src="may-twitter.png" alt="May Twitter" title="May's twitter stats"&gt;&lt;/p&gt;
&lt;p&gt;May saw me join in a Twitter experiment created by &lt;a href="https://twitter.com/meetkevon"&gt;@MeetKevon&lt;/a&gt; called
&lt;a href="https://twitter.com/search?q=%2330daysinpublic"&gt;#30DaysinPublic&lt;/a&gt;. It&amp;rsquo;s been great. Every second day &lt;a href="https://twitter.com/meetkevon"&gt;Kevon&lt;/a&gt; sends an email with a
&lt;em&gt;Twitter task&lt;/em&gt;, which is to write a themed tweet. Having a task and then having to think of
things to tweet about that has been great for my ability to think of sharable items. Usually, I
think everything I do isn&amp;rsquo;t worth sharing - its probably boring, or been done many times over.
Being held to account and committing to a cause has really helped overcome that - who cares if
what I tweet &lt;em&gt;is boring&lt;/em&gt;. If people don&amp;rsquo;t want to read it, or don&amp;rsquo;t like me that&amp;rsquo;s fine and
welcomed, after all, getting people to like you is not &lt;em&gt;your &lt;a href="https://www.amazon.com.au/Courage-Disliked-Japanese-phenomenon-happiness-ebook/dp/B06XSGNN61"&gt;task&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Project/Task Management&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am bit all over the shop with this. I have Notion but its a bit &lt;em&gt;heavy&lt;/em&gt; and &lt;em&gt;clicky&lt;/em&gt; for me
sometimes. I&amp;rsquo;m a Vim guy, mice scare me.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kill off distractions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;ve read Cal Newport&amp;rsquo;s books - an aside, I think &lt;a href="https://www.amazon.com.au/Good-They-Cant-Ignore-You/dp/1455509124"&gt;So Good They Can&amp;rsquo;t Ignore You&lt;/a&gt; is
far superior to &lt;a href="https://www.amazon.com.au/Deep-Work-Focused-Success-Distracted/dp/0349411905/"&gt;Deep Work&lt;/a&gt;. Distractions are ever present, but I really need to switch
off that time-sink/mind-rot machine called a phone when I &lt;em&gt;actually&lt;/em&gt; want to get work done. Days
where I replace the phone in my pocket with a notebook and pen are far more productive.&lt;/p&gt;
&lt;p&gt;Getting real off-topic. I had an idea a few months back about a product which is basically a box
you put all the families phones in. It has a timer and once set, locks the box until that time
(or an emergency override is hit). If I could be arsed, I make it and call it &lt;strong&gt;Family Time&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I pushed through and got &lt;a href="https://mudmap.io/?utm_campaign=retro-may-21&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; into the wild&lt;/li&gt;
&lt;li&gt;Segmented my time quite well between development, writing docs, and planning the next items&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="google-ranking"&gt;Google ranking&lt;/h3&gt;
&lt;p&gt;Nearly forgot but checking my Plausible stats, I noticed there is some google search terms that
pop up frequently. I did a search using a VPN and in incognito mode (just to be sure) and Mudmap is
Second!&lt;/p&gt;
&lt;p&gt;&lt;img src="google-rank.png" alt="mudmap google" title="Google ranking for Mudmap"&gt;&lt;/p&gt;
&lt;h2 id="next-months-goals"&gt;Next month&amp;rsquo;s goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Add a Rule set feature to Mudmap&lt;/li&gt;
&lt;li&gt;Email 5 prospects&lt;/li&gt;
&lt;li&gt;Reinvigorate an old project that I plan to monetize (long term SEO play now for reward later)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-05-01"
 let end_date = "2021-05-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-05-01"
 let end_date = "2021-05-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-05-01"
 let end_date = "2021-05-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>April 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-april-2021/</link><pubDate>Sun, 02 May 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-april-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Wrote a lot of &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;&amp;rsquo;s new frontend, integrated it with the backend, and
published another blog post for the site. I did not get all that I wanted to be done
within the month, though.&lt;/p&gt;
&lt;p&gt;Made the transition from using &lt;a href="https://stripe.com"&gt;Stripe&lt;/a&gt; to &lt;a href="https://paddle.com"&gt;Paddle&lt;/a&gt; after a wonderful exchange of
Tweets which led to a phone call with Paddle. They were incredibly helpful and
offered some points of refinement for the business. It cemented my commitment
to Paddle as I was treated like a valuable addition to their cause, instead
of a faceless and numbered money-generating object.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Integrated the new dashboard&lt;/li&gt;
&lt;li&gt;Published a post and sent out a newsletter for &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Finished the MVP feature-set for &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; minus the payment system&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;h3 id="1-deploy-mudmaps-application-for-beta-testing"&gt;1. Deploy Mudmap&amp;rsquo;s application for beta testing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Did not complete all the tasks necessary to do this 😞&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: D&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why a &lt;em&gt;D&lt;/em&gt; if I failed to launch it? Seems pretty binary.&lt;/p&gt;
&lt;p&gt;I accomplished a lot during this month and although the goal wasn&amp;rsquo;t completed,
I am reluctant to call it a &lt;em&gt;failure&lt;/em&gt;. Having a time box around this really
helped to keep me focused, made it easy to cut distractions, and in general,
pushed me hard to achieve it. That is not a failure in my eyes.&lt;/p&gt;
&lt;p&gt;So what did I get done?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;structlog&lt;/code&gt; for better logging management (also have Sentry)&lt;/li&gt;
&lt;li&gt;Improved the SSH Client error and timeout handling&lt;/li&gt;
&lt;li&gt;Reworked the device registration flow in the front and back end&lt;/li&gt;
&lt;li&gt;Built the React frontend sign in/out, registration, etc from scratch&lt;/li&gt;
&lt;li&gt;Integrated &lt;code&gt;dj-rest-auth&lt;/code&gt; after wasting time rolling my own cookie auth
&lt;ul&gt;
&lt;li&gt;This took a lot of time out of the month but it gave me so much for free that it was worth it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Replaced &lt;code&gt;fetch&lt;/code&gt; with &lt;code&gt;axios&lt;/code&gt; on the frontend to make JWT&amp;rsquo;s so much easier to manage&lt;/li&gt;
&lt;li&gt;Began the &lt;a href="https://paddle.com"&gt;Paddle&lt;/a&gt; integration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More importantly, what &lt;em&gt;didn&amp;rsquo;t&lt;/em&gt; I get done that prevented this from being a
success (i.e. launching the MVP on time):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Payment integration&lt;/li&gt;
&lt;li&gt;Underlying API tweaks (namely around the installer redundancy)&lt;/li&gt;
&lt;li&gt;Deployment testing (I see some initial difficulty getting &lt;a href="https://traefik.io"&gt;Traefik&lt;/a&gt; and &lt;a href="https://netlify.com"&gt;netlify&lt;/a&gt; to play nice)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Will I knock it out this month? I really do hope so!&lt;/p&gt;
&lt;h4 id="demonstrating-functionality"&gt;Demonstrating functionality&lt;/h4&gt;
&lt;p&gt;Here is a short clip of the dashboard&amp;rsquo;s device detail in action. It&amp;rsquo;s a bit neater
now, but the rough functionality is the same. It will at a glance give you up-to-date
information about the pfSense system, as well as let you start, stop or
restart system services.&lt;/p&gt;
&lt;p&gt;&lt;img src="dashboard-light-mvp.gif" alt=""&gt;&lt;/p&gt;
&lt;h3 id="2-write-a-blog-post-about-pfsense"&gt;2. Write a blog post about pfSense&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Done!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href="https://mudmap.io/blog/what-is-pfsense/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;What is pfSense?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A pretty easy goal but important. If I did not set this, I would not have done
any content marketing for &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; because I spend all my time down in weeds writing code
instead.&lt;/p&gt;
&lt;p&gt;I like writing, but it is definitely not something that I am gifted at.
It is very humbling when you think you know a subject but struggle to write
about it in a coherent and concise manner. Though, that is why you have to
keep producing and perfecting it as a craft.&lt;/p&gt;
&lt;h3 id="3-increase-mudmaps-marketing"&gt;3. Increase Mudmap’s marketing&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Average to poor - didn&amp;rsquo;t prioritise this as I should have&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: C&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wrote a blog post, publicised that on Twitter, and pushed out a newsletter to
my audience of zero! Can only go up from here. Now as I reflect, I see that
focusing so hard on knocking out code, so I can launch the MVP is also a bit of
a trap. The more marketing I do, the more awareness, the more feedback
and, the more validation I will get.&lt;/p&gt;
&lt;p&gt;On the topic of emails, I did find a great email template generator; &lt;a href="https://mjml.io"&gt;mjml&lt;/a&gt;.
I have not used it for any of my emails just yet, but it looks brilliant. Trying
to style an email (in my opinion) is a complete tyre-fire.&lt;/p&gt;
&lt;p&gt;I am, however, working on my Twitter game. It&amp;rsquo;s been a lot of fun so far. Trying
to write short 280 character messages that convey meaning is a lot harder than
I originally thought. Having to word things carefully has been great for my
communication skills - especially as our society&amp;rsquo;s attention spans continue to
rapidly decrease. It serves as my primary marketing platform for &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; at
the moment as well - something that will need to be adjusted as time goes on.
This makes it an important skill to refine and work on which is why I am investing
in it.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;I listened to Lex Friedman&amp;rsquo;s podcast; &lt;a href="https://lexfridman.com/robert-breedlove/"&gt;176 Robert Breedlove&lt;/a&gt;. Even if you
aren&amp;rsquo;t into bitcoin, it is a wonderful podcast with some interesting views on
the current state of the financial world. I highly recommend it, if you&amp;rsquo;re only
slightly interested in finance.&lt;/p&gt;
&lt;p&gt;Not a recommendation. A sad note. I am getting older! I am now working with
a lot of people that have never seen Home Alone or Jurassic Park. Thankfully, the guy
next to me is my age and &lt;em&gt;we&lt;/em&gt; can reminisce about the glory days of movies 🤣&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve nearly finished &lt;a href="https://en.wikipedia.org/wiki/The_Three-Body_Problem_(novel)"&gt;The Three Body Problem&lt;/a&gt;. It&amp;rsquo;s a great book, even if
most of the physics is above my pay grade (it is still understandable). I am
already looking forward to the next book in the trilogy. I usually read non-fiction
but have opted for fiction before bed these days as I need to unwind.&lt;/p&gt;
&lt;p&gt;This month I learned of &lt;a href="https://litestream.io/"&gt;litestream&lt;/a&gt;. I think it&amp;rsquo;s a project that has real potential - not
every application needs Postgres or some other server-based SQL solution.
For a great introduction, see this Tweet.&lt;/p&gt;
&lt;blockquote class="twitter-tweet"&gt;
 &lt;a href="https://twitter.com/dansult/status/1387768253854986247"&gt;&lt;/a&gt;
&lt;/blockquote&gt;
&lt;script async src="https://platform.twitter.com/widgets.js" charset="utf-8"&gt;&lt;/script&gt;


&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Marketing for Mudmap,&lt;/li&gt;
&lt;li&gt;Documentation - I am going to have a documentation fest once &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt; launches,
but it would be much easier to have done parts of it as I developed instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&amp;rsquo;ve knuckled down and accomplished a lot this month. So much that it wasn&amp;rsquo;t
until I started this retro and looked back that I realised &lt;em&gt;how much&lt;/em&gt; I have done,&lt;/li&gt;
&lt;li&gt;Went from having never used React to implementing a fully functional application with authentication.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next months goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Launch &lt;a href="https://mudmap.io/?utm_campaign=retro&amp;amp;utm_source=danielms&amp;amp;utm_medium=blog"&gt;Mudmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Start the documentation pages&lt;/li&gt;
&lt;li&gt;Replace the pricing page, and if possible, embed it in Hugo as a React component&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-04-01"
 let end_date = "2021-04-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;check-redirects.com
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-check-redirects.com'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='check-redirects.com-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "check-redirects.com"
 let start_date = "2021-04-01"
 let end_date = "2021-04-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-check-redirects.com')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'check-redirects.com-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-04-01"
 let end_date = "2021-04-30"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>March 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-march-2021/</link><pubDate>Fri, 02 Apr 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-march-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Launched &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt;&amp;rsquo;s landing page and blog! So far it&amp;rsquo;s been getting very minor
traffic but that is okay - it is still in development. I did however get an
email from a potential client asking to join in a beta in the meantime. That
sort of feedback really fuels the fire and has me working hard to get the MVP
out the door as fast as reasonably possible.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deployed &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt;&amp;rsquo;s landing page!&lt;/li&gt;
&lt;li&gt;Started the implementation of a React frontend for the application.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;Each month I write up how I went in the pursuit of last month&amp;rsquo;s goals. It is
also somewhere I can expand on the things I&amp;rsquo;ve done. Helpful for historical
review and analysis.&lt;/p&gt;
&lt;h3 id="finish-the-copywriting-on-mudmaps-landing-page"&gt;Finish the copywriting on mudmap’s landing page&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Deployed the entire landing page!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A+&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The copy is not perfect, and I will need to refine it continually, but it is
out there and getting read by potential customers.&lt;/p&gt;
&lt;p&gt;I had originally written all the copy in Django templates, and it seemed to
take a lot longer, and be more prone to ugliness than I was happy with. Most of
this is due to my lack of design skills. Nonetheless, I started a quick google
for some Hugo themes and found a great one that suited my style.&lt;/p&gt;
&lt;p&gt;Within 48 hours, I had cloned the theme repo, created my own &lt;code&gt;website&lt;/code&gt; repo and
deployed it to Netlify. It is now being indexed by google and something I can
work with to produce content that might attract more users in the future.&lt;/p&gt;
&lt;p&gt;I listened to a podcast that featured Pierre De Wulf
of &lt;a href="https://scrapingbee.com"&gt;ScrapingBee.com&lt;/a&gt; and was inspired by his experience running a
SaaS business that failed. He was able to sell it because it was generating a
lot of traffic. Pierre, said that their content marketing was what made it
sellable.&lt;/p&gt;
&lt;p&gt;It made me realize that everything that holds value is an asset, and valuable
things can be created anywhere and from the seemingly inconsequential. That&amp;rsquo;s
why I launched the site prematurely, it&amp;rsquo;s a way to start raising awareness,
keep me motivated and perfect my craft while no one really cares.&lt;/p&gt;
&lt;h3 id="integrate-stripe-as-the-payment-processor"&gt;Integrate Stripe as the payment processor&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Abandoned it for now as I am ripping out templates and
replacing them with a React and Django SPA for the dashboard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: F&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I spent a lot of time on this, and it was one of those &lt;em&gt;nail in the coffin&lt;/em&gt;
moments for the template system. I was getting frustrated with all the inline
javascript in &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt; already, and it was made worse with Stripe. Not Stripe&amp;rsquo;s
fault, just the way it has to be.&lt;/p&gt;
&lt;p&gt;So, in a way this &lt;strong&gt;F&lt;/strong&gt; score has actually been a blessing - part of the reason
I never look back on things with regret, only as a learning opportunity. This
helped set me down the path of React for the dashboard, and once I knocked out
a quick Proof-of-Concept I knew that it was &lt;em&gt;the&lt;/em&gt; way forward for the sort of
user experience I need to offer my future users.&lt;/p&gt;
&lt;p&gt;Stripe seems to have a superb React package as well which will hopefully made
the integration (hopefully this month - April) much easier.&lt;/p&gt;
&lt;h3 id="write-a-blog-post-for-danielms-and-mudmap"&gt;Write a blog post for danielms and &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: I wrote &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt;&amp;rsquo;s first blog post but did not write one for
this blog. I have been prioritising &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt; and with so little time between
that, family and my day job, something has to give.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I like writing. But, I also like making things, and the fire under my feet to
deliver &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt; is so strong right now. The only things I will not budge on
are my monthly Retro&amp;rsquo;s and weekly &lt;a href="https://whatgotdone.com"&gt;WhatGotDone&lt;/a&gt;&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;However, I did produce a &lt;a href="https://mudmap.io/blog/introducing-mudmap-a-pfsense-cloud-management-tool/"&gt;blog for Mudmap&lt;/a&gt;. It explains the rough genesis
and intent of &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt; and is a bit of taste of blog deployment for me too.&lt;/p&gt;
&lt;p&gt;In the future, blog post&amp;rsquo;s need to follow a deployment flow. The flow might
look something like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Write and deploy to the site&lt;/li&gt;
&lt;li&gt;Send out a newsletter to subscribers to let them know&lt;/li&gt;
&lt;li&gt;Send a Twitter post from the &lt;a href="https://twitter.com/mudmapio"&gt;Mudmap Twitter&lt;/a&gt; account&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At the moment it is all very manual which will lead to problems in the future.
How I automate this, I am not sure just yet but possibly via a GitHub action
workflow.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;I found &lt;a href="https://www.instagram.com/kneesovertoesguy/"&gt;KneesOverToesGuy&lt;/a&gt; the other day. His story and achievements in
the field of human performance are both contrarian and extraordinary. Worth a
look if you want to see a cool story.&lt;/p&gt;
&lt;p&gt;A show worth watching is the documentary, &lt;a href="https://en.wikipedia.org/wiki/Sherpa_(film)"&gt;Sherpa&lt;/a&gt;. It&amp;rsquo;s currently on Netflix,
at least in Australia (I won&amp;rsquo;t drop a link because it&amp;rsquo;ll be gone or different
for you, no doubt). It highlights the difficulties and conditions the Sherpa
face during Everest expeditions. Personally, the whole everest scene has become
a shambles - pictures of people attempting to summit with the helmets on
backwards and &lt;em&gt;climbers&lt;/em&gt;
only learning what a jumar is at basecamp indicate its now just a tourism
operation.&lt;/p&gt;
&lt;p&gt;Also, if you&amp;rsquo;re an indie hacker or thinking about starting an online business,
check out &lt;a href="https://twitter.com/arvidkahl"&gt;Arvid Khal&lt;/a&gt;&amp;rsquo;s book &lt;a href="https://thebootstrappedfounder.com/zero-to-sold/?ref=danielms.site"&gt;Zero to Sold&lt;/a&gt;. I&amp;rsquo;m listening to the
audible version and have found it to be a fantastic resource.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Research topics before getting straight into coding, for example, I was going
to integrate Auth0 when I already have 90% of session authentication already
handled&lt;/li&gt;
&lt;li&gt;Set Twitter boundaries, and use that &lt;em&gt;scroll&lt;/em&gt; time for development of ideas
instead&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deployed the landing page (without fear of &lt;em&gt;what people think&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Be open about my past work history online - something for years I have hidden
away&lt;/li&gt;
&lt;li&gt;Managed my time between Mudmap and my family&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="side-notes"&gt;Side Notes&lt;/h3&gt;
&lt;p&gt;Last month, I assessed a goal from January - to gain five Twitter followers.
Well this month, rather unexpectedly, I grew that quite significantly.&lt;/p&gt;
&lt;p&gt;&lt;img src="mar21-twitter.png" alt="March twitter analytics"&gt;&lt;/p&gt;
&lt;p&gt;I think my follower count went from 30 to 89 with engagements and impressions
skyrocketing. I attribute this to one thing; engaging with others. In the past
I have treated Twitter like I have treated my general life, that is to say,
avoiding people.&lt;/p&gt;
&lt;p&gt;Mostly, I have avoided talking about myself, or things I have done yet hoping
people would just &lt;em&gt;notice&lt;/em&gt; what I have done. This just isn&amp;rsquo;t how the world
works, not on Twitter, not in life, not anywhere. Two things come to mind here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://www.forbes.com/sites/blakemorgan/2016/12/05/a-squeaky-wheel-gets-the-grease-and-why-it-pays-to-be-an-angry-customer-2/"&gt;The squeaky wheel gets the oil,&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=uW8kBBqc5Zk"&gt;Disagreeable people get further in life.&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do you need to be disagreeable to get by in the world, not really but you
definitely need to stand from something and have opinions. My main point here,
is that silently consuming and never producing, and never engaging is not an
effective strategy.&lt;/p&gt;
&lt;p&gt;So, that is why I am committing more of my mental reserves to opening myself up
to world. This also means breaking a conditioned fear drummed into me from my
years in the special forces; going public about who you are, and what you&amp;rsquo;ve
done or are doing.&lt;/p&gt;
&lt;p&gt;In my past life, nearly ten years, I ran a cover story for meeting people. I
played in a Hockey team for 5 years and none of them knew what I really did. My
parents didn&amp;rsquo;t really know. It&amp;rsquo;s just the culture of that system - working in
the shadows. It has its place and is a necessity but eventually you leave that
world and need to remove those protective walls. For me, it starts with Twitter
and &lt;a href="https://mudmap.io?ref=danielms.site"&gt;Mudmap&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="next-months-goals"&gt;Next months goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deploy Mudmap&amp;rsquo;s application for beta testing&lt;/li&gt;
&lt;li&gt;Write a blog post about pfSense&lt;/li&gt;
&lt;li&gt;Increase Mudmap&amp;rsquo;s marketing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="analytics"&gt;Analytics&lt;/h2&gt;
&lt;div&gt;
 &lt;h3&gt;mudmap.io
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-mudmap.io'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='mudmap.io-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "mudmap.io"
 let start_date = "2021-03-01"
 let end_date = "2021-03-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-mudmap.io')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'mudmap.io-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;

&lt;div&gt;
 &lt;h3&gt;danielms.site
 &lt;/h3&gt;
 &lt;div id="plausible-table"&gt;
 &lt;table id='table-danielms.site'&gt;&lt;/table&gt;
 &lt;div&gt;
 &lt;canvas id='danielms.site-Chart' width="200"
 height="200"&gt;&lt;/canvas&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"&gt;&lt;/script&gt;
 &lt;script type="application/javascript"&gt;
 
 
 
 
 (() =&gt; {
 let domain = "https://fn.dansult.space/function"
 let site_id = "danielms.site"
 let start_date = "2021-03-01"
 let end_date = "2021-03-31"
 const payload = {
 site_id: site_id,
 start_date: start_date,
 end_date: end_date
 }
 fetch(`${domain}/plausible`, {
 method: "POST",
 body: JSON.stringify(payload)
 }).then(data =&gt; {
 return data.json()
 }).then(resp =&gt; {
 let table = document.getElementById('table-danielms.site')
 let aggregate = [resp.aggregate.results]
 let data = Object.keys(aggregate[0])
 generateTableHead(table, data)
 generateTable(table, aggregate)

 let result = resp.timeseries.results
 
 let dates = []
 result.forEach(elem =&gt; dates.push(elem.date))

 let visitors = []
 result.forEach(elem =&gt; visitors.push(elem.visitors))
 
 const ctx = 'danielms.site-Chart'
 new Chart(ctx, {
 type: "line",
 data: {
 labels: dates,
 datasets: [{
 label: "Visitors",
 data: visitors,
 backgroundColor: "rgba(255, 99, 132, 0.2)",
 borderColor: "rgba(255, 99, 132, 1)",
 borderWidth: 1,
 fill: true,
 tension: 0.3,
 }]
 },
 options: {
 responsive: true,
 maintainAspectRatio: false,
 plugins: {
 tooltip: {
 callbacks: {
 label: function (context) {
 let label = "visitors"
 if (label) {
 label += ': ';
 }
 if (context.parsed.y !== null) {
 label += context.parsed.y
 }
 return label;
 }
 }
 }
 }
 }
 })
 }).catch(err =&gt; console.log("failed to retrieve plausible stats", err))
 })()
 function generateTableHead(table, data) {
 function toTitleCase(str) {
 return str.replace(
 /\w\S*/g,
 function(txt) {
 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
 }
 );
 }
 let thead = table.createTHead();
 let row = thead.insertRow();
 for (let key of data) {
 let th = document.createElement("th");
 let text = document.createTextNode(toTitleCase(key.replace('_', ' ')));
 th.appendChild(text);
 row.appendChild(th);
 }
 }
 function generateTable(table, data) {
 function fmtMSS(s){return(s-(s%=60))/60+(9&lt;s?':':':0')+s}
 for (let element of data) {
 let row = table.insertRow();
 for (const key in element) {
 console.log(key)
 let cell = row.insertCell();
 if (key === 'bounce_rate') {
 let val = element[key].value + " %"
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else if (key === 'visit_duration') {
 let val = fmtMSS(element[key].value)
 let text = document.createTextNode(val);
 cell.appendChild(text);
 } else {
 let text = document.createTextNode(element[key].value);
 cell.appendChild(text);
 }
 }
 }
 }
 &lt;/script&gt;
&lt;/div&gt;
</description></item><item><title>Feb 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-feb-2021/</link><pubDate>Sun, 28 Feb 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-feb-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Added a blog to &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt; but am still not ready to launch. 😞&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Got the blog working at &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Backend code works as well but still has some quirks&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;Each month I write up how I went in the pursuit of last month&amp;rsquo;s goals. It is
also somewhere I can expand on the things I&amp;rsquo;ve done. Helpful for historical
review and analysis.&lt;/p&gt;
&lt;h3 id="add-a-blog-to-mudmap"&gt;Add a blog to Mudmap&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: Happy that Wagtail is so easy to introduce and that the blog
lives inside my app&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: A&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After looking around about how to deal with this I decieded on &lt;a href="https://wagtail.io/?ref=danielms.site"&gt;Wagtail&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It met my requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;static rendering&lt;/li&gt;
&lt;li&gt;supports markdown and rich text&lt;/li&gt;
&lt;li&gt;hosting under my control&lt;/li&gt;
&lt;li&gt;routable within the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Initially I considered a few options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ghost&lt;/li&gt;
&lt;li&gt;Hugo&lt;/li&gt;
&lt;li&gt;Pelican&lt;/li&gt;
&lt;li&gt;Plain HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those were a great fit, I really wanted to use Hugo because its a
brilliant product (this site is built on Hugo) but I just couldn&amp;rsquo;t get it to
work &lt;em&gt;how i&lt;/em&gt; wanted it to. In the end I chose &lt;a href="https://wagtail.io/?ref=danielms.site"&gt;Wagtail&lt;/a&gt; because its built for
Django and I&amp;rsquo;ve used it before, so I don&amp;rsquo;t need to use up one of my precious
&lt;a href="http://boringtechnology.club/?ref=danielms.site"&gt;innovation tokens&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the end, its working and let&amp;rsquo;s me write in both markdown and rich text. It&amp;rsquo;s
easy to make changes to the underlying HTML and CSS. It uses Jinja as the
templating language and integrates seemlessly into the Django ecosystem.&lt;/p&gt;
&lt;p&gt;My routing is simple but works exactly like a typical Django url and it
is hosted on my infrastructure. Supports SEO optimization out of the box as
well. Nothing is perfect but this seems pretty great so far. Only complaint is
my shabby formatting and design!&lt;/p&gt;
&lt;h3 id="complete-mudmaps-backend-poc"&gt;Complete Mudmap&amp;rsquo;s backend PoC&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: It works but there&amp;rsquo;s a lot of polish (and bugs) to iron out&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I am happy with the proof-of-concept. By month&amp;rsquo;s end the backend and dashboard
could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Register a new device in the database&lt;/li&gt;
&lt;li&gt;Make the initial connection&lt;/li&gt;
&lt;li&gt;Install the API package&lt;/li&gt;
&lt;li&gt;Install a service account user&lt;/li&gt;
&lt;li&gt;Retrieve system information and display it in the dashboard&lt;/li&gt;
&lt;li&gt;Update a registered device, or delete it&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gain-5-twitter-followers"&gt;Gain 5 Twitter followers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Appraisal&lt;/strong&gt;: I need to actually provide useful content to readers!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rating&lt;/strong&gt;: F&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="jan-twitter-analytics.png" alt="Jan twitter analytics"&gt;&lt;/p&gt;
&lt;p&gt;In hindsight, its kind of a crap metric. I do believe in having an audience,
hence why I am electing to publicise my work (good and bad). But, I don&amp;rsquo;t know
that holding myself to a follower count improvement is a good goal. It should
probably be the by-product of other goals such as &lt;em&gt;write a long form blog post&lt;/em&gt;
or some other value add for any readers.&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;I really enjoy listening to &lt;a href="https://nickjanetakis.com/?ref=danielms.site"&gt;Nick Janetakis&lt;/a&gt;&amp;rsquo; &lt;a href="https://runninginproduction.com/?ref=danielms.site"&gt;Running in Production&lt;/a&gt; podcast.
In particular, I throughly enjoyed his &lt;a href="https://runninginproduction.com/podcast/70-buttondown-lets-you-build-grow-and-launch-your-email-newsletter?ref=danielms.site"&gt;interview&lt;/a&gt; with Justin from &lt;a href="https://buttondown.email?ref=daniems.site"&gt;ButtonDown&lt;/a&gt;.
It&amp;rsquo;s good advertising too because I pretty much immediately signed up for his
service - I will always support the indie creator in favor of the big tech
companies.&lt;/p&gt;
&lt;p&gt;Otherwise, I watched a documentary on YouTube about animals returning to Chernobyl.
I highly recommend it, family friendly too - it&amp;rsquo;s like &lt;a href="https://www.youtube.com/watch?v=JZSqJWeZXaA?ref=danielms.site"&gt;Milo and Otis&lt;/a&gt; for those
that have now grown up!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=XaUNhqnpiOE?ref=danielms.site"&gt;Wildlife Takeover: How Animals Reclaimed Chernobyl&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If I am going to publish things to twitter, make sure they&amp;rsquo;re actually of value to any readers&lt;/li&gt;
&lt;li&gt;Be more focused; I need to prioritize features that will deliver the MVP in the least amount of time&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Used my previous experience with a product (Wagtail) to make a quick integration instead of pursuing the traditional developer move of trying something new&lt;/li&gt;
&lt;li&gt;Learnt a great deal about Django Rest Framework; pretty happy with its performance.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next months goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Finish the copywriting on &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt;&amp;rsquo;s landing page&lt;/li&gt;
&lt;li&gt;Integrate Stripe as the payment processor&lt;/li&gt;
&lt;li&gt;Write a blog post for &lt;a href="https://danielms.site?ref=danielms.site"&gt;danielms&lt;/a&gt; and &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Jan 2021 Retrospective</title><link>https://danielms.site/retrospectives/2021/retrospective-jan-2021/</link><pubDate>Sun, 31 Jan 2021 00:00:00 +0000</pubDate><guid>https://danielms.site/retrospectives/2021/retrospective-jan-2021/</guid><description>&lt;h1 id="summary"&gt;Summary&lt;/h1&gt;
&lt;p&gt;Thought of a potential business to create and its target market.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Purchased a domain; &lt;a href="https://mudmap.io"&gt;mudmap.io&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Created the proof of concept backend code&lt;/li&gt;
&lt;li&gt;Researched the domain for market fit, competition and general sense of
whether its viable&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="goal-performance"&gt;Goal Performance&lt;/h2&gt;
&lt;p&gt;Given this is my first retro, no goals were set last month. So I will talk to
the product and highlights above.&lt;/p&gt;
&lt;h3 id="finding-a-product-to-create"&gt;Finding a product to create&lt;/h3&gt;
&lt;p&gt;Late last year, I discovered a niche in something I have found annoying in the
past; managing multiple &lt;em&gt;pfSense&lt;/em&gt; firewalls. This usually means logging in to
each device one at a time using either SSH or a VPN. Its laborious and I hated
it.&lt;/p&gt;
&lt;p&gt;Then I found a few custom API&amp;rsquo;s that interface with &lt;em&gt;pfSense&lt;/em&gt;. This meant I
could now make remote calls to each device and script my tasks. Anything that
can be scripted can also be turned into a web application, and thus the idea
of &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt; was born.&lt;/p&gt;
&lt;p&gt;Before I started writing any code, I did some research to see if the idea was
viable, or needed. I must admit it did take me too long to find one of the
competitors in this space. Originally, &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt; was going to be called &lt;em&gt;pfmon&lt;/em&gt;
but &lt;a href="http://pfmonitor.com?ref=danielms.site"&gt;pfmonitor&lt;/a&gt; already exists. The naming similarities are just too close for
me to be comfortable with, even though I still really like the name.&lt;/p&gt;
&lt;p&gt;I asked questions on Reddit, searched &lt;em&gt;pfSense&lt;/em&gt;&amp;rsquo;s own forums and even poked
around on Twitter.&lt;/p&gt;
&lt;p&gt;A crude summary from a Reddit &lt;a href="https://www.reddit.com/r/PFSENSE/comments/9u1w4d/is_pfcentre_centralised_cloud_management_still_on/"&gt;post&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I can&amp;rsquo;t upvote this enough. I love pfSense and consider it top of the stack, but there are so many options who offer central management and that makes it tough to recommend it to customers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s pretty much this mentality that makes me feel this product is viable.
Time will tell&lt;/p&gt;
&lt;h2 id="recommendations"&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;If you live in Australia, or want to travel to Australia checkout &lt;a href="https://www.visitnewcastle.com.au/?ref=danielms.site"&gt;Newcastle&lt;/a&gt;.
I used to live there but moved away to take a job and I miss it. I was there
over Australia Day and its just magical. For a West Australian to say that an
east coast beach is brilliant is a big deal; west coast beaches are on a whole
unequivocally better.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;What can I do better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Catalogue my research, particularly product market and feasibility information into notion for future reference&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What have I done well?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Started on the path from worker to creator. I follow &lt;a href="https://twitter.com/dvassallo"&gt;dvassallo&lt;/a&gt;&amp;rsquo;s school of thought here; optimize life over money.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="next-months-goals"&gt;Next months goals&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Add a blog to &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Complete PoC of &lt;a href="https://mudmap.io?ref=danielms.site"&gt;mudmap&lt;/a&gt; backend; API install, account creation, device registration, view properties&lt;/li&gt;
&lt;li&gt;Gain 5 twitter followers&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>2020 In Review ✨</title><link>https://danielms.site/blog/2020-year-in-review/</link><pubDate>Thu, 31 Dec 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/2020-year-in-review/</guid><description>&lt;h1 id="what-a-time-to-be-alive"&gt;What a time to be alive.&lt;/h1&gt;
&lt;p&gt;This year has been as weird as it has been amazing. In Australia, we’ve gone from burning to the ground to Covid to a downright wintery Christmas. Alice Springs recorded its lowest Christmas high temperature of 20 degrees Celsius. The stock market has tanked, only to rebound and in some sectors flourish. China and Australia are starting to engage in economic warfare. Most notably, people were beating each other up over toilet paper.&lt;/p&gt;
&lt;p&gt;Thankfully, none of these are within my locus of control. This is actually a good thing because I can focus on what matters to me.&lt;/p&gt;
&lt;p&gt;So what has happened in my life that is noteworthy.&lt;/p&gt;
&lt;h2 id="personal"&gt;Personal&lt;/h2&gt;
&lt;p&gt;Quite a lot has happened in my life this year, but I am highlighting some of the best parts.&lt;/p&gt;
&lt;h3 id="home-and-away-"&gt;Home and away 🇦🇺&lt;/h3&gt;
&lt;p&gt;I survived my first Canberra winter. Canberra is one of those places where as soon as you mention you live there to a non-Canberran, they remark about how cold it is.&lt;/p&gt;
&lt;p&gt;It is not cold here. Not real cold, not you will die if locked out your house in boardies and t-shirt cold. It would suck but you won’t die.&lt;/p&gt;
&lt;p&gt;And, I loved it - it is beautiful here. Snow-covered mountains, foggy mornings, and sunny clear days (for the most part).&lt;/p&gt;
&lt;p&gt;We traveled to the south coast a few times this year. I have traveled a lot in Australia but seen little of the south coast. It is a magical place with some beautiful beaches, rainforests and lovely weather. In 2021, I hope to get out there more often and explore with the family. Highly recommend &lt;a href="https://www.visitnsw.com/destinations/south-coast/merimbula-and-sapphire-coast/merimbula"&gt;Merimbula&lt;/a&gt; and &lt;a href="https://www.visitnsw.com/destinations/south-coast/batemans-bay-and-eurobodalla/narooma"&gt;Narooma&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One bummer was hitting the snowfields this year. We got down there just in time for the fields to be declared shut for the entire weekend we were there. Still, I was able to experience some actual cold and do a little bit of hiking with my buddy. Out of all the traveling we’ve done this year it was the only true Covid effected situation we experienced.&lt;/p&gt;
&lt;h2 id="work"&gt;Work&lt;/h2&gt;
&lt;p&gt;Work has been busy but rewarding. I have learned so much as developed a lot as a person. Definitely the best working year I’ve had since 2016.
The highlights have been the increase in my skillset:
Django
Angular
Docker
CI/CD
Ansible, and Packer
Microservices
Design and architecture
I feel grateful to have been able to learn all this, and greater still to have kept my job during the pandemic.&lt;/p&gt;
&lt;h2 id="2021"&gt;2021&lt;/h2&gt;
&lt;p&gt;I don&amp;rsquo;t &lt;em&gt;do&lt;/em&gt; resolutions, I do try to make habits and sometimes I fail, or forget so this should keep me accountable.&lt;/p&gt;
&lt;p&gt;Things I need to do to achieve all that I am capable of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I plan on do more in public; building tools, more blogging and I aim to speak at a local tech conference/meetup.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;ve started a SaaS business idea that I think has legs. If it&amp;rsquo;s a flop, fine, but I will use it as a platform to level-up my dev and business skills. Checkout &lt;a href="http://pftrace.com"&gt;pftrace.com&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;My execise commitment wanes and waxes throughout the year, I need to make it more consistent - min 15 minutes of activity per day that isn&amp;rsquo;t walking (I do 20-40 mins of that already)&lt;/li&gt;
&lt;li&gt;Save money; I have calculated that it is the small frequent purchases that have the biggest effect on my bank balance. None of those things make me healthy, wealthy or happy.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;🍻 Here&amp;rsquo;s to internet accountability!&lt;/p&gt;
&lt;h2 id="reflection"&gt;Reflection&lt;/h2&gt;
&lt;p&gt;Another year completed and one of the best I have had in the last few. Whilst everyone is in the midst of Covid despair, I have tried to focus on what I have and what makes my life great. I have my family, a place to call home, and purpose in life - that’s what makes me feel wealthy, and this year has added to that wealth.&lt;/p&gt;</description></item><item><title>From requirements.txt to poetry's pyproject.toml</title><link>https://danielms.site/blog/requirements-txt-to-poetry-pyproject-toml/</link><pubDate>Tue, 27 Oct 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/requirements-txt-to-poetry-pyproject-toml/</guid><description>&lt;h1 id="poetry-and-requirementstxt"&gt;Poetry and Requirements.txt&lt;/h1&gt;
&lt;p&gt;I am now a poetry convert, opting for it in any reasonably large projects. Personally, I have found the ability to pin pacakges and
produce a &lt;code&gt;lock&lt;/code&gt; file invaluable for getting a complete picture of an applications dependencies. It also prevets needless docker build time
spikes if a package is updated.&lt;/p&gt;
&lt;p&gt;Unfortunately, converting &lt;code&gt;requirements.txt&lt;/code&gt; to &lt;code&gt;pyproject.toml&lt;/code&gt; is not yet integrated natively by &lt;code&gt;poetry&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;I needed to change a &lt;code&gt;requirements.txt&lt;/code&gt; file of this structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-r base.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;django-debug-toolbar&lt;span class="o"&gt;==&lt;/span&gt;3.1.1 &lt;span class="c1"&gt;# https://github.com/jazzband/django-debug-toolbar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;django-extensions&lt;span class="o"&gt;==&lt;/span&gt;3.0.9 &lt;span class="c1"&gt;# https://github.com/django-extensions/django-extensions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;django-coverage-plugin&lt;span class="o"&gt;==&lt;/span&gt;1.8.0 &lt;span class="c1"&gt;# https://github.com/nedbat/django_coverage_plugin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pytest-django&lt;span class="o"&gt;==&lt;/span&gt;4.1.0 &lt;span class="c1"&gt;# https://github.com/pytest-dev/pytest-django&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;... truncated ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;to a &lt;code&gt;pyproject.toml&lt;/code&gt; file like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;tool.poetry.dependencies&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;python&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^3.8&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;gunicorn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^20.0.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;psycopg2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^2.8.6&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sentry-sdk &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^0.19.3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;django-storages &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;extras&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;boto3&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^1.10.1&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;django-anymail &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;extras&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sendgrid&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="nv"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^8.1&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;pytz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^2020.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python-slugify &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;^4.0.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;... truncated ...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-solution"&gt;The solution&lt;/h2&gt;
&lt;p&gt;All it took was one line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cat requirements.txt | grep -E '^[^# ]' | cut -d= -f1 | xargs -n 1 poetry add&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It will loop over each line and strip out any &lt;code&gt;#&lt;/code&gt; then call &lt;code&gt;poetry add&lt;/code&gt; followed by the name of each package. Test it by calling &lt;code&gt;cat requirements.txt | grep -E '^[^# ]' | cut -d= -f1&lt;/code&gt; to see what it will output before trying to &lt;code&gt;poetry add&lt;/code&gt; it.&lt;/p&gt;
&lt;p&gt;If it encounters something like &lt;code&gt;-r base.txt&lt;/code&gt; which is not a package, poetry will throw an error to stdout but continue looping over the file. If you&amp;rsquo;re really keen, you could strip lines starting with &lt;code&gt;-r&lt;/code&gt; but why bother?&lt;/p&gt;
&lt;p&gt;The caveats, this will install the latest version of each package because &lt;code&gt;xargs&lt;/code&gt; will only return the package name and not any accompanying &lt;code&gt;==n.n.n&lt;/code&gt;. Again, this could be extended if needed.&lt;/p&gt;
&lt;p&gt;To install development dependancies the line can be amended with a &lt;code&gt;-D&lt;/code&gt; or &lt;code&gt;-dev&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="poetry-for-the-win"&gt;Poetry for the win&lt;/h2&gt;
&lt;p&gt;If you are considering switching to poetry but have many legacy &lt;code&gt;requirements.txt&lt;/code&gt; files for various build states, its now almost trivial
to switch. If you&amp;rsquo;re on docker, poetry or other tools such as &lt;code&gt;pipenv&lt;/code&gt; or &lt;code&gt;pip-tools&lt;/code&gt; are going to potentially save you a lot of lost time from
your pip layer caches being busted when an unpinned subpackage gets updated. Github&amp;rsquo;s dependabot can also interpret &lt;code&gt;pyproject.toml&lt;/code&gt; files meaning you will get automated pull requests for package updates just like you would with a &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Get poetry &lt;a href="https://python-poetry.org/docs/#installation"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>The Slight Edge, A Summary</title><link>https://danielms.site/blog/the-slight-edge-a-summary/</link><pubDate>Sun, 20 Sep 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/the-slight-edge-a-summary/</guid><description>&lt;h1 id="the-slight-edge-in-my-words"&gt;The Slight Edge in my Words&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Do the thing, and you shall have the power.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Slight Edge is a fantastic book that explains the power of continual small improvements that compound overtime. These 1% gains each day go on to make you appear to be an &lt;em&gt;overnight success&lt;/em&gt;. I have experienced this as a self taught software developer.&lt;/p&gt;
&lt;p&gt;To bystanders and friends my journey appears as though I learned everything I know about software development during my university degree (which was in arts, not science). Sadly, this train of thought is because people don&amp;rsquo;t see the slight edge at work - I spent everynight learning and practicing my craft to suddenly become an overnight success.&lt;/p&gt;
&lt;p&gt;I have written this as a homage to one of my top five favourite and most profound books.&lt;/p&gt;
&lt;p&gt;Two takeaways are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have complete control over the direction that the rest of your life takes.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;This is Football&amp;rdquo;&lt;/em&gt; or why we overcomplicate everything.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="beginning"&gt;Beginning&lt;/h2&gt;
&lt;h3 id="two-mindsets"&gt;Two Mindsets&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Entitled:&lt;/strong&gt; &amp;ldquo;What have you done for &lt;em&gt;me&lt;/em&gt; lately&amp;rdquo;, &amp;ldquo;Pay me more and I&amp;rsquo;ll work harder&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Value-driven:&lt;/strong&gt; &amp;ldquo;What can &lt;em&gt;I&lt;/em&gt; do to help you?&amp;rdquo;, &amp;ldquo;I&amp;rsquo;ll work harder, and then I expect you&amp;rsquo;ll pay me more&amp;rdquo;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The Water Hyacinth:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A story of the plant that doubles itself each day - starting at a single cell.&lt;/li&gt;
&lt;li&gt;It takes many days of seemingly no growth before it compounds monumentally to eventually over grow the entire lily pond&lt;/li&gt;
&lt;li&gt;This story demonstrates that the little changes compounded over time aren&amp;rsquo;t noticed, they are so slight that its not noticed for a long period. When it is noticed the onlookers think &amp;ldquo;it happened overnight&amp;rdquo; which it surely did not!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The Millionaires Sons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Another story, this time about two boys each given a choice; receive 1 million dollars 30 days after their fathers death, or get 1 penny but have its value double everyday for 30 days.&lt;/li&gt;
&lt;li&gt;1 penny compounded each day for 30 days is 10 million dollars&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;It&amp;rsquo;s never too late to start&amp;rdquo; &amp;amp; &amp;ldquo;It&amp;rsquo;s always too late to wait&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Parkinson&amp;rsquo;s Law states:
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Work expands to fill the time available for its completion.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;Spare a thought for what junk you put in your body:
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s not a trash can&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Many of us are literally digging our graves with our teeth.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Consistently repeated daily actions + time = unconquerable results&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="happiness"&gt;Happiness&lt;/h2&gt;
&lt;p&gt;Happiness is more important to our success than we realise. These are some &amp;ldquo;happy habits&amp;rdquo;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Each morning, write down three things you are grateful for:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Not the same three; new ones that force you to think of new tings to be grateful for.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Journal for two minutes each day about something positive:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Write down this positive moment in great detail - this causes the brain to relive that happy experience, doubling the positive impact. I also find it gives me time to reflect, and think back on my day often seeing many moments worth writing about.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Mediate daily&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Just stop and do nothing for two minutes. Breathe in and out letting it all wash over you. Relax your body from bottom to top through visualisations such as &amp;ldquo;making your legs concrete and heavy&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Do one random act of kindness each day&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Praise someone, or thank them when it is not expected. Offer time, or assistance when no one expects it.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="5"&gt;
&lt;li&gt;Exercise for 15 minutes daily&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Simple cardio; a brisk walk, or hardcore session, its this stimulation for the body that triggers a strong antidepressant response. 15 minutes is 1% of your day; you have the time!&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="6"&gt;
&lt;li&gt;Read at least &lt;strong&gt;ten&lt;/strong&gt; pages of a good book each day!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This list is only 30 minutes of each day but the key is not to over commit and start doing it all. Begin slowly, developing that slight edge by creating lifelong habits. Once you habitualise one, add another. This is a lifelong journey.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Are You Your Own Cause?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The upper half take full responsibility for their actions. They view all forces that brought them to this point, to be under their control - parents, teachers, friends, family, and so on. These influencers are viewed through the lens of gratitude and appreciation, not blame. They view themselves as the cause of what comes next in their life, no matter whether those influences have been negative or positive.&lt;/li&gt;
&lt;li&gt;Negative people dwell on the past and positive, successful people look to their future. Your past can bring you down, its what people on the failure curve look at - &lt;em&gt;I failed at this, so and so did this to me, I was X at school.&lt;/em&gt; People on the success curve don&amp;rsquo;t shun the past, they use it as a tool to shape their future for the better. Failure curvers use their past as weapon of self-flagellation and to hurt those around them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/the-slight-edge-explained.jpg" alt="alt text" title="slight edge wisdom"&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Honestly look at your your level of happiness; do you take the time to see things in a postive light, rather than a negative? Are you savouring the moment, and expressing your appreciation of others? Are you engaged in activies you &lt;em&gt;want&lt;/em&gt; to do, rather than those you feel you &lt;em&gt;have&lt;/em&gt; to do?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="problem-solving"&gt;Problem Solving&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The size of the problem determines the size of the person.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can quickly determine the limitations of someone&amp;rsquo;s life by the size of the problems getting them down. But you can equally determine the impact a person&amp;rsquo;s life has by the size of the problems they solve.&lt;/p&gt;
&lt;p&gt;If the biggest problems you are solving is &lt;em&gt;should I put the bread, or cans into the shopping bag first?&lt;/em&gt; Then that is the level of your problem solving, and that level will be commensurate with your income.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A problem is just a gap between point A and point B; an open space to be filled.&lt;/li&gt;
&lt;li&gt;When there is a gap - a problem - it will create tension. The tension can be overcome in only two ways:
&lt;ul&gt;
&lt;li&gt;We either move headlong into the tension to slowly overcome the gap and solve the problem (as the top 95% do), or we remove the tension by extracting ourselves from the problem - in a detrimental to our future way.&lt;/li&gt;
&lt;li&gt;Doing nothing, is a form of tension relief as time marches on the problems will erode but they will likely erode negatively. For instance, if you want to make exercise a habit, there will be a tension each time you want to train. By sleeping in, or missing that session today you have removed todays tension - its only one day! But overtime, you have trained less, are more habitually likely to skip sessions and therefore be highly likely to not be that person you wanted to be. The tension is gone but the problem leading you to wanting to start exercising still remains.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="master-the-slight-edge"&gt;Master the Slight Edge&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Be not afraid of moving slowly; be afraid only of standing still.&amp;rdquo; - Chinese proverb&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Momentum preveils:
&lt;ul&gt;
&lt;li&gt;Everything has an optimal rate of growth; never do long standing things have a high rate of growth that can maintain that rate over a long period. We harness gradual and seemingly inconsequential growth through the small and insignificant habits done everyday over years.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Completion:
&lt;ul&gt;
&lt;li&gt;A lack of completion drains us; all the little things we still have to do, all compound to suck our will and life from us. The only way to tame this beast is to actively address as many of these problems to make the list as small as possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Reflect, and do so in public:
&lt;ul&gt;
&lt;li&gt;do not hide your actions; catch yourself doing something right.&lt;/li&gt;
&lt;li&gt;Be open with the public about what you are doing; let the masses hold you to account, and be &amp;ldquo;shocked&amp;rdquo; about your sudden &amp;ldquo;sudden&amp;rdquo; overnight success.&lt;/li&gt;
&lt;li&gt;Holding the past you to account serves as a reminder that you are growing - all we need is the be better than our past self.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="three-yous"&gt;Three &amp;ldquo;you&amp;rsquo;s&amp;rdquo;&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Three words. Exercise and books.&amp;rdquo; - /u/ryans01&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We have three &amp;ldquo;you&amp;rsquo;s&amp;rdquo;; past, present and future. And this ain&amp;rsquo;t from the &lt;em&gt;slight edge&lt;/em&gt; but sums up a lot of it and is ghetto gospel for the youth. Straight from &lt;code&gt;ryans01&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;La deuxieme regle - yeah i learnt french. its a canadian thing. please excuse the lack of accent graves, but lemme get into rule number 2. BE GRATEFUL TO THE 3 YOU&amp;rsquo;S. Uh what? 3 me&amp;rsquo;s? That sounds like mumbo jumbo bullshit. News flash, there are three you&amp;rsquo;s homeslice. There&amp;rsquo;s the past you, the present you, and the future you. If you wanna love someone and have someone love you back, you gotta learn to love yourself, and the 3 you&amp;rsquo;s are the key. Be GRATEFUL to the past you for the positive things you&amp;rsquo;ve done. And do favours for the future you like you would for your best bro. Feeling like shit today? Stop a second, think of a good decision you made yesterday. Salad and tuna instead of Big Mac? THANK YOU YOUNGER ME. Was yesterday a nonzero day because you wrote 200 words (hey, that&amp;rsquo;s all you could muster)? THANK YOU YOUNGER ME. Saved up some coin over time to buy that sweet thing you wanted? THANK YOU. Second part of the 3 me&amp;rsquo;s is you gotta do your future self a favour, just like you would for your best fucking friend (no best friend? you do now. You got 2. It&amp;rsquo;s future and past you). Tired as hell and can&amp;rsquo;t get off reddit/videogames/interwebs? fuck you present self, this one&amp;rsquo;s for future me, i&amp;rsquo;m gonna rock out p90x Ab Ripper X for 17 minutes. I&amp;rsquo;m doing this one for future me. Alarm clock goes off and bed is too comfy? fuck you present self, this one&amp;rsquo;s for my best friend, the future me. I&amp;rsquo;m up and going for a 5 km run (or 25 meter run, it&amp;rsquo;s gotta be non zero). MAKE SURE YOU THANK YOUR OLD SELF for rocking out at the end of every.single.thing. that makes your life better. The cycle of doing something for someone else (future you) and thanking someone for the good in your life (past you) is key to building gratitude and productivity. Do not doubt me. Over time you should spread the gratitude to others who help you on your path.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="cultivating-the-slight-edge"&gt;Cultivating the Slight Edge&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Your Philosophy =&amp;gt; Your Attitude, Actions, Results =&amp;gt; Your Life&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Its habits and philosophy done repeatedly, over time that will make you the person you want to be.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Habits come from daily activities compounded over time. Those activities are choices you make in the moment (get out of bed early, read rather than scroll your socials). Your choices come from habits of thought, which come from your thinking, which are a representation of how you view the world and your place in it - this is your philosophy.&lt;/li&gt;
&lt;li&gt;In other words, its your everyday habits and beliefs that will shape who you are in the future. Simple little actions will make the difference over the long history of your life.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="habit-1-show-up"&gt;Habit 1: Show up&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Most people are struck down with hesitation, the cornerstone of mediocrity.&lt;/li&gt;
&lt;li&gt;The greatest achievers when asked how they did it often say something to the effect of: &lt;em&gt;I just decided to do it&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;do the thing, and you shall have the power&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="habit-2-be-consistent"&gt;Habit 2: Be Consistent&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;80 percent of success is showing up&lt;/em&gt; &lt;em&gt;&lt;strong&gt;every day&lt;/strong&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Do the work, every day and you will have beaten more than 50% of the competition&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="habit-3-have-a-positive-outlook"&gt;Habit 3: Have a Positive Outlook&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;People who see the glass more than half full in every circumstance are:
&lt;ul&gt;
&lt;li&gt;more successful,&lt;/li&gt;
&lt;li&gt;happier,&lt;/li&gt;
&lt;li&gt;more creative,&lt;/li&gt;
&lt;li&gt;live longer,&lt;/li&gt;
&lt;li&gt;make more money,&lt;/li&gt;
&lt;li&gt;have better immune response,&lt;/li&gt;
&lt;li&gt;less heart disease and strokes,&lt;/li&gt;
&lt;li&gt;have better, and longer lasting relationships, and&lt;/li&gt;
&lt;li&gt;are more successful in their careers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Does this mean you have to jubilantly happy &lt;em&gt;all the time&lt;/em&gt;? No, we all have bad days. But its how we manage those. When feeling blue, take time to reflect on your blessings; what you&amp;rsquo;ve achieved, your family and friends, your health and so.&lt;/p&gt;
&lt;p&gt;Will this take you out of your funk immediately? Probably not, and that&amp;rsquo;s when the importance of understanding contrast is powerful.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contrast:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How can I know love, if I&amp;rsquo;ve never felt loss,&lt;/li&gt;
&lt;li&gt;I cannot understand what good feels like, if I have never felt bad,&lt;/li&gt;
&lt;li&gt;Without feeling the funk, I can never know what happiness is,&lt;/li&gt;
&lt;li&gt;Life is ebb and flow - we must experience both spectrum&amp;rsquo;s to appreciate the side we want to live on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="habit-4-be-committed-to-the-long-haul"&gt;Habit 4: Be Committed to the Long Haul&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;It takes a long time to grow and old friend&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Plant, cultivate, harvest&lt;/em&gt; - the comma between &lt;em&gt;cultivate&lt;/em&gt; and &lt;em&gt;harvest&lt;/em&gt; often represents a looooooong period of time.&lt;/li&gt;
&lt;li&gt;10,000 hours doesn&amp;rsquo;t happen in a weekend - it takes years of sustained, and prudent effort to achieve the success we dream of. Commit to it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="habit-5-cultivate-a-burning-desire-backed-by-faith"&gt;Habit 5: Cultivate a Burning Desire Backed by Faith&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;No, not religious faith; faith in your self!&lt;/li&gt;
&lt;li&gt;Desire by itself is a weak binding by itself - it is not the sole marker, that desire must come with a burning desire - that which keeps you up at night and gets you out of bed early in the morning.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;desire backed by faith&lt;/em&gt; is the deep, passionate wanting to get somewhere and knowing - not wanting or wishing, but knowing that you will get there.&lt;/li&gt;
&lt;li&gt;There will be problems encountered along this path but remember: &lt;strong&gt;you can determine the size of a person by the size of the problems they overcome.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;A burning desire is what helps you confront them, rather than turning an running away.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="habit-6-be-willing-to-pay-the-price"&gt;Habit 6: Be Willing to Pay the Price&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;We can only do so much in our life, some times we must make a choice between greatness and pleasure.&lt;/li&gt;
&lt;li&gt;Very few billionaires are playing in the NHL, or whatever sport you follow - they made a sacrifice in one area, to be great in another.&lt;/li&gt;
&lt;li&gt;Whatever price you pay, there&amp;rsquo;s a bigger price to pay for &lt;em&gt;not&lt;/em&gt; doing it than there is for the price of doing it.&lt;/li&gt;
&lt;li&gt;The price of neglect is higher than the price of discipline!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="habit-7-practice-slight-edge-integrity"&gt;Habit 7: Practice Slight Edge Integrity&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Be committed to doing, even when no one is watching.&lt;/li&gt;
&lt;li&gt;Its the choices you make when you&amp;rsquo;re tired, or broke, or in a funk that define whether or not you have the integrity of the slight edge.&lt;/li&gt;
&lt;li&gt;You need to do what you set out to do, every day even when no one could ever find out that you didn&amp;rsquo;t do it because someone will always know the truth; you.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-achieve-a-goal"&gt;How to Achieve a Goal&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Write it down: give it a &lt;em&gt;what&lt;/em&gt; and a &lt;em&gt;when&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Look at it everyday: keep it in your face; soak your subconscious in it - auto-suggestion.&lt;/li&gt;
&lt;li&gt;Start with a plan: make the plan &lt;strong&gt;simple&lt;/strong&gt;. The plan won&amp;rsquo;t get you there, but it will get you started.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Do one small thing to advance yourself every day in key areas of your life - health, happiness, career, finances, impact, personal development&lt;/li&gt;
&lt;li&gt;Review these actions daily, and track them. You can journal them, write them down on a post-it, share them with your friends or mentor. It makes you both consistent and aware of your growth.&lt;/li&gt;
&lt;li&gt;Spend high-quality time with men and women who have achieved goals similar to those which you desire. Model those people and surround yourself with like minded persons, after all you are the average of your five closest friends.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Successful people own their problems, they do not blame. They know the path between success and failure are razor thin, and that by doing nothing we automatically fall down the failure curve.&lt;/li&gt;
&lt;li&gt;It is the &amp;ldquo;insignificant&amp;rdquo; actions done day in day out which set us apart between the 95% and the 5%.&lt;/li&gt;
&lt;li&gt;The top 5% acquire three kinds of knowledge required to succeed:
&lt;ul&gt;
&lt;li&gt;Book and street smarts,&lt;/li&gt;
&lt;li&gt;Learning through study and doing,&lt;/li&gt;
&lt;li&gt;Catalysing and accelerating that knowledge by finding mentor and modelling their behaviour.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;successful people always ask:
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;Who am I spending time with? Are they people who best represent where I want to be headed?&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;They surround themselves with, and develop strong relationships with positive people.&lt;/li&gt;
&lt;li&gt;They read at least ten pages of a good book each day, or 15 minutes of high quality audio.&lt;/li&gt;
&lt;li&gt;They work on their philosophy first because they know it creates their values, actions and results in life.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To understand the slight edge you must know:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The power of simple things,&lt;/li&gt;
&lt;li&gt;The power of daily disciplines,&lt;/li&gt;
&lt;li&gt;How the water hyacinth works, and how to use it,&lt;/li&gt;
&lt;li&gt;When you are being offered the choice of wisdom,&lt;/li&gt;
&lt;li&gt;How to put the slight edge to work for you, rather than against you.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Getting Fedora 32 and Docker to Play Nice</title><link>https://danielms.site/blog/getting-fedora-32-docker-play-nice-a-how-to/</link><pubDate>Sat, 29 Aug 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/getting-fedora-32-docker-play-nice-a-how-to/</guid><description>&lt;h1 id="fedora-32-and-docker-are-incompatible"&gt;Fedora 32 and Docker Are Incompatible&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/fedora-docker.png" alt="Fedora and Docker logos" title="fedora and docker logo's combined"&gt;&lt;/p&gt;
&lt;p&gt;Fedora is a great distribution for development, its very customizable, and supports a great number of packages and applications out of the box. If you&amp;rsquo;re going to use a linux environment thats not Debian based, it ought to be fedora.&lt;/p&gt;
&lt;p&gt;Sadly nothing is ever perfect, meaning Fedora too, has its downsides. Currently, at least to me, is its inability to run Docker out of the box.&lt;/p&gt;
&lt;h2 id="its-actually-dockers-fault"&gt;Its actually dockers fault&lt;/h2&gt;
&lt;p&gt;To be fair its not Fedora&amp;rsquo;s fault but Dockers. Fedora no longer supports &lt;a href="https://wiki.archlinux.org/index.php/cgroups"&gt;control group&lt;/a&gt; version 1 and has moved onto the much better version 2. Unfortunately, Docker only supports version 1 and they aren&amp;rsquo;t backwards compatible.&lt;/p&gt;
&lt;p&gt;This effectively makes using Fedora with docker untenable without modification.&lt;/p&gt;
&lt;h2 id="podman-to-the-rescue"&gt;Podman to the rescue&lt;/h2&gt;
&lt;p&gt;Sure, you can use podman but the world still uses docker and more specifically docker-compose.&lt;/p&gt;
&lt;p&gt;Your work environment might even negate both by just using kubernetes or openshift. But in the open source and hobby project world that is overkill.&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s instead support both.&lt;/p&gt;
&lt;h2 id="why-not-have-both"&gt;Why not have both?&lt;/h2&gt;
&lt;p&gt;While the current version of docker (19.03 as of writing) doesn&amp;rsquo;t support cgroupv2, it does support it on its &lt;a href="https://github.com/moby/moby"&gt;moby&lt;/a&gt; branches.&lt;/p&gt;
&lt;p&gt;Unstable? Well, its either that or no docker for you! It appears docker will merge support and make a release sometime this year - 2020. But until then you need to use the binaries supplied from compiling the docker branches.&lt;/p&gt;
&lt;p&gt;Thankfully, some &lt;a href="https://twitter.com/_AkihiroSuda_?s=20"&gt;legend&lt;/a&gt; has already done it. Go to the &lt;a href="https://github.com/AkihiroSuda/moby-snapshot"&gt;github&lt;/a&gt; page, download the latest release and follow the installation instructions - which is just &lt;code&gt;dnf install&lt;/code&gt;. Once its installed, you will now have docker with cgroupv2 support.&lt;/p&gt;
&lt;p&gt;Your new docker setup will still need to be configured as per the normal &lt;a href="https://docs.docker.com/engine/install/linux-postinstall/"&gt;docker instructions&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Profit!&lt;/p&gt;
&lt;h2 id="docker-and-podman-in-harmony"&gt;Docker and Podman in harmony&lt;/h2&gt;
&lt;p&gt;Now you can safely run both docker and podman without downgrading anything and still get all the benefits of fedora and cgroupv2.&lt;/p&gt;
&lt;p&gt;I personally think Podman is great, but so is docker. For my outside-of-work humble purposes, I only need docker and am thankful there is a workaround. Because, unpopular opinion, macosx sucks and so does ubuntu.&lt;/p&gt;
&lt;p&gt;Docker has a rich ecosystem, massive mind share and wonderful support in both vscode and pycharm. Something most other container platforms lack. Until podman has a better integration I think its uptake in the developer community at large will be slow.&lt;/p&gt;
&lt;h2 id="recap"&gt;Recap&lt;/h2&gt;
&lt;p&gt;Fedora 31+ cant run docker unless you install the precompiled binaries from the Moby branch.&lt;/p&gt;
&lt;p&gt;After installing these, you&amp;rsquo;ll have docker and podman working seamlessly.&lt;/p&gt;
&lt;p&gt;No excuse to not use fedora as a docker acolyte.&lt;/p&gt;
&lt;p&gt;Also if you want more information as to &lt;em&gt;why&lt;/em&gt; we should be switching to cgroup version 2 see &lt;a href="https://www.youtube.com/watch?v=yZpNsDe4Qzg"&gt;here&lt;/a&gt; (this dude is legit, probably &lt;em&gt;the&lt;/em&gt; authority on linux), &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/system_design_guide/understanding-control-groups_setting-limits-for-applications"&gt;here&lt;/a&gt; and &lt;a href="https://0xax.gitbooks.io/linux-insides/content/Cgroups/linux-cgroups-1.html"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>2020 So Far, A Brag Doc</title><link>https://danielms.site/blog/2020-brag-doc/</link><pubDate>Mon, 13 Jul 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/2020-brag-doc/</guid><description>&lt;h1 id="brag-documents"&gt;Brag Documents&lt;/h1&gt;
&lt;p&gt;A reminder to yourself to review what you&amp;rsquo;ve done this year. Catalogue your wins and reinforce &lt;em&gt;good&lt;/em&gt; behaviour.&lt;/p&gt;
&lt;h2 id="2020-in-review"&gt;2020 In Review&lt;/h2&gt;
&lt;p&gt;Here is what I have to brag about in 2020 so far:&lt;/p&gt;
&lt;p&gt;Technologies:&lt;/p&gt;
&lt;p&gt;I have developed, integrated or otherwise taken the following technologies beyond &amp;ldquo;beginner&amp;rdquo;, or enhanced these skills with the following examples.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Angular&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gone from knowing zero Angular to being the lead (sole) developer UI developer in a complex microservices application.&lt;/li&gt;
&lt;li&gt;Lead the UI redesign including initial mock up and wireframes&lt;/li&gt;
&lt;li&gt;Produced the scope of work within sprints&lt;/li&gt;
&lt;li&gt;On boarded another UI developer and helped other experienced member re familiarise themselves with the code base&lt;/li&gt;
&lt;li&gt;Deployed the redesign to production&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Vue.js&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Built two separate web application with vue, both using Typescript&lt;/li&gt;
&lt;li&gt;Both integrated into Python application backend&amp;rsquo;s using &lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ansible&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Identified that creating the infrastructure for deployments is tedious, error prone and not easily repeatable made learning ansible a high priority.&lt;/li&gt;
&lt;li&gt;Successfully created a playbook to create a consistent deploy environment for side projects;
&lt;ul&gt;
&lt;li&gt;update server security and packages&lt;/li&gt;
&lt;li&gt;install load balancer&lt;/li&gt;
&lt;li&gt;ensure small servers have sufficient swap space (to compile javascript)&lt;/li&gt;
&lt;li&gt;git operations on private and public repositories&lt;/li&gt;
&lt;li&gt;encrypt secrets using vault&lt;/li&gt;
&lt;li&gt;development with Vagrant and live instances&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Flask&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Developed a bespoke application which could interface with other custom devices on a large network&lt;/li&gt;
&lt;li&gt;Utilised plain javascript and server side rendered templates to create a UX that users found more familiar than the tool it replaced&lt;/li&gt;
&lt;li&gt;Integrated stripe into Flask&lt;/li&gt;
&lt;li&gt;Used a third party websockets service to facilitate realtime chat between users&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;FastAPI&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deployed two application with the framework, replacing it with Flask in one instance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Docker (and swarm)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Went from having a rudimentary understanding to being able to create detailed docker-compose files and deploy to a swarm&lt;/li&gt;
&lt;li&gt;Now use docker as my development environment&lt;/li&gt;
&lt;li&gt;All side projects have been ported to swarm with load balancing and HTTPS included&lt;/li&gt;
&lt;li&gt;Utilised kubernetes for a short notice project and learnt the basics of the platform&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Skills:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Leadership
&lt;ul&gt;
&lt;li&gt;Lead the UI design and implementation after being assigned to short staffed, over worked close knit team on a complex microservice architecture.&lt;/li&gt;
&lt;li&gt;Integrated well with the team and was able to provide subject matter expertise in Angular within a short period of time&lt;/li&gt;
&lt;li&gt;Became the lead developer of another project requiring the integration of two systems (both in python) requiring facilitation of planning and design sessions between the teams&lt;/li&gt;
&lt;li&gt;Speaking role in presentations selling our new product to senior management, including live demonstration of our MVP&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Design
&lt;ul&gt;
&lt;li&gt;High-level planning sessions for the integration of two platforms&lt;/li&gt;
&lt;li&gt;Mock up and wireframing the UI for another team&amp;rsquo;s platform&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CI/CD
&lt;ul&gt;
&lt;li&gt;From being relatively inexperienced in CI/CD to developing pipelines for projects ranging from established to newly created&lt;/li&gt;
&lt;li&gt;Learnt how to establish shell and docker runners in using Gitlab and AWS&lt;/li&gt;
&lt;li&gt;Created segregated pipelines for separate deploys based on tags&lt;/li&gt;
&lt;li&gt;Successfully integrated Github actions in all side project web applications, including deployment of containers to docker swarm&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Bash | Shell scripting
&lt;ul&gt;
&lt;li&gt;Took over established and complicated deployment shell scripts for production assets&lt;/li&gt;
&lt;li&gt;Learnt a lot about debugging leading to the identification of several fixes resulting in the eradication of pipelines passing when they should have failed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Accomplishments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deployed &lt;a href="https://lic-investing.online"&gt;lic-investing.online&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;An Australian financial web application which visually shows whether certain stocks are trading at a premium or discount.&lt;/li&gt;
&lt;li&gt;Uses FastAPI and Vue.js&lt;/li&gt;
&lt;li&gt;Scrapes web resources using various techniques to gather the data, this includes punting that raw data into Postgres.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Deployed &lt;a href="https://check-redirects.com"&gt;check-redirects.com&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;Provides a service for tracing the redirection chain of a URL including status codes and response headers.&lt;/li&gt;
&lt;li&gt;Includes an API for third parties&lt;/li&gt;
&lt;li&gt;Built using FastAPI and Vue.js&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Replaced a rotting internal legacy tool with a web application:
&lt;ul&gt;
&lt;li&gt;Took an internal tool which was not being actively maintained and replaced it with Flask.
&lt;ul&gt;
&lt;li&gt;The tool needed to be either fixed or replaced to meet a looming deadline and our team was assigned to aid in unblocking the task&lt;/li&gt;
&lt;li&gt;I first attempted to learn the code base and improve upon the design, however there were several critical issues which were cost prohibitive&lt;/li&gt;
&lt;li&gt;After proposing a porting of the tool into a web application the design, implementation and handover of the task was completed in four weeks&lt;/li&gt;
&lt;li&gt;This lead to the successful redeployment of a much needed tool, ultimately saving future development resources by rewriting it with a framework almost all developers in the organisation are familiar with.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After writing all this, its plain to see that I &lt;em&gt;have&lt;/em&gt; achieved more than I realised.&lt;/p&gt;
&lt;p&gt;The value in taking the time to write out what you have achieved over a period of time cannot be under estimated.&lt;/p&gt;
&lt;p&gt;I did this in one sitting, unrefined and (mostly) unedited. This is for you to look back on and polish later &lt;strong&gt;when you need it&lt;/strong&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;what gets measured, gets managed. - Peter Drucker&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>Flask's jsonify in 2020</title><link>https://danielms.site/blog/flask-jsonify-you-dont-need-it/</link><pubDate>Thu, 14 May 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/flask-jsonify-you-dont-need-it/</guid><description>&lt;h1 id="flask-jsonify-do-i-still-need-it"&gt;Flask jsonify, do I still need it?&lt;/h1&gt;
&lt;p&gt;For a long time, returning JSON in flask required using the &lt;code&gt;flask.jsonify&lt;/code&gt; API.
However, since this &lt;a href="https://github.com/pallets/flask/pull/3111"&gt;PR&lt;/a&gt; Flask will by default call &lt;code&gt;jsonify&lt;/code&gt; &lt;a href="https://github.com/pallets/flask/blob/master/src/flask/app.py#L2017"&gt;under the hood&lt;/a&gt; on any
dictionary it receives on a &lt;code&gt;make_response&lt;/code&gt; call.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work&lt;/h2&gt;
&lt;p&gt;When you need to return json in a response, you can simply use a plain python dictionary instead of the &lt;code&gt;jsonify&lt;/code&gt; API.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# plain dictionary&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/plaindict&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;testplain&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;testing&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;roger&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;development&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above code will return the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.0 &lt;span class="m"&gt;200&lt;/span&gt; OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;105&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Type: application/json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Date: Thu, &lt;span class="m"&gt;14&lt;/span&gt; May &lt;span class="m"&gt;2020&lt;/span&gt; 21:20:06 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Server: Werkzeug/1.0.1 Python/3.8.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;age&amp;#34;&lt;/span&gt;: 30,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;development&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;testing&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 1,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;]&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;roger&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the same code but with &lt;code&gt;jsonify&lt;/code&gt; (for comparison) will return the exact same thing.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/jsonify&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;testjsonify&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;testing&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;roger&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;development&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;lst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-does-it-work-1"&gt;How does it work&lt;/h2&gt;
&lt;p&gt;As stated above, Flask now does an &lt;code&gt;isinstance&lt;/code&gt; check during the &lt;code&gt;make_response&lt;/code&gt; API call. In the conditional, Flask
checks if the body is of type &lt;code&gt;dict&lt;/code&gt; and if so calls &lt;code&gt;jsonify&lt;/code&gt;. To see this in action refer to the &lt;a href="https://github.com/pallets/flask/blob/master/src/flask/app.py#L2017"&gt;previously&lt;/a&gt; mentioned line in the core Flask &lt;code&gt;app.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, while we no longer &lt;em&gt;need&lt;/em&gt; to explicitly call &lt;code&gt;jsonify&lt;/code&gt; it is still very much being used by the application itself.&lt;/p&gt;
&lt;h2 id="whats-the-catch"&gt;Whats the catch?&lt;/h2&gt;
&lt;p&gt;There is one small gotcha, as seen from &lt;a href="https://pgjones.dev"&gt;pgjones&lt;/a&gt; pull request.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This doesn&amp;rsquo;t support returning anything other than an associate array at the top level in the JSON response. I&amp;rsquo;m ok with this as in practice APIs are only extensible if the top level is an associate array.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;An example of when you will still need to user &lt;code&gt;jsonify&lt;/code&gt; is when the response is not of type &lt;code&gt;dict&lt;/code&gt;. As an example, doing a list comprehension and returning that directly will be required to be wrapped in &lt;code&gt;jsonify&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/users&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;users_api&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_all_users&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="fin"&gt;Fin&lt;/h2&gt;
&lt;p&gt;While its not a &lt;em&gt;complete&lt;/em&gt; replacement for &lt;code&gt;flask.jsonify&lt;/code&gt;, it will probably reduce its explicit usage by a huge proportion.
Personally, I removed &lt;code&gt;jsonify&lt;/code&gt; from most of my applications and now just drop in plain dictionaries.&lt;/p&gt;</description></item><item><title>Starting Your Side Hustle</title><link>https://danielms.site/blog/side-hustle-notes-getting-started/</link><pubDate>Sun, 12 Apr 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/side-hustle-notes-getting-started/</guid><description>&lt;h1 id="starting-your-side-hustle"&gt;Starting Your Side Hustle&lt;/h1&gt;
&lt;p&gt;This is a collection of notes from one of the world&amp;rsquo;s expert Side Hustlers, &lt;a href="https://chrisguillebeau.com/"&gt;Chris Guillebeau&lt;/a&gt;. If you have not read his book The $100 Startup, you really should. It also includes important takeaways from other bloggers and authors. Wherever applicable references have been added.&lt;/p&gt;
&lt;p&gt;Its also pretty important to define a &lt;em&gt;side hustle&lt;/em&gt;. We are talking about small enterprises which take little to no time for setup, maintenance and daily running. We aren&amp;rsquo;t looking to start the next &lt;em&gt;Facebook&lt;/em&gt; - though it could evolve into that but its not the intent from day one.&lt;/p&gt;
&lt;h2 id="notes-from-a-hustler"&gt;Notes From a Hustler&lt;/h2&gt;
&lt;h3 id="side-hustle-questions-to-ask-yourself"&gt;Side Hustle Questions to Ask Yourself&lt;/h3&gt;
&lt;p&gt;There is three (3) things you need to ask yourself about any chosen side hustle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does this solve a &lt;strong&gt;clear&lt;/strong&gt; and urgent problem, or, does this &lt;em&gt;thing&lt;/em&gt; make someone&amp;rsquo;s life easier in a &lt;strong&gt;specific&lt;/strong&gt; way?&lt;/li&gt;
&lt;li&gt;Will the idea generate income over a long time scale?&lt;/li&gt;
&lt;li&gt;Is this something you &lt;em&gt;like&lt;/em&gt; to do, or are you just doing it for the money?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you can answer those questions in the positive sense then you have a potential option to explore.&lt;/p&gt;
&lt;h3 id="are-there-bad-side-hustle-ideas"&gt;Are There Bad Side Hustle Ideas?&lt;/h3&gt;
&lt;p&gt;Yep, and here is a list of things to look out for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Part-time jobs dressed up as side hustles (freelancing),&lt;/li&gt;
&lt;li&gt;Anything where you give up control of the income potential,&lt;/li&gt;
&lt;li&gt;Something that exists solely on one platform or network,&lt;/li&gt;
&lt;li&gt;If its not replicable (each iteration is a one-time deal),&lt;/li&gt;
&lt;li&gt;Anything where you are simply trading time for money.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rideshare&lt;/li&gt;
&lt;li&gt;Gig economy options&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="the-three-categories"&gt;The Three categories&lt;/h3&gt;
&lt;p&gt;Every hustle can be put into one of three baskets:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Products,&lt;/li&gt;
&lt;li&gt;Services, and&lt;/li&gt;
&lt;li&gt;Everything else.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Products&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Something you &lt;em&gt;give&lt;/em&gt; people,&lt;/li&gt;
&lt;li&gt;Build once and sell once,&lt;/li&gt;
&lt;li&gt;One, or multiple items for sale,&lt;/li&gt;
&lt;li&gt;Books, gifts and other non-recurring items sold to a consumer&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Services&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Something you &lt;em&gt;do&lt;/em&gt; for people,&lt;/li&gt;
&lt;li&gt;Often ongoing for either short or long periods,&lt;/li&gt;
&lt;li&gt;Customer facing,&lt;/li&gt;
&lt;li&gt;Courses, coaching and management are good examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Everything else&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anything where you are not selling an item or dealing with customers,&lt;/li&gt;
&lt;li&gt;Affiliate commissions, arbitraging items between stores or in currency are examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of the three types will require slightly difference avenues of management and marketing. Knowing where your idea sits is important.&lt;/p&gt;
&lt;h3 id="idea-curation-and-selection"&gt;Idea Curation and Selection&lt;/h3&gt;
&lt;p&gt;This is the hardest part for someone like me, and probably most people. How do you first find a good idea, and if you have several, select the right one.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.kalzumeus.com/2010/03/20/running-a-software-business-on-5-hours-a-week/"&gt;Patrick McKenzie&lt;/a&gt; said it best.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Some people profess difficulty at finding applications to write. I have never understood this: talk to people. People have problems — lots of problems, more than you could enumerate in a hundred lifetimes. Talk to a carpenter, ask him what about carpentry sucks. Talk to the receptionist at your dentist’s office — ask her what about her job sucks. Talk to a teacher — ask her what she spends time that she thinks adds the least value to her day. (I’ll bet you the answer is “Prep!” or “Paperwork!”)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&amp;rsquo;s what it really comes down to, find a problem someone is having that you can solve. It could even be something you find annoying. Whatever it is, your next step is to assess it again the following criteria:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Feasibility: can I even do this thing, do I have the skills?&lt;/li&gt;
&lt;li&gt;Profitability: will people buy this, or will my outlay be recovered by purchases. Include both &lt;em&gt;time&lt;/em&gt; and &lt;em&gt;financial&lt;/em&gt; outlay.&lt;/li&gt;
&lt;li&gt;Urgency: is this a good idea in today&amp;rsquo;s world. Do people care about solving this problem today - many things have launched way before the masses respected the problem being solved.&lt;/li&gt;
&lt;li&gt;Efficiency: can I achieve this quickly? What might hold me back?&lt;/li&gt;
&lt;li&gt;Motivation: simply, do I &lt;em&gt;really&lt;/em&gt; care about this? If not, you won&amp;rsquo;t have the gumption to stick out the tough moments.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Think hard on each point, write it out. Score it honestly and even discus it with close family or friends. They might see problems from a view which you are blinded to right now.&lt;/p&gt;
&lt;h3 id="finding-your-customer"&gt;Finding Your Customer&lt;/h3&gt;
&lt;p&gt;This was a big takeaway; identify your perfect client in exacting detail and be ruthless in designing the product around them.&lt;/p&gt;
&lt;p&gt;Ask yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How old are they?&lt;/li&gt;
&lt;li&gt;Where do they live?&lt;/li&gt;
&lt;li&gt;Gender,&lt;/li&gt;
&lt;li&gt;Race,&lt;/li&gt;
&lt;li&gt;Country or region,&lt;/li&gt;
&lt;li&gt;Unique characteristics, and&lt;/li&gt;
&lt;li&gt;What challenges do they have.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you hone in on your target, design your product or services around them. Speak to them. Act as if the product/service is tailored (and it is) exactly for them.&lt;/p&gt;
&lt;p&gt;You need to now speak to them, and if you have bracketed this person or persons correctly they should be hearing your loud and clear.&lt;/p&gt;
&lt;h3 id="idea-selected-now-what"&gt;Idea Selected, Now What?&lt;/h3&gt;
&lt;p&gt;Time for &lt;strong&gt;action.&lt;/strong&gt; What do I now need to get the ball rolling? Here is simple list for getting something out there.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Website with Sales, Product/Service, Contact and About pages,&lt;/li&gt;
&lt;li&gt;Email list,&lt;/li&gt;
&lt;li&gt;Social media,&lt;/li&gt;
&lt;li&gt;Initial inventory (id applicable)&lt;/li&gt;
&lt;li&gt;Payment processor&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Focus only on the bare essentials. Don&amp;rsquo;t build out excess &amp;ldquo;functionality&amp;rdquo; based on what you &lt;em&gt;think&lt;/em&gt; the customer wants. They will tell you.&lt;/p&gt;
&lt;p&gt;A realy important part is designing your payment workflow.&lt;/p&gt;
&lt;p&gt;How does your custome make a purchase - think this through carefully. Any hiccups on this road may lead to a lost sale, we are most likely to reject a product just before buying it.&lt;/p&gt;
&lt;h3 id="successful-offers"&gt;Successful offers&lt;/h3&gt;
&lt;p&gt;At its core, each category is making an offering to its potential customer.
Every successful offer has three things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Promise - why should you care about my widget,&lt;/li&gt;
&lt;li&gt;Pitch - why you need it right now,&lt;/li&gt;
&lt;li&gt;Price - and you can get it for &lt;em&gt;n&lt;/em&gt; Now&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;How to craft the offer. Remember you are writing to a person, not a group or large swath of people. Each sale is form &lt;strong&gt;one person&lt;/strong&gt; at a time, so speak to that person! You action words to describe the product, why its needed and why its needed right now.
Always use numbers to explain statistics or prices.
And, use stories that elicit happy emotions.&lt;/p&gt;
&lt;h3 id="pricing-your-yourself"&gt;Pricing your Yourself&lt;/h3&gt;
&lt;p&gt;Simple, know what you are worth.&lt;/p&gt;
&lt;p&gt;If its a service, work out what your time is worth and add a margin on that.
And if its a product, calculate your expense for each item, time to create and add a profit margin.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t be coy, a profit margin of less than 20% is probably going to be a profit of 0% in the long run.&lt;/p&gt;
&lt;h3 id="testing-the-hustle"&gt;Testing the Hustle&lt;/h3&gt;
&lt;p&gt;Rehearse, rehearse, rehearse. Double check your work in every aspect: does my website work on desktop and mobile, does my payment workflow work (have I manually tested in several times), does my landing page have spelling or grammatical errors?&lt;/p&gt;
&lt;p&gt;No doubt after so long reading the same script over and over, you aren&amp;rsquo;t seeing simple mistakes. This is when its paramount we get friends or family to proof our work.&lt;/p&gt;
&lt;p&gt;Does your offering have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A clear offer,&lt;/li&gt;
&lt;li&gt;Pitch, Promise and Price,&lt;/li&gt;
&lt;li&gt;Can it be explained in one sentence (and understood),&lt;/li&gt;
&lt;li&gt;How are your customers going to find you (don&amp;rsquo;t rely on one (1) method),&lt;/li&gt;
&lt;li&gt;A simple payment workflow&lt;/li&gt;
&lt;li&gt;And, do you as the creator have goal for this project for &lt;em&gt;at least&lt;/em&gt; first 30 days.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="side-hustler"&gt;Side Hustler&lt;/h1&gt;
&lt;p&gt;Creating a side hustle doesn&amp;rsquo;t have to be massive, or change the world but it needs to have several characteristics if its going to survive. Contrast each idea against this and see where you fall short, and if you can fix it.&lt;/p&gt;</description></item><item><title>Web Scraping Javascript with Python</title><link>https://danielms.site/blog/web-scraping-javascript-with-python-xhr/</link><pubDate>Fri, 13 Mar 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/web-scraping-javascript-with-python-xhr/</guid><description>&lt;h1 id="scraping-dynamic-pages-with-python"&gt;Scraping Dynamic Pages with Python&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/scraping.png" alt="" title="web scraping diagram"&gt;&lt;/p&gt;
&lt;h2 id="web-scraping"&gt;Web scraping&lt;/h2&gt;
&lt;p&gt;Python is a great tool for web scraping tasks, it is efficient, easy to read and fast. Whenever looking to grab data from a site, the canonical packages are &lt;a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/"&gt;BeautifulSoup&lt;/a&gt; and &lt;a href="https://github.com/psf/requests/"&gt;Requests&lt;/a&gt;. Unfortunately, when our target site is dynamically rendered, BeautifulSoup can&amp;rsquo;t &amp;ldquo;see&amp;rdquo; those parts leading to a lot of head scratching. The often touted answer to this is &lt;a href="https://selenium-python.readthedocs.io/"&gt;Selenium&lt;/a&gt; which spins up a browser thereby rendering the JavaScript making it possible to scrape data from it.&lt;/p&gt;
&lt;p&gt;While selenium does work it kinda sucks, can be a pain to setup and introduces more complexity. On big projects where scrolling, pagination or link traversal is required you may be best served by using Selenium. Though I would recommend &lt;a href="https://scrapy.org/"&gt;Scrapy&lt;/a&gt;, which is excellent and is built for scraping - Selenium is not.&lt;/p&gt;
&lt;p&gt;In some cases, there is another way.&lt;/p&gt;
&lt;h2 id="an-alternative"&gt;An alternative&lt;/h2&gt;
&lt;p&gt;Many modern web applications render data from third party API&amp;rsquo;s or other backend services. When a site uses JavaScript to do this, then we can get a lot of data from it by using only Requests, &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt; and &lt;a href="https://curl.haxx.se/"&gt;cURL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The basic steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;find the API endpoint using the browser&amp;rsquo;s development tools,&lt;/li&gt;
&lt;li&gt;copy the URL as a curl command,&lt;/li&gt;
&lt;li&gt;import that command into postman, checking it works,&lt;/li&gt;
&lt;li&gt;get postman to auto generate the request into Python code, and&lt;/li&gt;
&lt;li&gt;plug that into your script, and profit!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This will provide you repeatable python code which will always return a response with the data you require.&lt;/p&gt;
&lt;h3 id="step-1-find-the-url"&gt;Step 1: Find the URL&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s use an example &lt;a href="https://lic-investing.online"&gt;website&lt;/a&gt; which uses an &lt;a href="https://en.wikipedia.org/wiki/XMLHttpRequest"&gt;XMLHttpRequest&lt;/a&gt; pull data from another server and populate a table with stock data.&lt;/p&gt;
&lt;p&gt;After navigating to the site, open the development tools and click on the network pane. You may need to refresh the page to populate the network pane with its requests.&lt;/p&gt;
&lt;p&gt;Searching through the requests, we find what we are looking for, in this case the requests are named after the stock symbols. There is no standard but you can filter by XHR in the network pane to make finding juicy targets easier.&lt;/p&gt;
&lt;p&gt;For the uninitiated, when clicking on the XHR request, select the response pane and look at the JSON data it has returned.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/scrape-net-tab.png" alt="" title="dev tools network tab with json xhr response data"&gt;&lt;/p&gt;
&lt;h3 id="step-2-copy-as-curl"&gt;Step 2: Copy as cURL&lt;/h3&gt;
&lt;p&gt;After finding the appropriate XHR endpoint, we now need to replicate the GET request.&lt;/p&gt;
&lt;p&gt;In the network tab, as seen above, right click and hover over &lt;em&gt;Copy&lt;/em&gt; which will show you an option to &lt;em&gt;Copy as cURL&lt;/em&gt;. Selecting this will output something like the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl &lt;span class="s1"&gt;&amp;#39;https://lic-investing.online/api/stocks/bki&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:74.0) Gecko/20100101 Firefox/74.0&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;Accept: application/json, text/plain, */*&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;Accept-Language: en-US,en;q=0.5&amp;#39;&lt;/span&gt; --compressed &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;DNT: 1&amp;#39;&lt;/span&gt; -H &lt;span class="s1"&gt;&amp;#39;Connection: keep-alive&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;Referer: https://lic-investing.online/&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;Pragma: no-cache&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;Cache-Control: no-cache&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-H &lt;span class="s1"&gt;&amp;#39;TE: Trailers&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# formatted for easier reading&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can test that this works by running the command in your console on MacOS or Linux. On Windows? 🤷 soz, I don&amp;rsquo;t know.&lt;/p&gt;
&lt;h3 id="step-3-import-into-postman"&gt;Step 3: Import into Postman&lt;/h3&gt;
&lt;p&gt;After copying as cURL above, open up &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In the top left will be a orange box titled &lt;em&gt;New&lt;/em&gt;, and to the right of it will be &lt;em&gt;Import&lt;/em&gt;. Select &lt;em&gt;Import&lt;/em&gt; and then click on the &lt;em&gt;Paste Raw Text&lt;/em&gt; tab. Paste in the cURL command and hit &lt;em&gt;Import&lt;/em&gt;. This process should resemble the image below.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/scrape-postman.png" alt="" title="Postman import raw text example"&gt;&lt;/p&gt;
&lt;p&gt;After importing the command, Postman will populate all the GET parameters needed to make a request in its main window. Clicking on the blue &lt;em&gt;Send&lt;/em&gt; button will fire the request and once Postman receives the response you will see it in the main body of the screen.&lt;/p&gt;
&lt;p&gt;Right now we have returned a JSON object from our target without needing to be on their website proving we can simulate a request from their frontend to the backend. This is how we will scrape the site in a repeatable and reliable way.&lt;/p&gt;
&lt;h3 id="step-4-get-the-python-code"&gt;Step 4: Get the python code&lt;/h3&gt;
&lt;p&gt;So far, we&amp;rsquo;ve created a request to retrieve data from the server. But now we need to turn this request into something we can replicate using Python.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s use Postman to automatically generate some Python code for us.&lt;/p&gt;
&lt;p&gt;To get this auto generated code simply select the &lt;em&gt;Code&lt;/em&gt; text block which is directly below the &lt;em&gt;Save&lt;/em&gt; drop-down on the right hand side of Postman&amp;rsquo;s main screen.&lt;/p&gt;
&lt;p&gt;Scroll down to find Python code nicely generated for us. In this example I have chosen Requests, though it also offers &lt;code&gt;http.client&lt;/code&gt; as well.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/scrape-python.png" alt="" title="Postman code auto generator example"&gt;&lt;/p&gt;
&lt;h3 id="step-5-win"&gt;Step 5: Win&lt;/h3&gt;
&lt;p&gt;Now we have fully functional Python code which can make a request to the endpoint we are targeting and it will return a response as if we were the website. What we do now is pure business logic but the main thing is we did not need to reach for Selenium, and that is worth its weight in code.&lt;/p&gt;
&lt;h2 id="closing-points"&gt;Closing points&lt;/h2&gt;
&lt;p&gt;Be wary of hitting the endpoint repeatedly, you may get black listed by the site. To prevent this, use a library such as &lt;a href="https://github.com/reclosedev/requests-cache"&gt;Requests-cache&lt;/a&gt;. Give them a star if you can!&lt;/p&gt;
&lt;p&gt;This is also helpful when playing with API&amp;rsquo;s which have request or rate limits imposed.&lt;/p&gt;
&lt;p&gt;Not related to XHR but always look over the source of a webpage in a browser, and then contrast that with BeautifulSoup&amp;rsquo;s response data. I have had success on React sites by grabbing their Props data that I couldn&amp;rsquo;t even see rendered in the browser. To get at custom, hard to scrape data like this, Regex is your friend.&lt;/p&gt;
&lt;p&gt;If its on the internet, its possible to scrape (within legal limits of course).&lt;/p&gt;</description></item><item><title>Wagtail embeded YouTube videos</title><link>https://danielms.site/blog/wagtail-embedurl-youtube-tags/</link><pubDate>Sat, 25 Jan 2020 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/wagtail-embedurl-youtube-tags/</guid><description>&lt;h2 id="wagtail-embed-video"&gt;Wagtail Embed Video&lt;/h2&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/wagtail-logo.png" alt="" title="wagtail icon"&gt;&lt;/p&gt;
&lt;p&gt;Wagtail is a brilliant content management service built atop of Django. It comes with all of Django&amp;rsquo;s functionality and Django Rest Framework built in for headless work making life a lot easier.&lt;/p&gt;
&lt;p&gt;In all, I really love it, especially its &lt;a href="https://docs.djangoproject.com/en/dev/howto/custom-template-tags/"&gt;StreamFields&lt;/a&gt;. But I did stumble on one component, getting embedded videos to work correctly.&lt;/p&gt;
&lt;h3 id="embedding-videos"&gt;Embedding videos&lt;/h3&gt;
&lt;p&gt;This is harder than I feel it should be and poorly explained by the Wagtail documentation. Perhaps too harsh, in a sense - the documentation is actually really good. Except for in the circumstance whereby you want to render an embedded video inside your own HTML and CSS.&lt;/p&gt;
&lt;p&gt;Before, covering that, again I should mention that wagtail&amp;rsquo;s documentation is excellent and so too is their implementation of this Framework. For instance, you &lt;em&gt;can&lt;/em&gt; render an embed video &amp;lsquo;block&amp;rsquo; by calling a template tag and creating the component in the model.&lt;/p&gt;
&lt;p&gt;Except, it will &lt;strong&gt;always&lt;/strong&gt; render this block inside a custom CSS div which apparently cannot be overridden. I&amp;rsquo;d wager 8 out of 10 times this might mess with your sites style.&lt;/p&gt;
&lt;h3 id="custom-embedding-of-video-content"&gt;Custom embedding of video content&lt;/h3&gt;
&lt;p&gt;There is a solution to this issue; just call the &lt;em&gt;video.url&lt;/em&gt; within your own implementation of an iframe element. Except, if its YouTube, and your are using a URL such as &lt;a href="https://www.youtube.com/watch?v=xUWd3o6z2bk"&gt;https://www.youtube.com/watch?v=xUWd3o6z2bk&lt;/a&gt;, it will likely not run. Why? It&amp;rsquo;s the &lt;em&gt;watch&lt;/em&gt; url endpoint. YouTube, wants embedded videos to use &lt;a href="https://www.youtube.com/embed?v=xUWd3o6z2bk"&gt;https://www.youtube.com/embed?v=xUWd3o6z2bk&lt;/a&gt; instead. &lt;strong&gt;Note&lt;/strong&gt; the use of &lt;em&gt;embed&lt;/em&gt; in place of &lt;em&gt;watch&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And, wagtail will not accept YouTube urls containing &lt;em&gt;embed&lt;/em&gt; causing a template error. So what&amp;rsquo;s a person to do; custom template tags to override this blocker!&lt;/p&gt;
&lt;h3 id="code-or-gtfo"&gt;Code or GTFO&lt;/h3&gt;
&lt;p&gt;Lets create a VideoBlock that will embed a video component into our template.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# blocks.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VideoBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blocks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StructBlock&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Only used for Video Card modals.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EmbedBlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;-- the part we need&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;streams/video_card_block.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;icon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;media&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Embed Video&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will now give us a StreamField which we can then use to enter the url we wish to embed into the template. In this example the url we are entering into our StreamField video_card_block is &lt;a href="https://www.youtube.com/watch?v=xUWd3o6z2bk"&gt;https://www.youtube.com/watch?v=xUWd3o6z2bk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Next, in the video block template we&amp;rsquo;ll use Bootstrap&amp;rsquo;s embedded video component and inject the url into the &lt;em&gt;src&lt;/em&gt; attribute.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- video_card_block.html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% load wagtailcore_tags wagtailembeds_tags %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% block content %}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed-responsive embed-responsive-16by9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed-responsive-item&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ self.video.url }}?rel=0&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{% endblock %}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unfortunately, this will render it as seen below, and be blocked by YouTube. In fact when it is blocked, you will need to consult dev tools to see that wagtail did indeed render the HTML.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed-responsive embed-responsive-16by9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed-responsive-item&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://www.youtube.com/watch/zpOULjyy-n8?rel=0&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To fix this we need to create a &lt;a href="https://docs.djangoproject.com/en/dev/howto/custom-template-tags/"&gt;custom template tag&lt;/a&gt; which will intercept the &lt;em&gt;watch&lt;/em&gt; url and inject the &lt;em&gt;embed&lt;/em&gt; url into the template. This will make wagtail&amp;rsquo;s embed &lt;a href="https://github.com/wagtail/wagtail/blob/master/wagtail/embeds/oembed_providers.py"&gt;classifiers&lt;/a&gt; happy and us too.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@register.filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;embedurl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_embed_url_with_parameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;youtube.com&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;youtu.be&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;regex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;(?:https:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?(.+)&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Get video id from URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;embed_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://www.youtube.com/embed/\1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Append video id to desired URL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embed_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;embed_url_with_parameters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embed_url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;?rel=0&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Add additional parameters&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;embed_url_with_parameters&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will now render the &lt;em&gt;embed&lt;/em&gt; url as expected and upon inspection with dev tools should look like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed-responsive embed-responsive-16by9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;embed-responsive-item&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;https://www.youtube.com/embed/zpOULjyy-n8?rel=0&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;allowfullscreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="fixed"&gt;fixed&lt;/h3&gt;
&lt;p&gt;I Searched the internet for hours and only after piecing it together over time did I eventually unlock this riddle! This is still an &lt;a href="https://github.com/wagtail/wagtail/issues/4127"&gt;open&lt;/a&gt; ticket on the tail issue tracker!&lt;/p&gt;</description></item><item><title>Ecosia</title><link>https://danielms.site/blog/ecosia-tree-saving-browser/</link><pubDate>Sat, 28 Dec 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/ecosia-tree-saving-browser/</guid><description>&lt;h1 id="ecosia"&gt;Ecosia&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/ecosia_logo.png" alt="" title="ecosia logo"&gt;&lt;/p&gt;
&lt;h2 id="building-awareness"&gt;Building awareness&lt;/h2&gt;
&lt;p&gt;The search engine that plants trees.&lt;/p&gt;
&lt;p&gt;As we welcome 2020, lets acknowledge that doing something to help make our planet more sustainable is worth our time and effort.&lt;/p&gt;
&lt;p&gt;Not everyone need agree on this topic, but those that do and have yet to hear about Ecosia let me give you some background.&lt;/p&gt;
&lt;h2 id="the-tree-planting-search-engine"&gt;The tree planting search engine&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;79 million trees have been planted to date and every 0.8 seconds a new tree is planted - December 2019&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ecosia was founded in 2009 by Christian Kroll who after graduating university travelled searching for a business idea with positive social impact. It was founded as a means to fund forest regeneration through ad revenue derived from search. It is also a &lt;a href="%5Bhttps://en.wikipedia.org/wiki/B_Corporation_(certification%5D(https://en.wikipedia.org/wiki/B_Corporation_%28certification%29?wprov=sfla1))"&gt;B Corporation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The business is very transparent with its finances publishing its &lt;a href="%5Bhttps://blog.ecosia.org/ecosia-financial-reports-tree-planting-receipts/%5D(https://blog.ecosia.org/ecosia-financial-reports-tree-planting-receipts/)"&gt;balance sheet&lt;/a&gt; each month. It doesnt just say it uses 80% of its profits to plant trees, it proves it. Further, the owner has two publicly stated core beliefs regarding the future and funding of the mission; to &lt;strong&gt;never&lt;/strong&gt; sell and &lt;strong&gt;never&lt;/strong&gt; take profits out of the company. Ecosia is a steward-owned company making a breach of either commitment illegal.&lt;/p&gt;
&lt;p&gt;In 2019, the company became completely carbon neutral through the construction of its own solar plant. It offsets all of its servers carbon footprint through the use solar energy.&lt;/p&gt;
&lt;h2 id="surely-the-results-are-rubbish"&gt;Surely the results are rubbish&lt;/h2&gt;
&lt;p&gt;The search provider backing Ecosia is Bing, though they do tweak the algorithm somewhat. Anecdotally, I have not found it to be much different than either DuckDuckGo or Google. In fact it is my mobile browser and search engine of choice.&lt;/p&gt;
&lt;p&gt;Bing is also &lt;a href="%5Bhttps://blogs.microsoft.com/green/%5D(https://blogs.microsoft.com/green/)"&gt;carbon neutral&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="features"&gt;Features&lt;/h2&gt;
&lt;p&gt;All the usual search engine features are available such as images, maps, videos, news with filtering options. They also offer direct searches into YouTube and Wikipedia to name a few.&lt;/p&gt;
&lt;p&gt;If you are used to DuckDuckGo&amp;rsquo;s &amp;lsquo;shebangs&amp;rsquo;, its pleasing to know that Ecosia offers this feature also - not as many I should add.&lt;/p&gt;
&lt;p&gt;The main ones to remember are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#w&lt;/code&gt; wikipedia&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#yt&lt;/code&gt; YouTube&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#g&lt;/code&gt; Google&lt;/li&gt;
&lt;li&gt;&lt;code&gt;#a&lt;/code&gt; Amazon&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unique to Ecosia is its &amp;ldquo;green leaf&amp;rdquo; icon and Ecosia travel.&lt;/p&gt;
&lt;p&gt;Ecosia travel lets you search and make accommodate bookings directly from the browser. For every booking, they pledge to plant 25 trees, though the actual number is derived from total cost of booking so it may be more, or less.&lt;/p&gt;
&lt;p&gt;On some sites a &amp;ldquo;green leaf&amp;rdquo; may appear next to it. This denotes a business that shares the values of Ecosia. To be awarded the icon, each business must have demonstrated ecologically friendly or supply sustainable products or services. The leaf icon does not increase visibility in search rankings, nor can it be bought, only earned.&lt;/p&gt;
&lt;p&gt;Thankfully, Ecosia respects privacy offering a service without trackers, analytics, onselling to advertisers and actually respects Do Not Track headers. Trust but verify &lt;a href="%5Bhttps://info.ecosia.org/privacy%5D(https://info.ecosia.org/privacy)"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="where-can-i-find-more-info"&gt;Where can I find more info&lt;/h2&gt;
&lt;p&gt;Checkout Ecosia by giving it a try below.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.ecosia.org/"&gt;Search page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.ecosia.org/"&gt;Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ecosia.teemill.com/"&gt;Swag&lt;/a&gt;&lt;/p&gt;</description></item><item><title>learn docker in one month</title><link>https://danielms.site/blog/learn-docker-in-one-month/</link><pubDate>Sun, 24 Nov 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/learn-docker-in-one-month/</guid><description>&lt;h1 id="learning-docker-in-one-month"&gt;Learning Docker in one month&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/docker.png" alt="" title="docker image"&gt;&lt;/p&gt;
&lt;p&gt;Yep, &lt;a href="https://www.norvig.com/21-days.html"&gt;one month&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="docker-basics"&gt;Docker basics&lt;/h2&gt;
&lt;h3 id="what"&gt;What&lt;/h3&gt;
&lt;p&gt;docker is a product that uses operating system virtualisation to created packages and applications which live within a &amp;ldquo;container&amp;rdquo;.
These containers bundle together applications and dependancies without the need for installation on the host operating system. Containerisation is meant to empower developers by providing a &amp;ldquo;written once and run anywhere&amp;rdquo; methodology.&lt;/p&gt;
&lt;h3 id="why"&gt;Why&lt;/h3&gt;
&lt;p&gt;Some of the core reasons to use docker.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The ability to have standalone applications and packages with dependencies that are isolated for the base operating system and other applications is a boon for developers and users,&lt;/li&gt;
&lt;li&gt;Linux hosts can have several docker containers running without fear of contaminated package versions or dependancies causing errors,&lt;/li&gt;
&lt;li&gt;Sharing consistent environments between developers is possible as each docker container image is idempotent, meaning the result of building a docker image will be the same whether built once, or one thousand times,&lt;/li&gt;
&lt;li&gt;Version pinning is possible making compatibility issues on new release cycles less problematic, and testing of packages easier to implement,&lt;/li&gt;
&lt;li&gt;Continious Integration is a perfectly paired with docker allowing each new release, or push to the repository to be built automatically with a new docker image, and tested to ensure against regression.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="virtual-machines-arent-docker"&gt;Virtual Machines aren&amp;rsquo;t docker&lt;/h3&gt;
&lt;p&gt;Docker and virtual machines differ in their implementation and uses cases. They are complimentary rather than competitors and service different needs through each use-case.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/dockervm.png" alt="" title="docker versus virtual machines"&gt;&lt;/p&gt;
&lt;p&gt;Containers leverage the base operating systems kernel whereas virtual machines initialise each machine with its own hardware virtualised operating system. Although docker utilises the host&amp;rsquo;s operating system, its application are seperated from the kernel ensuring isolation via the docker engine.&lt;/p&gt;
&lt;p&gt;Regardless, each technology should be utilised as a method to achieve an outcome rather than be a dogmatic position for any one developer to live and die by. In fact, both methods can be used in conjunction, it is possible to run docker contianers within virtual machines - this being something I do often in Vagrant.&lt;/p&gt;
&lt;h2 id="docker-processes"&gt;Docker processes&lt;/h2&gt;
&lt;p&gt;An important distinction with docker is its process orientation. Containers are designed to execute one process and then quit - the length of the running process arbitary but once its is complete the container will effectively exit.&lt;/p&gt;
&lt;p&gt;this is an important point for new docker users. It contrasts heavily from a virtual machine which will live forever, sitting idly doing nothing until instructed.&lt;/p&gt;
&lt;p&gt;For example, a contianer may run a command line application that fetches the weather from your local area. Once the container has finished this process it will stop and cease to draw any resources until manually called again by the user or application.&lt;/p&gt;
&lt;h2 id="how-does-docker-work"&gt;How does docker work&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;docker container run -d -p 8080:80 --name myapache httpd&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is two main concepts to grasp with docker; images and containers.&lt;/p&gt;
&lt;h3 id="images"&gt;images&lt;/h3&gt;
&lt;p&gt;Images can be thought of as virtual machine snapshots - a point in time that contains an application, its dependancies and files. Unlike a virtual machine this &amp;ldquo;snapshot&amp;rdquo;, or image is immutable and static. It serves as a blueprint, rather than a dormant virtual appliance waiting to be brought back to life.&lt;/p&gt;
&lt;p&gt;To sum, images are what we then create containers from and ensure that each launched container will be identical to the base image.&lt;/p&gt;
&lt;h3 id="containers"&gt;containers&lt;/h3&gt;
&lt;p&gt;Containers are the running applications brought to life from a docker image. This is the process which does some work, be it short or long lived. At their essence, containers are meant to be disposable, existing only to complete their task and then exiting.&lt;/p&gt;
&lt;p&gt;Principles of containers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Immutable&lt;/li&gt;
&lt;li&gt;Disposable&lt;/li&gt;
&lt;li&gt;One process per container&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="demo-1"&gt;Demo #1&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start a docker application.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker run --rm danielmichaels/http-tracer http://nyti.ms/1QETHgV&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You should see something similar to the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Unable to find image &lt;span class="s1"&gt;&amp;#39;danielmichaels/http-tracer:latest&amp;#39;&lt;/span&gt; locally
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;latest: Pulling from danielmichaels/http-tracer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;c87736221ed0: Already exists
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;9dc197b2c846: Already exists
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;02f2755d81e6: Already exists
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;012e932ae3a4: Pull &lt;span class="nb"&gt;complete&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Digest: sha256:b463068aec6a475854277d8e0a485281166d1fa18eb7c9c8e09cb858e3031684
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Status: Downloaded newer image &lt;span class="k"&gt;for&lt;/span&gt; danielmichaels/http-tracer:latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Docker always checks the local machine for any images matching the request. If it does not find any it will check its upstream repository for any matches, in this case, &lt;a href="https://hub.docker.com"&gt;Docker Hub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If found, docker will then start to pull down the container&amp;rsquo;s image. Once its finished downloading, docker executes the &lt;code&gt;docker run&lt;/code&gt; command. Try it out.&lt;/p&gt;
&lt;p&gt;In this example, we&amp;rsquo;ve downloaded a script that executes once - printing information to the terminal and then exits. The next time the container is called, it finds the cached image and spins up the container much faster. The only slow down being the scripts IO bound process.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look at a long lived process, such as a webserver.&lt;/p&gt;
&lt;h2 id="demo-2"&gt;Demo #2&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;docker run --rm -it -p 8081:80 nginx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Again, an image will be downloaded (don&amp;rsquo;t worry we&amp;rsquo;ll go over deleting images) before it finally runs the container.&lt;/p&gt;
&lt;p&gt;This time, nothing should happen and it may appear to have hung. In a browser, navigate to &lt;a href="http://127.0.0.1:8081"&gt;localhost:8081&lt;/a&gt; and observe the terminal where the container is running. Requests will be printed to the screen.&lt;/p&gt;
&lt;p&gt;If you are wondering about the &lt;code&gt;-p&lt;/code&gt; flag, it is shorthand for &lt;code&gt;--publish&lt;/code&gt; and is the mechanism for mapping the &lt;em&gt;host&lt;/em&gt; and &lt;em&gt;container&lt;/em&gt; port. The host port is first, container second or &lt;code&gt;-p &amp;lt;host&amp;gt;:&amp;lt;container&amp;gt;&lt;/code&gt;. For nginx, it expects port 80 so we set the container to that however the host port could be set to any realistic port.&lt;/p&gt;
&lt;p&gt;So we should have the &lt;em&gt;nginx&lt;/em&gt; default page in the browser, and the request for that page in our terminal. This is a good example of how docker processes can be long running - the webserver is a process and it&amp;rsquo;s still running.&lt;/p&gt;
&lt;p&gt;We can also make this process run without printing anything to the screen by omitting the &lt;code&gt;-it&lt;/code&gt; and replacing it with &lt;code&gt;-d&lt;/code&gt; or &lt;code&gt;--detach&lt;/code&gt;. The terminal should output a long alphanumeric string indicating its &lt;em&gt;container id&lt;/em&gt;. Confirm this with &lt;code&gt;docker ps&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To stop detached containers use &lt;code&gt;docker stop &amp;lt;container id|container name&amp;gt;&lt;/code&gt;. Again, the id or name used to kill the process should be echoed to the terminal. A &lt;code&gt;docker ps&lt;/code&gt; will confirm this.&lt;/p&gt;
&lt;p&gt;Lastly, the &lt;code&gt;--rm&lt;/code&gt; flag simply removes the non-dead container from the host filesystem. For our purposes, we do not need or want to keep copies of our containers on the system. Generally, its a good idea to keep them for debugging but can really start to absorb a lot of disk space on the host during testing.
To check for this run &lt;code&gt;docker ps -a&lt;/code&gt; which will print out any old containers that can be cleaned up. To clean away these container use the &lt;code&gt;prune&lt;/code&gt; command: &lt;code&gt;docker container prune&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="bind-mounts"&gt;Bind Mounts&lt;/h2&gt;
&lt;p&gt;So far the utility has lacked any real substance. I&amp;rsquo;ve got a web app, how does my code get on here?&lt;/p&gt;
&lt;p&gt;There are two methods to mount data to contianers; using &lt;code&gt;--volume/-v&lt;/code&gt; or &lt;code&gt;--mount&lt;/code&gt;. Docker &lt;a href="https://docs.docker.com/storage/bind-mounts/"&gt;recommends&lt;/a&gt; using the &lt;code&gt;--mount&lt;/code&gt; method for a few reasons, namely the ability to share mounts between contianers.&lt;/p&gt;
&lt;h3 id="using--v"&gt;Using &lt;code&gt;-v&lt;/code&gt;&lt;/h3&gt;
&lt;h2 id="short-running-container"&gt;Short running container&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c1"&gt;# run docker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--rm &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c1"&gt;# remove container once done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-it &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c1"&gt;# interactive mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-v &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;:/src python:3 &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c1"&gt;# volume to mount with container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python hello.py &lt;span class="c1"&gt;# process to run; python and script located in the volume - /src&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Long running container&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c1"&gt;# run docker&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--rm &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c1"&gt;# remove container once done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-it &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c1"&gt;# interactive mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# replace -it with -d to detach and run in background&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-v &lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;:/usr/share/nginx/html &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c1"&gt;# volume to mount with container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-p 8080:80 &lt;span class="se"&gt;\ &lt;/span&gt; &lt;span class="c1"&gt;# port host:container&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nginx:latest &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c1"&gt;# image to run with tag (latest version)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;bind routes &lt;code&gt;-v&lt;/code&gt; put area on local to area on container. &lt;code&gt;docker run -d -p 8080:80 --name nginx-bind -v $(pwd):/var/share/html nginx&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="and---mount"&gt;and &lt;code&gt;--mount&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This method is more verbose than &lt;code&gt;-v&lt;/code&gt; but much more powerful. Because we are using key:value pairs the order is not important.&lt;/p&gt;
&lt;p&gt;From the documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;type&lt;/code&gt; of the mount, which can be &lt;code&gt;bind&lt;/code&gt;, &lt;code&gt;volume&lt;/code&gt;, or &lt;code&gt;tmpfs&lt;/code&gt;. This topic discusses bind mounts, so the type is always &lt;code&gt;bind&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;source&lt;/code&gt; of the mount. For bind mounts, this is the path to the file or directory on the Docker daemon host. May be specified as &lt;code&gt;source&lt;/code&gt; or &lt;code&gt;src&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;destination&lt;/code&gt; takes as its value the path where the file or directory is mounted in the container. May be specified as &lt;code&gt;destination&lt;/code&gt;, &lt;code&gt;dst&lt;/code&gt;, or &lt;code&gt;target&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;readonly&lt;/code&gt; option, if present, causes the bind mount to be mounted into the container as read-only.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;bind-propagation&lt;/code&gt; option, if present, changes the bind propagation. May be one of &lt;code&gt;rprivate&lt;/code&gt;, &lt;code&gt;private&lt;/code&gt;, &lt;code&gt;rshared&lt;/code&gt;, &lt;code&gt;shared&lt;/code&gt;, &lt;code&gt;rslave&lt;/code&gt;, &lt;code&gt;slave&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;consistency&lt;/code&gt; option, if present, may be one of &lt;code&gt;consistent&lt;/code&gt;, &lt;code&gt;delegated&lt;/code&gt;, or &lt;code&gt;cached&lt;/code&gt;. This setting only applies to Docker Desktop for Mac, and is ignored on all other platforms.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;To illustrate we are creating our volume (a bind mount) in our current working directory. Inside the directory we have a python script which runs &lt;code&gt;print('--mount!')&lt;/code&gt; and nothing more. The file is called &lt;code&gt;mount.py&lt;/code&gt;. The structure should look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# tree .&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── mount.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;0&lt;/span&gt; directories, &lt;span class="m"&gt;1&lt;/span&gt; file
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To mount this file and make it executable via docker we use the &lt;code&gt;--mount&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker run --rm --mount source=&amp;quot;$(pwd)&amp;quot;/,destination=/apps,type=bind python python /apps/mount.py&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Breaking down the &lt;code&gt;--mount&lt;/code&gt; starting with &lt;code&gt;source&lt;/code&gt;. This is the location of the data to be &amp;ldquo;loaded&amp;rdquo; into the container, most often from a local machine. The unix command &lt;code&gt;$(pwd)&lt;/code&gt; returns an absolute path, which &lt;code&gt;mount&lt;/code&gt; requires—it cannot accept relative paths.
This effectively tells docker to put anything within that directory into a volume and attach itot the container.&lt;/p&gt;
&lt;p&gt;Next is &lt;code&gt;destination&lt;/code&gt; which can also be called as &lt;code&gt;target&lt;/code&gt;. This tells docker to place the &lt;code&gt;source&lt;/code&gt; data inside &lt;em&gt;this&lt;/em&gt; location within the container. So if I was to set &lt;code&gt;destination=/shazwazza&lt;/code&gt;, then the data would be located in the &lt;code&gt;/&lt;/code&gt; as &lt;code&gt;/shazawazza&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, in this example we are setting &lt;code&gt;type=bind&lt;/code&gt;. This is the most simple method, though less extensible than &lt;code&gt;volume&lt;/code&gt; mounting. For small independant applications it is often best to at least start with a &lt;code&gt;bind&lt;/code&gt; type and then when ready move onto &lt;code&gt;volume&lt;/code&gt;. See &lt;a href="https://docs.docker.com/storage/volumes/"&gt;here&lt;/a&gt; for detailed information regarding the &lt;code&gt;volume&lt;/code&gt; mounting, and when you should use it.&lt;/p&gt;
&lt;h2 id="useful-docker-commands"&gt;Useful docker commands&lt;/h2&gt;
&lt;h3 id="docker-info"&gt;docker info&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Displays system-wide information&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-inspect-imagecontianer"&gt;docker inspect &amp;lt;image/contianer&amp;gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Return low-level information about a container or image&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-container-ls"&gt;docker container ls&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Returns currently running containers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-ps"&gt;docker ps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Shorthand for &lt;code&gt;docker container ls&lt;/code&gt;. Appending &lt;code&gt;-a&lt;/code&gt; will show containers that have exited.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-image-ls"&gt;docker image ls&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Prints all images on the local machine that have been pulled down from docker hub.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-pull"&gt;docker pull&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Will download a docker image onto the local machine&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-run"&gt;docker run&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Runs a docker container, and if not found on the local machine will attempt to download it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="docker-flags"&gt;docker flags&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--rm&lt;/code&gt;: remove container from list once process exits&lt;/li&gt;
&lt;li&gt;&lt;code&gt;prune&lt;/code&gt;: remove any old or &amp;ldquo;dangling&amp;rdquo; images, containers or networks. Good for deleting old cached builds no longer in use. E.g. &lt;code&gt;docker container prune&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;exec: &lt;code&gt;docker exec -it myapache bash&lt;/code&gt; executes the bash shell for an interactive session.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="dockerfile"&gt;Dockerfile&lt;/h2&gt;
&lt;p&gt;So far, we have only pulled and ran docker images, or added a mount to a local container. That is useful but dockers biggest selling point is being able to create and bundle our own applications into a container and allowing others to use them on other systems. To do this, we use a Dockerfile.&lt;/p&gt;
&lt;p&gt;From &lt;a href="https://docs.docker.com/engine/reference/builder/"&gt;Docker&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When creating a docker image, we most often want to pull from an existing image within a registry such as &lt;a href="https://www.docker.com/products/docker-hub"&gt;Docker hub&lt;/a&gt; and extend it.&lt;/p&gt;
&lt;h2 id="example"&gt;Example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# simple example&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FROM jfloff/alpine-python:3.6-slim
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ENV &lt;span class="nv"&gt;LC_ALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C.UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ENV &lt;span class="nv"&gt;LANG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;C.UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RUN pip install http-tracer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ENTRYPOINT &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http-tracer&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pfsense.org&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above Dockerfile, has some keyword&amp;rsquo;s denoted by capitals, followed by an argument. This is the general structure of a Dockerfile and the layout is very important as we will see below. There are many more keywords but that&amp;rsquo;s reserved for your own experimentation.&lt;/p&gt;
&lt;p&gt;All dockerfile&amp;rsquo;s start with &lt;code&gt;FROM&lt;/code&gt; - which basically means &amp;ldquo;pull this image and extend it with the following..&amp;rdquo;&lt;/p&gt;
&lt;p&gt;In this particular application, we need to ensure the languages are set correctly. Dockerfile&amp;rsquo;s allow users to set Environment Variables with the &lt;code&gt;ENV&lt;/code&gt; keyword.&lt;/p&gt;
&lt;p&gt;Perhaps the most important and well known command is &lt;code&gt;RUN&lt;/code&gt; which tells docker to call the following commands within the container. In this case, as we are building atop of an alpine-linux minimal python version, &lt;code&gt;pip&lt;/code&gt; comes bundled with it. If the image was &lt;code&gt;ubuntu&lt;/code&gt; based we could call &lt;code&gt;apt-get&lt;/code&gt; and so on.&lt;/p&gt;
&lt;p&gt;The next two lines (layers) are special to this commandline application. &lt;code&gt;ENTRYPOINT&lt;/code&gt; defines the command we want to execute within the container and because our app, &lt;code&gt;http-tracer&lt;/code&gt; is installed in the &lt;code&gt;PATH&lt;/code&gt; as apart of its &lt;code&gt;pip&lt;/code&gt; installation, it is executable. Typically, &lt;code&gt;ENTRYPOINT&lt;/code&gt; is used in contianers that are designed to be run as an executable, which this application is.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CMD&lt;/code&gt; is similar to &lt;code&gt;ENTRYPOINT&lt;/code&gt; except we can only use it once and it is used to set default commands or parameters. the argument that follows a &lt;code&gt;CMD&lt;/code&gt; keyword will run anytime the container is executed without specifying any arguments with it. So in this case, out &lt;code&gt;http-tracer&lt;/code&gt; container will be executed with &lt;code&gt;&amp;quot;pfense.org&amp;quot;&lt;/code&gt; as its default argument. If the container runs with a command, this default will be overwritten.&lt;/p&gt;
&lt;p&gt;More simply, to allow user input;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The ENTRYPOINT specifies a command that will always be executed when the container starts. The CMD specifies arguments that will be fed to the ENTRYPOINT&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;confused?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Calling our docker image without any arguments will result in the following:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/docker.svg" alt="" title="example of http-tracer executing without user applied parameter and defaulting to pfsense.org"&gt;&lt;/p&gt;
&lt;p&gt;Whereas if we set a argument of &amp;ldquo;cisco.com&amp;rdquo; it will overwrite the default and return data relating to that parameter.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/docker2.svg" alt="" title="example of http-tracer with parameter of cisco.com applied as user input"&gt;&lt;/p&gt;
&lt;p&gt;Another example worth looking at is the &lt;code&gt;ping&lt;/code&gt; command. You can execute that with a dockerfile containing the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FROM debian:wheezy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ENTRYPOINT [&amp;#34;/bin/ping&amp;#34;] # executes when container is run
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CMD [&amp;#34;localhost&amp;#34;] # executes unless user input supplied
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="layers"&gt;layers&lt;/h2&gt;
&lt;p&gt;In a Dockerfile, each line is a layer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;layers cannot shrink the size of the image&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;layers are cached (checks the Dockerfile lines for differences)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;caching means it does not need to be rebuilt, only new layers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;container is a layer, one that exists only while the container lives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the build layers are read only - cannot edit them and are shared across many containers&lt;/li&gt;
&lt;li&gt;if the image needs updating, all layers must be rebuilt&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;container layer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;read write&lt;/li&gt;
&lt;li&gt;modifications of source, or volumes within the container exist only within &lt;strong&gt;that&lt;/strong&gt; container unless rebuilt into a new image&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;image layer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;read only&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="docker-networks"&gt;docker networks&lt;/h2&gt;
&lt;p&gt;Docker also provides a networking layer, allowing many contianers to be on a local area network. If this is not setup, each container is essentially within its own virtual LAN and cannot communicate with other containers. This is a big topic so read up on the docs [here].&lt;/p&gt;
&lt;p&gt;The basics are as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;to create a network for a &lt;code&gt;mysql&lt;/code&gt; database and &lt;code&gt;node&lt;/code&gt; application to communitcate run: &lt;code&gt;docker network create &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;see that your network has been created with &lt;code&gt;docker network ls&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;inspect your network with &lt;code&gt;docker network inspect &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/docker3.svg" alt="" title="example of docker network ls and inspect output"&gt;&lt;/p&gt;
&lt;p&gt;To add containers to a network, and test they have connectivity with &lt;code&gt;ping&lt;/code&gt; is just a matter of adding them to the network with &lt;code&gt;--net&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# create mysql on docker network named &amp;#39;test&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run --rm -d --net &lt;span class="nb"&gt;test&lt;/span&gt; --name test_mysql -e &lt;span class="nv"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;root&amp;#39;&lt;/span&gt; mysql:5.6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# create node linking to the &amp;#39;test&amp;#39; network and spawn a shell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run --rm -it --net &lt;span class="nb"&gt;test&lt;/span&gt; --name test_node node:8 /bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# once they&amp;#39;ve been built it will drop into a shell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# test the network connection by trying to ping test_mysql&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ping test_mysql &lt;span class="c1"&gt;# from node /bin/bash shell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# unless explicitly denied, this gives access to internet&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ping 1.1.1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="docker-enough-to-get-by"&gt;Docker enough to get by&lt;/h2&gt;
&lt;p&gt;That is really enough docker to start playing around and learning to be comfortable in it. This hasn&amp;rsquo;t covered &lt;code&gt;docker-compose&lt;/code&gt; which is another powerful tool, but having a baseline understanding of docker is paramount. The whole point is to learn slower than you think, and be exposed to new technologies over a longer period of time - rather than glossing of all the new shinies and not really learning anything. It&amp;rsquo;s okay to be slow - its all &lt;em&gt;time under tension&lt;/em&gt; and repeated small exposures that eventualy lead to true understanding.&lt;/p&gt;</description></item><item><title>Defending your apps</title><link>https://danielms.site/blog/defence-mechanisms-for-web-apps/</link><pubDate>Sat, 19 Oct 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/defence-mechanisms-for-web-apps/</guid><description>&lt;h1 id="web-application-defence-mechanisms"&gt;Web Application Defence Mechanisms&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;one rule; do not trust user input.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/defence-mechanisms-post.jpg" alt="" title="binary globe"&gt;&lt;/p&gt;
&lt;h2 id="core-defences"&gt;Core defences&lt;/h2&gt;
&lt;p&gt;The methods by which we can defend against attackers.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Handling user access&lt;/li&gt;
&lt;li&gt;Handling user input&lt;/li&gt;
&lt;li&gt;Handling attacks or ensuring that the application works as intended when being directly targeted, and&lt;/li&gt;
&lt;li&gt;Handling the application itself so everything can be monitored, and configured correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="user-access"&gt;User Access&lt;/h3&gt;
&lt;p&gt;In most applications that have users, there will be several privileges or roles within the user space. For instance, a social-networking site might have the base user, a group administrator and overall application administrator accounts. Likewise, the owner of an account should be able to view their own data but not that of others. Mitigation&amp;rsquo;s, or security mechanisms in place within this spectrum are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Authentication,&lt;/li&gt;
&lt;li&gt;Session Management,&lt;/li&gt;
&lt;li&gt;Access Control.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="authentication"&gt;Authentication&lt;/h3&gt;
&lt;p&gt;The simplest to understand; a method to check the validity of a user - username and password. Authentication can scale appropriately with the applications need, such as MFA. Many authentication mechanisms also have password resets, account recovery, and self-registrations. All of which add to the attack surface.&lt;/p&gt;
&lt;h3 id="session-management"&gt;Session Management&lt;/h3&gt;
&lt;p&gt;On busy web applications there can be several other users sending requests simultaneously to the server requesting identical things. For instance, hundreds of people may be viewing the same page on amazon and adding an item to their basket whilst browsing without being logged in. The application must keep track of theses requests and be able to distinguish each unique visitor.&lt;/p&gt;
&lt;p&gt;This is done via tokens, or sessions that track each user and are stored on the web server. The server then forwards the unique token to the client, which then returns that token in each subsequent HTTP request. This allows both sides to session track. Cookies are one such method for session management.&lt;/p&gt;
&lt;p&gt;Common attacks against session management is stealing tokens, guessing tokens or exploiting defects in how the token is used to authenticate the owner. Stealing a session token could allow an attacker to use an authenticated users token effectively allowing them to masquerade as that user.&lt;/p&gt;
&lt;h3 id="access-control"&gt;Access Control&lt;/h3&gt;
&lt;p&gt;Simply put, the applications ability to assess whether a user has the required Bona-Fides to gain access to the system resources they are requesting. This often requires some fine-grained logic and thus can be exploited if developers make too many assumptions about how a &amp;ldquo;user&amp;rdquo; will access the resources.&lt;/p&gt;
&lt;h2 id="user-input"&gt;User Input&lt;/h2&gt;
&lt;p&gt;All user input is untrustworthy. Therefore, applications must handle all input with strict enforcement of this rule. Sanitation and restriction of inputs and reducing assumptions about what a &amp;ldquo;user&amp;rdquo; would submit are necessary precautions. But, protecting against all forms of malicious input is a difficult task and many applications are vulnerable to this.&lt;/p&gt;
&lt;h3 id="varieties-of-input"&gt;Varieties of Input&lt;/h3&gt;
&lt;p&gt;Limiting what a user can input is common practice. For example, a user sign up page may use an email account that requires the use of an @ symbol and must only contain alphanumeric letters with a minimum length of four characters. Other examples may be the requirement to submit a textual paragraph that allows all alphabetical characters plus special characters but will not allow HTML tags. Many other possibilities exist such as markup languages and how they are delimited so as not to allow arbitrary code to be run.&lt;/p&gt;
&lt;h2 id="approaches-to-input-handling"&gt;Approaches to Input Handling&lt;/h2&gt;
&lt;h3 id="reject-known-bad"&gt;Reject Known Bad&lt;/h3&gt;
&lt;p&gt;Blacklisting known bad things. The weakest form of defence, as spoofing or encoding arbitrary code blocks could slip past this measure.&lt;/p&gt;
&lt;p&gt;For example, if &lt;code&gt;SELECT&lt;/code&gt; is blocked, &lt;code&gt;SeLeCt&lt;/code&gt; might not be! or if &lt;code&gt;alert('xss')&lt;/code&gt; is blocked maybe &lt;code&gt;prompt('xsss')&lt;/code&gt; isn&amp;rsquo;t. In addition, some tokensing parsers which detect these blacklisted words may stop searching when the detect a NULL Byte. &lt;code&gt;%00&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&lt;/code&gt; may get through.&lt;/p&gt;
&lt;h3 id="accept-known-good"&gt;Accept Known Good&lt;/h3&gt;
&lt;p&gt;This method is very effective against stopping code injection as it is based off only allowing accepted elements to be input. The flaw in this is where accepting a known good can conflict with the requirements of the application itself. For instance, a name field in a form will likely necessitate the use of &lt;code&gt;-&lt;/code&gt;, as some people legitimately have hyphenated names. But, &lt;code&gt;-&lt;/code&gt; are commonly used to attack databases on the backend, so it may not be used in all cases where it could detract from the user experience.&lt;/p&gt;
&lt;h3 id="sanitisation"&gt;Sanitisation&lt;/h3&gt;
&lt;p&gt;A common defence mechanism that relies on the fact that users will try malicious code, so before processing the application will sanitise the input in various ways. Methods such as removing dangerous characters or escaping them before processing. This is something all applications should do, and their are many libraries in every language to support this.&lt;/p&gt;
&lt;h2 id="boundary-validation"&gt;Boundary Validation&lt;/h2&gt;
&lt;p&gt;Simplistically, this is the process of validating, or checking user input on the periphrases first prior to the input reaching the backend. Think taking dirty input on the web side and cleaning it before sending to the server side, and therefore trusting all server side code to be &amp;ldquo;clean&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;This has some serious faults. Often attackers can exploit such a scheme by chaining exploits in a way that no validation method could feasibly defend against without serious detriment to the overall functionality of the application itself.&lt;/p&gt;
&lt;p&gt;The best solution to defending against this, is boundary validation where for each layer of processing, a validation check is conducted appropriate to that layer; defence in depth.&lt;/p&gt;
&lt;h2 id="handling-attackers"&gt;Handling Attackers&lt;/h2&gt;
&lt;p&gt;All applications should be of the mindset that they will be targeted and attacked by dedicated and skilled attackers. With this mindset, the developers must have a methodology for handling and monitoring such incidents. These measures should include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Error Handling,&lt;/li&gt;
&lt;li&gt;Logging,&lt;/li&gt;
&lt;li&gt;Alerts,&lt;/li&gt;
&lt;li&gt;Reacting to attackers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="handling-errors"&gt;Handling Errors&lt;/h3&gt;
&lt;p&gt;Robust testing of the application prior to launch should be conducted, and if done properly identify many bugs and errors in the design and code base. But, not all issues can be counted for in testing and as such there must be a way to capture and respond to errors in the application.&lt;/p&gt;
&lt;p&gt;Debug and system messages should never be presented to the outer layers - they greatly assist the attackers in identifying weaknesses in the application. All errors should be gracefully raised, and catered for so as to not give away too much information to the attacker.&lt;/p&gt;
&lt;h3 id="audit-logs"&gt;Audit Logs&lt;/h3&gt;
&lt;p&gt;All activities taken on the system should be logged for analysis later. Items that definitely require logging are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All authentication actions; failed, successful, and forgotten or changed passwords,&lt;/li&gt;
&lt;li&gt;Transactions such as payments,&lt;/li&gt;
&lt;li&gt;Activities blocked by access control mechanisms,&lt;/li&gt;
&lt;li&gt;Any attack strings such as malicious input.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is important that these logs are protected against unauthorised read and writes. This can sometimes be achieved by the use of autonomous systems that only accept input data from the servers.&lt;/p&gt;
&lt;h3 id="alerting-administrators"&gt;Alerting Administrators&lt;/h3&gt;
&lt;p&gt;Audit logs are retroactive; they allow a detailed look into the past for forensic analysis but provide little real time information. Alert mechanisms that should be brought up to the administrators attention are things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Anomalies, such as a large number of requests originating from a single IP,&lt;/li&gt;
&lt;li&gt;business anomalies like unusual funds transfers or out of hours actions on a machine,&lt;/li&gt;
&lt;li&gt;requests containing attack strings,&lt;/li&gt;
&lt;li&gt;requests which data is hidden with the intent to obfuscate the true intent of the data, or request.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="reacting-to-attacks"&gt;Reacting to Attacks&lt;/h3&gt;
&lt;p&gt;Alerts signifying an attack is nothing without a response. Reactive approaches will should be unique to the application in question but many are generic. Things such as slowing or putting up things to block simple attacks will not defeat a prudent and patient attacker, but should stop a lot of automated broad brush attacks.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;The above methods for protecting web applications are nothing new, in fact, much of this content is taken from old resources such as the &lt;a href="https://portswigger.net/web-security/web-application-hackers-handbook"&gt;web application hackers handbook&lt;/a&gt;. That is because the advice still holds true even in today&amp;rsquo;s &amp;ldquo;modern&amp;rdquo; web app&amp;rsquo;s. Attacks and methods may change slightly, or evolve to overcome new frameworks securities mechanisms but the vast majority of attacks simply exploit what could be defended against by implementing whats contained in this post.&lt;/p&gt;
&lt;p&gt;Things don&amp;rsquo;t change that much.&lt;/p&gt;</description></item><item><title>Dropbear and AWS</title><link>https://danielms.site/blog/dropbear-aws-pem-keys/</link><pubDate>Tue, 10 Sep 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/dropbear-aws-pem-keys/</guid><description>&lt;h1 id="busybox-ssh-and-ec2"&gt;BusyBox, SSH and EC2&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/bash-terminal.png" alt="terminal" title="terminal graphic"&gt;&lt;/p&gt;
&lt;p&gt;Accessing an EC2 instance from BusyBox&amp;rsquo;s Dropbear SSH client isn&amp;rsquo;t easy. Firstly, &lt;code&gt;.pem&lt;/code&gt; files are not compatible with &lt;code&gt;dropbear&lt;/code&gt;, nor can you convert them to dropbear&amp;rsquo;s key format with the built-in &lt;code&gt;dropbearconvert&lt;/code&gt;. Secondly, depending on your version of &lt;code&gt;openssh&lt;/code&gt; it may not be immediately apparent that your private keys are incompatible with the conversion application either. Thankfully, workarounds are possible.&lt;/p&gt;
&lt;h2 id="dropbear-ssh"&gt;Dropbear SSH&lt;/h2&gt;
&lt;p&gt;Dropbear is a lightweight client and server application mostly seen on embedded devices. It is designed to replace OpenSSH in low memory footprint systems as it can be compiled down to &lt;a href="https://lists.ucc.gu.uwa.edu.au/pipermail/dropbear/2004q3/000022.html"&gt;110kb&lt;/a&gt;.
It is compatible with &lt;code&gt;.ssh/authorized_keys&lt;/code&gt;, however it does have limitations.&lt;/p&gt;
&lt;h2 id="those-limitations"&gt;Those limitations&lt;/h2&gt;
&lt;p&gt;Amazon Web Services such as Elastic Cloud Compute hand out private keys for passwordless login. Unfortunately for dropbear users, the format is &lt;code&gt;.pem&lt;/code&gt; which is incompatible. And, &lt;code&gt;dropbearconvert&lt;/code&gt; - the program provided for turning &lt;code&gt;openssh&lt;/code&gt; keys into &lt;code&gt;dropbear&lt;/code&gt; keys - does not accept &lt;code&gt;.pem&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Dropbear&amp;rsquo;s conversion tool will also not accept private keys from &lt;code&gt;openssh&lt;/code&gt;, as seen below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-----BEGIN OPENSSH PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NhAAAAAwEAAQAAAYEAtNY9pkKAhGYD/qDIThmRd7Y8kMo7QvbSQj+tv5huz4hTqdgALmxP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;....
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;FBuu7Rt2qQdJPBAAAADGRhbmllbEBBbm9hdAECAwQFBg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-----END OPENSSH PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Instead, it must be an &lt;code&gt;rsa&lt;/code&gt; key like so.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-----BEGIN RSA PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;VY2Q002wQjJzfA783q0wPwPgdQVNBj8timSYHTmZLlZ54pPtBLhMvZ4tJ/AeXxSm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIIEpQIBAAKCAQEA4N5r5Z+/rl2lmNdxsmcqyhfZ49m1g/5mIMSdPbTXgKcn2T3o
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pEJt+8fBAoGBAM2KBHEA5RFnv812nGJG6f2scaMxufbQh5vtc0tf7DDAPqmHlnqr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-----END RSA PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="the-workaround"&gt;The workaround&lt;/h2&gt;
&lt;p&gt;I found the most reliable means to gain access to EC2 from &lt;code&gt;dropbear&lt;/code&gt; on &lt;code&gt;busybox&lt;/code&gt; was to create the keys elsewhere and then move them to the device manually.&lt;/p&gt;
&lt;h3 id="the-steps"&gt;The steps&lt;/h3&gt;
&lt;h4 id="1-create-a-public-and-private-key-on-either-the-aws-ec2-instance-or-your-local-machine-with"&gt;1. Create a public and private key on either the AWS EC2 instance, or your local machine with:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssh-keygen -m PEM -t rsa -b 4096 -f &amp;lt;new_privkey&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-copy-the-private-key-to-busybox"&gt;2. Copy the &lt;em&gt;private&lt;/em&gt; key to BusyBox:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scp &amp;lt;priv_key_filename&amp;gt; &amp;lt;user&amp;gt;@&amp;lt;busybox&amp;gt;:/&amp;lt;user&amp;gt;/.ssh/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-copy-the-private-key-to-the-cloud---only-if-doing-these-steps-on-your-local-system"&gt;3. Copy the private key to the cloud - &lt;em&gt;only if doing these steps on your local system&lt;/em&gt;:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scp -i &amp;lt;aws-ec2-private-key&amp;gt;.pem &amp;lt;new_privkey&amp;gt; ec2-user@&amp;lt;ec2-dns-address&amp;gt;:/home/&amp;lt;ubuntu&amp;gt;/.ssh/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="4-on-the-system-you-are-running-these-steps-inside-sshauthorized_hosts-append-the-public-key"&gt;4. On the system you are running these steps, inside &lt;code&gt;.ssh/authorized_hosts&lt;/code&gt; append the public key:&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;from local machine&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cat &amp;lt;new_pubkey&amp;gt;.pub | ssh -i &amp;lt;aws-ec2-private_key&amp;gt; | &amp;quot;cat &amp;gt;&amp;gt; /home/ubuntu/.ssh/authorized_keys&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;inside ec2&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cat &amp;lt;new_pubkey&amp;gt;.pub &amp;gt;&amp;gt; .ssh/authorized_keys&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="5-this-will-convert-our-rsa-key-into-a-dropbear-compatible-key"&gt;5. This will convert our rsa key into a dropbear compatible key&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Login to busybox&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dropbearconvert openssh dropbear &amp;lt;new_privkey&amp;gt; &amp;lt;new_privkey_dropbear_compat&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="6-login-in-to-aws"&gt;6. Login in to AWS&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssh -i &amp;lt;new_privkey_dropbear_compat&amp;gt; &amp;lt;ec2-user&amp;gt;@&amp;lt;ec2-dns-address&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;profit&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="get-to-work"&gt;Get to work&lt;/h2&gt;
&lt;p&gt;We can now access our AWS server from our &lt;code&gt;busybox&lt;/code&gt; device. From here we can do whatever we need to do such as establish a reverse tunnel for administration 😉&lt;/p&gt;</description></item><item><title>Curl you an email for great good</title><link>https://danielms.site/blog/curl-emails-for-great-good/</link><pubDate>Mon, 12 Aug 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/curl-emails-for-great-good/</guid><description>&lt;h1 id="curl"&gt;Curl&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/curl-logo.svg" alt="" title="curl icon"&gt;&lt;/p&gt;
&lt;p&gt;Today I learnt that Curl can also be used to send emails. One of my projects is to manage a fleet of embedded devices that communicate information back to our servers. Email is one part of this process. And, &lt;code&gt;curl&lt;/code&gt; is installed on these devices, greatly simplifying things.&lt;/p&gt;
&lt;h2 id="syntax"&gt;Syntax&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# gmail specific&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl --url &lt;span class="s1"&gt;&amp;#39;smtps://smtp.gmail.com:587&amp;#39;&lt;/span&gt; --ssl-reqd &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--mail-from &lt;span class="s1"&gt;&amp;#39;username@gmail.com&amp;#39;&lt;/span&gt; --mail-rcpt &lt;span class="s1"&gt;&amp;#39;rcpt@email.com&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--user &lt;span class="s1"&gt;&amp;#39;username@gmail.com:password&amp;#39;&lt;/span&gt; --insecure &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--upload-file fileToBeUploaded.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s break down each component:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--url&lt;/code&gt; - specifies the SMTP server address and port&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--ssl-reqd&lt;/code&gt; - mandates SSL/TLS must be used&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--mail-from&lt;/code&gt; - who sent the email&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--mail-rcpt&lt;/code&gt; - the addressee&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--user&lt;/code&gt; - credentials for the mail server&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--insecure&lt;/code&gt; - a &lt;code&gt;curl&lt;/code&gt; idiom that tells &lt;code&gt;curl&lt;/code&gt; to ignore CA errors and continue&lt;/li&gt;
&lt;li&gt;&lt;code&gt;upload-file&lt;/code&gt; - &lt;code&gt;curl&lt;/code&gt; cannot send a body, but will accept an &lt;a href="https://tools.ietf.org/html/rfc5322.htm"&gt;RFC 5322&lt;/a&gt; compliant text file as its message. Json, HTML and text data can be sent by adding a &lt;code&gt;Content-Type&lt;/code&gt; header appropriate to the type of data being sent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Curl &lt;a href="https://ec.haxx.se/usingcurl-smtp.html"&gt;documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="bash-script"&gt;Bash script&lt;/h2&gt;
&lt;p&gt;A simple example bash script to illustrate some of the potential that this curl functionality provides us. The code can be found &lt;a href="https://github.com/danielmichaels/databank/blob/master/utils/mailer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/bash.png" alt="" title="bash icon"&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# A simple email script that takes a recipient and&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# RFC5322 compliant text file as the message body.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Curl auto-emailer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;SUPPORTS GMAIL ONLY&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;RCPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#39;username@example.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="c1"&gt;# takes a filename, relative or abs path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;FROM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server@gmail.com &lt;span class="c1"&gt;# &amp;#39;me@me.com&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server@gmail.com:p@ssword &lt;span class="c1"&gt;# &amp;#39;username@gmail.com:password&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Missing argument 1; recipient address\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; eg username@gmail.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Missing argument; File to be uploaded&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl --url &lt;span class="s1"&gt;&amp;#39;smtp://smtp.gmail.com:587&amp;#39;&lt;/span&gt; --ssl-reqd &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --mail-from &lt;span class="nv"&gt;$FROM&lt;/span&gt; --mail-rcpt &lt;span class="nv"&gt;$RCPT&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --user &lt;span class="nv"&gt;$EMAIL&lt;/span&gt; --upload-file &lt;span class="nv"&gt;$FILE&lt;/span&gt; --insecure
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is for demonstration purposes only. Also, its worth mentioning that entering credentials in the commandline without the appropriate safe guards can be a security issue!&lt;/p&gt;
&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;Check out &lt;code&gt;curl wttr.in&lt;/code&gt; and &lt;code&gt;curl rates.sx&lt;/code&gt; in your terminal for some of the more interactive things that can be done with this great tool.
The possibilities of &lt;code&gt;curl&lt;/code&gt; are nearly endless and its used by many of us everyday.&lt;/p&gt;
&lt;p&gt;If you want to learn more about sending emails from the commandline then &lt;a href="https://blog.edmdesigner.com/send-email-from-linux-command-line/"&gt;this&lt;/a&gt; might be worth a read. More information about sending HTML RFC5322 complaint message body files can be found &lt;a href="https://blog.edmdesigner.com/send-email-from-linux-command-line/#sendingemailusingcurlcommand"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>HTTP Methods Primer</title><link>https://danielms.site/blog/http-basic-methods-and-verbs/</link><pubDate>Thu, 11 Jul 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/http-basic-methods-and-verbs/</guid><description>&lt;h1 id="http-101"&gt;HTTP 101&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/http-verbs.png" alt="" title="Fetching a page across the internet"&gt;&lt;/p&gt;
&lt;h2 id="prefetch"&gt;Prefetch&lt;/h2&gt;
&lt;p&gt;What happens when you type an address into the search bar and hit enter:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The browser extracts the domain name from the search bar.&lt;/li&gt;
&lt;li&gt;The users Domain Name System (DNS) cache is queried. If no previous entries are found a DNS request is sent via your configured DNS server.&lt;/li&gt;
&lt;li&gt;After getting the IP address via DNS, the host will initiate a TCP connection with that address.&lt;/li&gt;
&lt;li&gt;If the connection is successful, a response will be sent from the server and its data will be rendered using HTTP.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a more detailed explanation of DNS resolution in step 2, check out my &lt;a href="https://danielms.site/blog/dns-the-easy-parts/"&gt;post&lt;/a&gt; on the subject.&lt;/p&gt;
&lt;h2 id="http"&gt;HTTP&lt;/h2&gt;
&lt;p&gt;Hyper-Text Transfer Protocol (HTTP) is an application layer protocol for communications between a client and web server. It operates on a request-response model with the client initiating any connection to the server.&lt;/p&gt;
&lt;h2 id="http-methods"&gt;HTTP Methods&lt;/h2&gt;
&lt;p&gt;Each request starts with a HTTP method which defines the request semantics, indicating the purpose of the clients request and what the client expects in a successful response. More succinctly, the method tells the server what sort of data it is sending or wants to receive. The server can then respond appropriately to the request.&lt;/p&gt;
&lt;p&gt;The nine methods available are listed below.&lt;/p&gt;
&lt;h3 id="get"&gt;&lt;strong&gt;GET&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;The most common request, it broadly asks the remote host to send the requested data back to the client. In general, &lt;code&gt;GET&lt;/code&gt; requests will retrieve the data from a Uniform Request Identifier (URI).&lt;/p&gt;
&lt;p&gt;Responses to a &lt;code&gt;GET&lt;/code&gt; request are cacheable, meaning a cache can elect to use the it on subsequent &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; requests.&lt;/p&gt;
&lt;h3 id="head"&gt;&lt;strong&gt;HEAD&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Very similar to the &lt;code&gt;GET&lt;/code&gt; request except that it only retrieves the header information. All body content is omitted.&lt;/p&gt;
&lt;p&gt;Responses to a &lt;code&gt;HEAD&lt;/code&gt; request are cacheable, meaning a cache can elect to use the it on subsequent &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;HEAD&lt;/code&gt; requests.&lt;/p&gt;
&lt;h3 id="post"&gt;&lt;strong&gt;POST&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;POST&lt;/code&gt; request will typically be sent via a HTML form and will effect some change on the server. According the &lt;a href="https://tools.ietf.org/html/rfc7231#section-4.3.://tools.ietf.org/html/rfc7231#section-4.3.3"&gt;specification&lt;/a&gt; it is indicated by the &lt;code&gt;Content-Type&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;Common &lt;code&gt;POST&lt;/code&gt; actions are things like adding new users, or payment processing. The &lt;code&gt;POST&lt;/code&gt; sends that information to the server for it to be processed and declares the scheme it will use to send the data. The server needs this information so it knows how to handle the request.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Schemes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;multipart/form-data&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;application/json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;POST /payments HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host: bank.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Type: application/x-www-form-urlencoded
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;34&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;accountNumber&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;123&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;9999&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;toAccount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;789&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Successful &lt;code&gt;POST&lt;/code&gt; requests should get a &lt;code&gt;201 Created&lt;/code&gt; response and a &lt;code&gt;Location&lt;/code&gt; header pointing to the data&amp;rsquo;s new URI.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;201&lt;/span&gt; Created
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Location: /payments/&amp;lt;ID&amp;gt;/&amp;lt;transactionID&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="put"&gt;&lt;strong&gt;PUT&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Similar to a &lt;code&gt;POST&lt;/code&gt; request, a &lt;code&gt;PUT&lt;/code&gt; references an already existing entity, and requests a update to it. The server must verify the validity of the data to be updated, and is not obligated to action the request.&lt;/p&gt;
&lt;p&gt;Every &lt;code&gt;PUT&lt;/code&gt; is &lt;a href="https://www.youtube.com/watch?v=UaKZ4wKytcA"&gt;idempotent&lt;/a&gt; whereas &lt;code&gt;POST&lt;/code&gt; requests are not. Each &lt;code&gt;POST&lt;/code&gt; will have an updating effect on the data. For instance, repeatedly sending a &lt;code&gt;POST&lt;/code&gt; on endpoints like payment processing could see that payment processed as many times as the request was sent.
This is one of the differences between the two at the HTTP level. But, the most important difference is how the each request interacts with the URI it is requesting.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;PUT&lt;/code&gt; request must know the exact URI it intends to influence.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Updates an existing record &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# OR adds a record to that exact URI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PUT /api/v1/books/3452 HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host: https://library-store.com/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Should return a &lt;code&gt;200 OK&lt;/code&gt; or in some cases a &lt;code&gt;201 Created&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer"&gt;REST&lt;/a&gt; API&amp;rsquo;s usually adhere to the correct use of &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; requests. A URI such as above is a good indication of a REST conforming web application.&lt;/p&gt;
&lt;h3 id="patch"&gt;&lt;strong&gt;PATCH&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Unlike &lt;code&gt;PUT&lt;/code&gt; which will replace an existing entity, &lt;code&gt;PATCH&lt;/code&gt; only modifies a portion of it. This makes it an non-idempotent method. Similar to &lt;code&gt;POST&lt;/code&gt;, it will make alterations or add data for each request. However, a &lt;code&gt;PATCH&lt;/code&gt; can be made idempotent - it depends on context and the data being modified.&lt;/p&gt;
&lt;p&gt;This method is relatively new in comparison to its siblings, having been introduced as early as &lt;a href="https://tools.ietf.org/html/rfc2616"&gt;RFC 2616&lt;/a&gt; but only being ratified in &lt;a href="https://tools.ietf.org/html/rfc5789"&gt;RFC 5789&lt;/a&gt; circa 2010.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;PATCH /file.txt HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host: www.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Type: application/example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;If-Match: &lt;span class="s2"&gt;&amp;#34;e0023aa4e&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Length: &lt;span class="m"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A successful response generally yields a &lt;code&gt;204 No Content&lt;/code&gt; as no message body is present. A &lt;code&gt;200 OK&lt;/code&gt; requires a message body. Other codes can be configured by the server if required.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;204&lt;/span&gt; No Content
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Location: /file.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ETag: &lt;span class="s2"&gt;&amp;#34;e0023aa4f&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note how the &lt;code&gt;If-Match&lt;/code&gt; and &lt;code&gt;ETag&lt;/code&gt; data match. This will be covered in a later post.&lt;/p&gt;
&lt;h3 id="delete"&gt;&lt;strong&gt;DELETE&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Is a request to the server that calls a function capable of deleting a specified entity. The server is responsible for checking and carrying out the function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DELETE /user/2341 HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Should return a &lt;code&gt;202 Accepted&lt;/code&gt;, &lt;code&gt;204 No Content&lt;/code&gt; or &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="trace"&gt;&lt;strong&gt;TRACE&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;An uncommon request that is often seen during diagnostics such as Traceroute. Can see what the server was delivered.&lt;/p&gt;
&lt;p&gt;Often this method is disabled as its an attack vector for &lt;a href="https://www.owasp.org/index.php/Cross_Site_Tracing"&gt;Cross Site Tracing&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="connect"&gt;&lt;strong&gt;CONNECT&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;For use with a proxy to initiate the connection.&lt;/p&gt;
&lt;h3 id="options"&gt;&lt;strong&gt;OPTIONS&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;A utility call that asks the server which HTTP methods are supported.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;allow&lt;/code&gt; header shows which options are authorized. Some applications prohibit the &lt;code&gt;OPTIONS&lt;/code&gt; method and will return a &lt;code&gt;4XX&lt;/code&gt; status code.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/2 &lt;span class="m"&gt;200&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;allow: OPTIONS, GET, HEAD, POST
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cache-control: max-age&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;604800&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-type: text/html&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;date: Sun, &lt;span class="m"&gt;14&lt;/span&gt; Jul &lt;span class="m"&gt;2019&lt;/span&gt; 05:17:14 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;expires: Sun, &lt;span class="m"&gt;21&lt;/span&gt; Jul &lt;span class="m"&gt;2019&lt;/span&gt; 05:17:14 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;server: EOS &lt;span class="o"&gt;(&lt;/span&gt;vny006/044F&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;content-length: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Digging into the HTTP specifications and RFC&amp;rsquo;s has not been wasted effort. They underpin the Internet and all associated technologies that power it. Learn some for great good.&lt;/p&gt;</description></item><item><title>HTTP Security Basics</title><link>https://danielms.site/blog/http-security-basics/</link><pubDate>Sat, 06 Jul 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/http-security-basics/</guid><description>&lt;h1 id="http-basics"&gt;HTTP Basics&lt;/h1&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/httpie.png" alt="" title="http put image output"&gt;&lt;/p&gt;
&lt;p&gt;HTTP is an application level message based model where the client sends a request and the server returns a response. HTTP uses a stateful protocol - TCP - but is &lt;em&gt;stateless&lt;/em&gt;, where each request is connectionless avoiding the need for servers to hold an open connection.&lt;/p&gt;
&lt;h2 id="http-requests"&gt;HTTP Requests&lt;/h2&gt;
&lt;p&gt;The first line of every HTTP request has three (3) items, separated by spaces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The verb - &lt;code&gt;GET&lt;/code&gt;,&lt;code&gt;PUT&lt;/code&gt;,&lt;code&gt;DELETE&lt;/code&gt;,&lt;code&gt;POST&lt;/code&gt;,&lt;code&gt;TRACE&lt;/code&gt;,&lt;code&gt;CONNECT&lt;/code&gt;,&lt;code&gt;OPTIONS&lt;/code&gt;, or &lt;code&gt;HEAD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The requested URL, typically the name of the resource plus an optional query string. Query strings are noted by the &lt;code&gt;?&lt;/code&gt; eg. &lt;code&gt;/auth/448/yourdetails.ashx?uid=345&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The HTTP version in use. Most common is 1.1 and will look like this &lt;code&gt;HTTP/1.1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Example request&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host: www.w3.org
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;User-Agent: Mozilla/5.0 &lt;span class="o"&gt;(&lt;/span&gt;X11&lt;span class="p"&gt;;&lt;/span&gt; Linux x86_64&lt;span class="p"&gt;;&lt;/span&gt; rv:60.0&lt;span class="o"&gt;)&lt;/span&gt; Gecko/20100101 Firefox/60.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Accept: text/html,application/xhtml+xml,application/xml&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nv"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.9,*/*&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nv"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Accept-Language: en-US,en&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nv"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Accept-Encoding: gzip, deflate, br
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Referer: https://duckduckgo.com/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DNT: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Connection: keep-alive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Upgrade-Insecure-Requests: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="http-responses"&gt;HTTP Responses&lt;/h2&gt;
&lt;p&gt;Like the request, each responses first line will return three items:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The HTTP version in use,&lt;/li&gt;
&lt;li&gt;The status code - &lt;code&gt;200&lt;/code&gt;, &lt;code&gt;302&lt;/code&gt; and &lt;code&gt;404&lt;/code&gt; being most common,&lt;/li&gt;
&lt;li&gt;The response &amp;ldquo;phrase&amp;rdquo; that further describes the status of the response - legacy and not required by browsers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Responses can contain many more items after the first line, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Server&lt;/code&gt;: This may contain information about the server such as what engine it is running or modules installed. It may or may not be correct.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Set-Cookie&lt;/code&gt;: Will set a cookie for future use, and it will be submitted via the &lt;code&gt;Cookie&lt;/code&gt; header in future requests.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Pragma&lt;/code&gt;: Can be used to instruct the browser not the store the response in its cache. Likewise the &lt;code&gt;Expires&lt;/code&gt; header says when the is set to cache expire, and if expired it won&amp;rsquo;t load from the cache.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Type&lt;/code&gt;: Almost all responses will contain a message body after the headers (separated by a single blank line). The &lt;code&gt;Content-Type&lt;/code&gt; header indicates what is in the message body.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Length&lt;/code&gt;: The total size of the response in bytes.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Example response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP/1.1 &lt;span class="m"&gt;200&lt;/span&gt; OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Date: Mon, &lt;span class="m"&gt;18&lt;/span&gt; Jun &lt;span class="m"&gt;2018&lt;/span&gt; 04:29:32 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Server: Apache/2.2.22 &lt;span class="o"&gt;(&lt;/span&gt;Debian&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Location: rfc7230.html
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Vary: negotiate,Accept-Encoding
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;TCN: choice
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Last-Modified: Sun, &lt;span class="m"&gt;17&lt;/span&gt; Jun &lt;span class="m"&gt;2018&lt;/span&gt; 07:19:43 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ETag: &lt;span class="s2"&gt;&amp;#34;3c9fe1-417e4-56ed145dec1c0;56ee301a89430&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Accept-Ranges: bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cache-Control: max-age&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;604800&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Expires: Mon, &lt;span class="m"&gt;25&lt;/span&gt; Jun &lt;span class="m"&gt;2018&lt;/span&gt; 04:29:32 GMT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Encoding: gzip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Strict-Transport-Security: max-age&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Frame-Options: SAMEORIGIN
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Xss-Protection: 1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;block
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;X-Clacks-Overhead: GNU Terry Pratchett
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Keep-Alive: &lt;span class="nv"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5, &lt;span class="nv"&gt;max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Connection: Keep-Alive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Transfer-Encoding: chunked
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Content-Type: text/html&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;UTF-8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="http-methods"&gt;HTTP Methods&lt;/h3&gt;
&lt;h4 id="get"&gt;GET&lt;/h4&gt;
&lt;p&gt;The most common method. When clicking on a hyperlink, you are likely sending a &lt;code&gt;GET&lt;/code&gt; request. &lt;code&gt;GET&lt;/code&gt; requests can also be used to send information via a URL query string such as &lt;code&gt;?&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;http://example.com/over/there?name&lt;span class="o"&gt;=&lt;/span&gt;ferret
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;URL&amp;rsquo;s are displayed on screen and also logged in places such as browser history and web access logs. They are also transmitted via the &lt;code&gt;Referrer&lt;/code&gt; header when clicking on an link that takes you an external resource. In such an example sensitive information in a query string would be logged in an external servers logs. Further, these strings (if unencrypted, or SSLstripped) could be sniffed by a malicious actor. Never use &lt;code&gt;GET&lt;/code&gt; requests for transmission of sensitive data.&lt;/p&gt;
&lt;h4 id="post"&gt;POST&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;POST&lt;/code&gt; is used to submit data to a resource. A &lt;code&gt;POST&lt;/code&gt; can submit this information via the URL query string, or via the message body (preferred method, imo). If the &lt;code&gt;POST&lt;/code&gt; is coupled with URL query strings, any bookmarking would remove the query from the URL, in an attempt to provide security. Using this method also ensures that only one request is sent with that information at a time. That is, if a user submits a &lt;code&gt;POST&lt;/code&gt; request and then hits the &amp;lsquo;back&amp;rsquo; button, they will likely see a prompt stating something along the lines of &amp;lsquo;having to resubmit&amp;rsquo;. This ensures that the user can only submit the &lt;code&gt;POST&lt;/code&gt; once per action, as the browser will not automatically reissue the request - unlike a &lt;code&gt;GET&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="head"&gt;HEAD&lt;/h4&gt;
&lt;p&gt;This functions in the same manner as a &lt;code&gt;GET&lt;/code&gt; except it only returns the header information.&lt;/p&gt;
&lt;h4 id="trace"&gt;TRACE&lt;/h4&gt;
&lt;p&gt;Used for diagnostic purposes only. The server should return in the response body the exact contents that it received. Often used to diagnose issues and check if proxies are working correctly/ intercepting data unexpectedly. Commonly disallowed option returning &lt;code&gt;405 Method Not Allowed&lt;/code&gt;&lt;/p&gt;
&lt;h4 id="options"&gt;OPTIONS&lt;/h4&gt;
&lt;p&gt;Returns what HTTP methods are available for a particular resource.&lt;/p&gt;
&lt;h4 id="put"&gt;PUT&lt;/h4&gt;
&lt;p&gt;Allows uploading of resources to the server. Used by API&amp;rsquo;s to update existing records or data. Can provide the attacker with an avenue to upload malicious scripts to the server for execution.&lt;/p&gt;
&lt;h2 id="uniform-resource-locator-url"&gt;Uniform Resource Locator (URL)&lt;/h2&gt;
&lt;p&gt;URL&amp;rsquo;s are unique identifiers for web resources.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Example URI specification from RFC 3986&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; foo://example.com:8042/over/there?name&lt;span class="o"&gt;=&lt;/span&gt;ferret#nose
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="se"&gt;\_&lt;/span&gt;/ &lt;span class="se"&gt;\_&lt;/span&gt;_____________/&lt;span class="se"&gt;\_&lt;/span&gt;________/ &lt;span class="se"&gt;\_&lt;/span&gt;________/ &lt;span class="se"&gt;\_&lt;/span&gt;_/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; scheme authority path query fragment
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;|&lt;/span&gt; _____________________&lt;span class="p"&gt;|&lt;/span&gt;__
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; / &lt;span class="se"&gt;\ &lt;/span&gt;/ &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; urn:example:animal:ferret:nose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Within the &lt;code&gt;authority&lt;/code&gt; a port number is delimited by &lt;code&gt;:&lt;/code&gt;, this is optional and generally only included if not default for the protocol in use.&lt;/p&gt;
&lt;h3 id="rest"&gt;REST&lt;/h3&gt;
&lt;p&gt;Representational State Transfer (ReST) is a style of architecture for distributed systems in which requests and responses contain representations of the current state of the systems resources.
What is means is that RESTful applications will have their parameters within the path, rather than in the query string.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Non-RESTful&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://example.com/search?manuf=samsung&amp;amp;model=galaxy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RESTful&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://example.com/search/samsung/galaxy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;See &lt;a href="https://en.wikipedia.org/wiki/Representational_state_transfer"&gt;here&lt;/a&gt; for more information.&lt;/p&gt;
&lt;h3 id="http-headers"&gt;HTTP Headers&lt;/h3&gt;
&lt;p&gt;Some interesting and important headers:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;General Headers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Connection&lt;/code&gt;: Lets the other end know whether to shutdown communication or keep it open for future messages. Defaults to &lt;code&gt;keep-alive&lt;/code&gt; in HTTP/1.1 but &lt;em&gt;must not&lt;/em&gt; be used in HTTP/2 as header metadata is handled differently due to HPACK.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Encoding&lt;/code&gt;: Specifies what encoding is used. &lt;code&gt;gzip&lt;/code&gt; being very common as it compresses the data for faster transmission.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Length&lt;/code&gt;: Provides the message body length in bytes.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Content-Type&lt;/code&gt;: What type of contents are in the message body such as &lt;code&gt;text/html&lt;/code&gt;, or &lt;code&gt;application/json&lt;/code&gt;. Very important during &lt;code&gt;POST&lt;/code&gt; requests in determining the type of data that will be sent in the body, and how the server should expect to parse it. One type is &lt;code&gt;multipart/form-data&lt;/code&gt; which will set a &lt;code&gt;boundary=&lt;/code&gt; string, allowing the server to accept each field as a new &lt;em&gt;part&lt;/em&gt; and separate them according to the boundary defined in the &lt;code&gt;Content-Type&lt;/code&gt;. Another method is the &lt;code&gt;application/x-www-form-urlencode&lt;/code&gt; type which will place the data from the form into the body of the message, rather than in the query string.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Transfer-Encoding&lt;/code&gt;: If any encoding has been done on the message body, typically seen when chunking has been performed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Request Headers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Accept&lt;/code&gt;: Tells the server what kind of content the client is willing to accept, such as image types etc.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Accept-Encoding&lt;/code&gt;: What encoding types the server will accept.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Authorization&lt;/code&gt;: Submission of credentials to the server for one the built-in HTTP authentication types.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cookie&lt;/code&gt;: Submits cookies that the server has previously issued.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Host&lt;/code&gt;: Hostname that appeared in the requested URL.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;If-Modified-Since&lt;/code&gt;: When the browser last received the requested resource. If no changes have happened since then the server may issue a 304 status code telling it to render from its cached copy. Compared against &lt;code&gt;Last-Modified&lt;/code&gt; and determines the cached variant is older or newer than the current resource.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;If-None-Match&lt;/code&gt;: A sort of hash that the server issues to the browser which it checks to see if is valid prior to telling the browser to render from cache. This specifies an &lt;em&gt;entity tag&lt;/em&gt; and it is the &lt;em&gt;ETag&lt;/em&gt; that is validated. It cares only if the information is identical or not and takes precedence or &lt;code&gt;If-Modified-Since&lt;/code&gt; as the &lt;code&gt;ETag&lt;/code&gt; is considered stronger validator. See &lt;a href="https://www.rfc-editor.org/rfc/rfc7232.txt"&gt;RFC 7232&lt;/a&gt; for more information.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Referer&lt;/code&gt;: From which URL this request has originated.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;User-Agent&lt;/code&gt;: Information about the browser or application making this request.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Response Headers&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;: Whether this resource can be retrieved via a cross-domain request.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cache-Control&lt;/code&gt;: Passes caching directives to the browser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ETag&lt;/code&gt;: The entity tag submitted to server. A special header that specifies the version of a resource. Used to verify if the server needs to send a full response, or if the client can render a cached copy. Each time a resource changes, and new &lt;code&gt;ETag&lt;/code&gt; value must be generated.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Expires&lt;/code&gt;: How long the contents of the message body are valid for. Cached copies may be used until this time.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Location&lt;/code&gt;: Used in redirect responses (3XX) to specify the target of the redirect.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Pragma&lt;/code&gt;: Passes caching directives to the browser.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Server&lt;/code&gt;: Information about the server&amp;rsquo;s software.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Set-Cookie&lt;/code&gt;: Issues cookies to the browser which it will send back to the server in subsequent requests.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WWW-Authenticate&lt;/code&gt;: Provides details on types of authentication that server supports after 401 status code.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;X-Frame-Options&lt;/code&gt;: Whether responses can be loaded within browsers frame and how to do it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cookies"&gt;Cookies&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/cookies.png" alt="" title="cookie output request headers"&gt;&lt;/p&gt;
&lt;p&gt;Many web applications rely on cookies as allow servers to send data to the client, and then have the client return that data in its transmissions within the server. Since HTTP is stateless, cookies allow functionality to persist over time. Login data, shopping carts and site settings all rely on cookies for a better user experience.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Example of setting a cookie&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cookie: &lt;span class="nv"&gt;tracking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dw34tad23590ddfsawWcsTs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cookies are usually made up of a key value pair but may consist of any string that does not contain a space. A server can send more than one &lt;code&gt;Set-Cookie&lt;/code&gt; per response by using a semi-colon to separate the cookies.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Set-Cookie&lt;/code&gt; can also include optional attributes such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;expires&lt;/code&gt;: The date at which the cookie will become invalid. This means that cookies are stored by the browser on disk and may be reused repeatedly until that date. If no expires is set, it will only remain valid for the current browser session.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;domain&lt;/code&gt;: Which domain the cookie is valid for.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;secure&lt;/code&gt;: Whether that cookie must be sent via HTTPS or not.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HttpOnly&lt;/code&gt;: if this is it set, it prevents javascript from accessing it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="status-codes"&gt;Status Codes&lt;/h3&gt;
&lt;p&gt;Five (5) groupings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1xx&lt;/code&gt; - Informational.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2xx&lt;/code&gt; - Request successful.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3xx&lt;/code&gt; - Redirection.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;4xx&lt;/code&gt; - Request error of some type.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;5xx&lt;/code&gt;- Server error of some type.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most common status codes are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;100 continue&lt;/code&gt;: Sometimes sent when a server receives a request containing a body. It means that the headers were received and the client should continue sending the message. Once all is received a further response will be sent.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;200 OK&lt;/code&gt;: Successfully received.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;201 Created&lt;/code&gt;: The request was successful and a new resource was created. Typically the result of &lt;code&gt;POST&lt;/code&gt; and &lt;code&gt;PUT&lt;/code&gt; requests.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;301 Moved Permanently&lt;/code&gt;: Redirection to the new URL of the requested resource. A &lt;code&gt;Location&lt;/code&gt; header will specify the new resource location and that it should use that in future.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;302 Found&lt;/code&gt;: Redirection to a temporary URL, again specified in the &lt;code&gt;Location&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;304 Not Modified&lt;/code&gt;: Instruction to browser saying it should use its cached copy of the requested resource. &lt;code&gt;If-Modified-Since&lt;/code&gt; and &lt;code&gt;If-None-Match&lt;/code&gt; headers are sent to determine if client has most recent copy.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;400 Bad Request&lt;/code&gt;: Invalid HTTP request was received.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;401 Unauthorized&lt;/code&gt;: Invalid credentials or none were supplied. A reply with &lt;code&gt;WWW-Authenticate&lt;/code&gt; headers will be sent stating what authentication types are supported.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;403 Forbidden&lt;/code&gt;: No one is allowed access.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;404 Not Found&lt;/code&gt;: Resource does not exist.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;405 Method Not Allowed&lt;/code&gt;: Method used is not supported by the specified URL. Both &lt;code&gt;HEAD&lt;/code&gt; and &lt;code&gt;GET&lt;/code&gt; must never be disabled, and should not return this code.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;413 Request Entity Too Large&lt;/code&gt;: Some endpoints or browsers will limit the string length - usually seen in buffer overflow attacks.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;414 Request URI Too Long&lt;/code&gt;: Same as above.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;500 Internal Server Error&lt;/code&gt;: The server hit some error processing your request. A good hint that you are trying to exploit something it is not configured to handle properly. Always try and identify the nature of this error.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;503 Service Unavailable&lt;/code&gt;: An indication that the server is responding but the application is not.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="state-and-sessions"&gt;State and Sessions&lt;/h3&gt;
&lt;p&gt;Although HTTP is stateless there is a requirement for the client and server to process long running requests. An example of this is a sites Shopping Cart. A user can browse and add items to their cart whilst making several requests over a period of time. To make this possible the server maintains a User Session, which allows it to track the users actions across the site.&lt;/p&gt;
&lt;p&gt;In some cases, state information is stored on the client side. Instead of the server storing the session, now the data is sent to the client in each server response and the client will send it back in each request. It is important that the server does not trust the client, as it may alter the session information for nefarious reasons. One mitigation is using a hash of the state, which the server validates before accepting the clients session data.&lt;/p&gt;
&lt;p&gt;Given the stateless nature of HTTP, applications need a method in which to re-identify clients. Typically, this is achieved through the use of tokens - a unique identifier for each user such as cookie.&lt;/p&gt;
&lt;h3 id="encoding-schemes"&gt;Encoding Schemes&lt;/h3&gt;
&lt;p&gt;HTTP has several encoding schemes to ensure the safe delivery of data. The following is some of the most common.&lt;/p&gt;
&lt;h4 id="url-encoding"&gt;URL Encoding&lt;/h4&gt;
&lt;p&gt;URL&amp;rsquo;s can only contain characters from the US-ASCII set; 0x20 through to 0x7e. Several characters within this set are restricted as they have special meaning in the scheme or HTTP.
All URL encoded characters are prefixed by &lt;code&gt;%&lt;/code&gt; followed by their hexadecimal representation of the ASCII character.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%3d&lt;/code&gt; — =&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%25&lt;/code&gt; — %&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%0a&lt;/code&gt; — New Line (\n)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%00&lt;/code&gt; — Null Byte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;note&lt;/strong&gt;: &lt;code&gt;+&lt;/code&gt; represents the &amp;lsquo;space&amp;rsquo; in a URL.&lt;/p&gt;
&lt;h4 id="unicode-encoding"&gt;Unicode Encoding&lt;/h4&gt;
&lt;p&gt;Unicode is the system that allows us to use many different character sets. For English speakers, &lt;code&gt;utf-8&lt;/code&gt; will be the most common, but many more exists.
Encoding 16-bit unicode in a URL requires the prefix &lt;code&gt;%u&lt;/code&gt; followed by the hexadecimal.&lt;/p&gt;
&lt;p&gt;Example unicode&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%u2215&lt;/code&gt; — /&lt;/li&gt;
&lt;li&gt;&lt;code&gt;u00e9&lt;/code&gt; — é&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UTF-8 is a variable length encoding that uses one or more bytes for each character. To send this using URL encoding, each byte is delimited by a &lt;code&gt;%&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;%c2%a9&lt;/code&gt; — ©&lt;/li&gt;
&lt;li&gt;&lt;code&gt;%e2%89%a0&lt;/code&gt; — ≠&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unicode is an important part of attacking web applications as it can defeat input validation schemes.&lt;/p&gt;
&lt;h4 id="html-encoding"&gt;HTML Encoding&lt;/h4&gt;
&lt;p&gt;Safe incorporation of HTML within web applications is important. Several HTML characters have special meaning and must be encoded correctly.
When attacking an application, HTML encoding will be most evident when probing for XSS vulnerabilities. If an application returns user input unmodified, it is likely vulnerable.&lt;/p&gt;
&lt;h4 id="base64"&gt;Base64&lt;/h4&gt;
&lt;p&gt;Base64 allows any binary data to be represented as using only ASCII characters. It permits the sending of ASCII strings over the wire for safe reassembly on the other side in the original format. It is also used heavily in basic HTTP user authentication.&lt;/p&gt;
&lt;p&gt;Base64 splits the bytes into 6bit streams, allowing for 64 possible permutations - each chunk of 6 bits allows for 64 possible characters. It allows only the following characters:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/+&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If the final block results in fewer than three chunks (16 bits or 2 bytes) then it will be marked by &lt;code&gt;=&lt;/code&gt; or &lt;code&gt;==&lt;/code&gt; which says that their may be one or two trailing blocks of zero&amp;rsquo;s.&lt;/p&gt;
&lt;p&gt;Base64 is prevalent across the web and is often used to transmit binary data within cookies and other parameters. It is often used to obfuscate data in transit through security by obscurity. Always decode any intercepted Base64 data, it could be a goldmine. Base64 can often be identified quickly by the &lt;code&gt;=&lt;/code&gt; trail, or if it is JSON it will start with &lt;code&gt;ey&lt;/code&gt; which represents &lt;code&gt;{&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="fin"&gt;Fin&lt;/h2&gt;
&lt;p&gt;HTTP is an important protocol not just for security and network engineers but for developers too. Having a cursory understanding aides every one who uses the web. Next time you look at the network tab in your browsers developer tools be sure to look at the headers, you might notice something worth exploring.&lt;/p&gt;</description></item><item><title>User Agents 101</title><link>https://danielms.site/blog/user-agents-101/</link><pubDate>Wed, 15 May 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/user-agents-101/</guid><description>&lt;h1 id="user-agents"&gt;User agents&lt;/h1&gt;
&lt;h2 id="what"&gt;What&lt;/h2&gt;
&lt;p&gt;Every browser sends with its request, a string that says who it is. That string will contain information on its application type, operating system, and software version currently in use.&lt;/p&gt;
&lt;p&gt;Heres mine: &lt;code&gt;Mozilla/5.0 (X11; Linux x86_64; rv:66.0) Gecko/20100101 Firefox/66.0&lt;/code&gt; but it could just as easily be &lt;code&gt;Mozilla/5.0 (PlayStation 4 4.71) AppleWebKit/601.2 (KHTML, like Gecko)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve ever been surfing the net on your mobile and selected &amp;ldquo;Request Desktop Site&amp;rdquo; then you have changed your phone browsers user agent. And, if you have done that you might have noticed a significant change in that sites resolution. Its all to do with that string of data, and what the website does with that information.&lt;/p&gt;
&lt;p&gt;Not only will each browser have its own user agent but so will any program used to query a web site. Google uses a series of &amp;ldquo;spiders&amp;rdquo; which crawl the internet and index it giving us Google search. Googlebot, cURL, wget, Postman and your latest smart light bulb all have user agents.&lt;/p&gt;
&lt;p&gt;You can find yours by typing &amp;ldquo;user agent&amp;rdquo; into &lt;a href="https://duckduckgo.com/?q=user+agent"&gt;DuckDuckGo&lt;/a&gt;. Or, you could open your browsers &lt;em&gt;Development Tools&lt;/em&gt; with &lt;code&gt;Ctrl + Shift I&lt;/code&gt; and then select the &lt;em&gt;Network&lt;/em&gt; tab. Once inside look for a subsection named &lt;em&gt;Headers&lt;/em&gt;. Your user agent will be located in the request headers section.&lt;/p&gt;
&lt;h2 id="why-do-i-need-to-know-this"&gt;Why do I need to know this&lt;/h2&gt;
&lt;p&gt;Knowing is half the battle. My bank once wouldn&amp;rsquo;t let me login unless I used Chrome or Edge which is ridiculous. I choose my browser not the web application, so I just changed the user agent, and wouldn&amp;rsquo;t you know, &lt;em&gt;&amp;hellip;I got in&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Maybe you are an app developer, or for some reason need to verify something by assessing the user agent. At least now you know that it isn&amp;rsquo;t a fool-proof way to confirm a device - you will need more than one method to ascertain the true origin. Don&amp;rsquo;t rely on user agents alone as you can see below.&lt;/p&gt;
&lt;p&gt;As a developer, or user if you repeatedly request a website it is possible to be served a &lt;code&gt;HTTP/1.1 429 Too Many Requests&lt;/code&gt;. It&amp;rsquo;s probable that your user agent in conjunction with your IP address has triggered that.&lt;/p&gt;
&lt;p&gt;Web scraping is a common programming task, and allows developers to access information from sites that do not offer an application programming interface (API).
Some pages offer API&amp;rsquo;s but a programmer may choose not to use it. Usually this is to circumvent having to pay for it, or maybe the API does not offer a certain, wanted piece of information. More often then not, scraping is in violation of the web applications terms of service. It&amp;rsquo;s best to utilise an API whenever possible.&lt;/p&gt;
&lt;p&gt;So how do people get around this?&lt;/p&gt;
&lt;h2 id="its-just-a-string"&gt;It&amp;rsquo;s just a string&lt;/h2&gt;
&lt;p&gt;Thankfully, HTTP is stateless (yes, even HTTP/2) and thus we can influence each request and its headers.&lt;/p&gt;
&lt;p&gt;We can demonstrate this with a simple script which will choose a user agent at random from a list and then send it with its request. Every language has the ability to get information from the internet and will likely already have a way to do the same thing. This example will be in python using the &lt;code&gt;requests&lt;/code&gt; library.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1. Set the user agents to be selected randomly.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_agent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;user_agents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (Windows NT 5.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_agents&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Step 2. Call the URL with our randomly selected user agents.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_ua&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://www.whatsmyua.info/api/v1/ua?=&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User-Agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user_agent&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ua&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ua&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rawUa&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ua&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We override the default &lt;code&gt;headers&lt;/code&gt; with our &lt;code&gt;user_agent&lt;/code&gt; function and are returned a response. In this case we&amp;rsquo;ve hit an endpoint that will return our user agent as a JSON object.&lt;/p&gt;
&lt;p&gt;The source for this script can be found &lt;a href="https://github.com/danielmichaels/utils/blob/master/user_agent_mixer.py"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So changing a user agent is that easy. Firefox has extensions, Chrome would, Opera has it built-in. PC, laptop or mobile handset its all the same after all its just HTTP.&lt;/p&gt;</description></item><item><title>LessPass: A Primer</title><link>https://danielms.site/blog/lesspass-eli5/</link><pubDate>Mon, 15 Apr 2019 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/lesspass-eli5/</guid><description>&lt;h1 id="lesspass"&gt;LessPass&lt;/h1&gt;
&lt;h2 id="the-manager-less-password-manager"&gt;The manager-less password manager&lt;/h2&gt;
&lt;p&gt;LessPass, unlike other password managers does not store any information in a database or local cache. It simply takes in three pieces of information and then hashes that to return a password. If all three pieces of information haven&amp;rsquo;t changed LessPass will always return the same password.&lt;/p&gt;
&lt;h2 id="why-use-it-over-other-choices"&gt;Why use it over other choices&lt;/h2&gt;
&lt;p&gt;Straight from the creator himself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How do I synchronize this file on all my devices?&lt;/li&gt;
&lt;li&gt;How do I access a password on my parents’ computer without installing my password manager?&lt;/li&gt;
&lt;li&gt;How do I access a password on my phone, without any installed app?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For some of these I have my own workarounds but they are hacks and not very robust. For instance, I upload my password database to a cloud service and then pull it down on to my phone. It&amp;rsquo;s a pain, not user friendly and exposes that database to a third-party vendor.&lt;/p&gt;
&lt;p&gt;LessPass is also Free as in free beer, open source and secure. It has a command line implementation, a web application, android app (sorry iOS) and browser extensions.&lt;/p&gt;
&lt;p&gt;I would also add another reason to use it, its easy and what is easy gets used. Good luck getting your dad to download an application that requires you to go to github, clone the repo and then run an install script or bat file. Even worse, good luck explaining to dad why 1Password is worth the 5 bucks a month or whatever it is. The smallest barriers to entry can be enough to be prohibitive when the user isn&amp;rsquo;t computer savvy.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;How does it work&lt;/h2&gt;
&lt;p&gt;LessPass returns a unique password that it derives from three (or four, more on that later) pieces of information; &lt;code&gt;site&lt;/code&gt;, &lt;code&gt;login&lt;/code&gt; and a &lt;code&gt;master password&lt;/code&gt;.
If they are same it will always return the same output, a great example of idempotence or a &lt;em&gt;pure function&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/lesspass-hash.png" alt="alt text" title="LessPass image showing how hashing works."&gt;&lt;/p&gt;
&lt;p&gt;An image from the creator&amp;rsquo;s Medium &lt;a href="https://blog.lesspass.com/lesspass-how-it-works-dde742dd18a4"&gt;post&lt;/a&gt; giving a great visual representation of what is happening under the hood.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/lesspass.gif" alt="alt text" title="LessPass fullmotion gif of the app in action."&gt;&lt;/p&gt;
&lt;p&gt;The &amp;lsquo;how&amp;rsquo; of LessPass and it&amp;rsquo;s ease of use.&lt;/p&gt;
&lt;p&gt;It also allows the user to set password rules such as using capitalised letters, special characters and numbers. So if certain websites require that the user &lt;em&gt;not&lt;/em&gt; use special characters, LessPass can be configured for this. It defaults to 16 characters but can be set to a maximum of 32.&lt;/p&gt;
&lt;p&gt;You are also able to set whats called a &lt;code&gt;counter&lt;/code&gt; meaning if you do not want to change your &lt;code&gt;master password&lt;/code&gt; but are required to update your password you can issue a counter. It is an effective way to keep the three main pieces of information the same yet result in a new hash. Granted, this requires another level of cognitive load if you need to track several of these counters across sites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There must be a better way&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="lesspass-profiles"&gt;LessPass Profiles&lt;/h2&gt;
&lt;p&gt;The application also offers something called a &lt;code&gt;profile&lt;/code&gt; which allows you to store such information. This requires a sign up to LessPass which is free, or you can host your own &lt;a href="https://github.com/lesspass/lesspass"&gt;docker&lt;/a&gt; image for extra security. Each &lt;code&gt;site&lt;/code&gt; gets its own profile containing everything &lt;strong&gt;except&lt;/strong&gt; the &lt;code&gt;master password&lt;/code&gt; and the generated password.&lt;/p&gt;
&lt;p&gt;Each profile is stored as a &lt;code&gt;json&lt;/code&gt; object like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;login&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;danielmichaels&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;site&amp;#34;&lt;/span&gt;: &lt;span class="s2"&gt;&amp;#34;www.github.com&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;lowercase&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;uppercase&amp;#34;&lt;/span&gt;: false,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;symbols&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;numbers&amp;#34;&lt;/span&gt;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;counter&amp;#34;&lt;/span&gt;: 1,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;length&amp;#34;&lt;/span&gt;: &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="command-line-interface"&gt;Command Line Interface&lt;/h2&gt;
&lt;p&gt;I really like the CLI for LessPass - it suits my workflow by allowing me to set profiles on my machine via &lt;code&gt;alias&lt;/code&gt;. I refer to this as a &amp;lsquo;hardcoded&amp;rsquo; approach - it helps me none when I&amp;rsquo;m not on my box but I can just as easily hit lesspass.com for it in such a case so it&amp;rsquo;s hardly a show stopper. As example this is how I grab my github password.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;ghpw&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;lesspass -c github.com danielmichaels&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This drops me straight into a prompt for my &lt;code&gt;master password&lt;/code&gt; and then copies it to my clipboard. I could go further and set an environment variable but I have my dotfiles on github and would rather not accidentally expose such information.&lt;/p&gt;
&lt;h2 id="i-dont-need-a-password-manager"&gt;I Don&amp;rsquo;t Need A Password Manager&lt;/h2&gt;
&lt;p&gt;Said someone just waiting to get owned by a credential dump. We humans just simply cannot remember suitably difficult passwords that are hard for a computer to guess and easy for us to remember.&lt;/p&gt;
&lt;p&gt;Password manager public enemy number one is; if my master gets cracked they own everything. Yep thats true. If my house key gets cloned they&amp;rsquo;ve got unrestricted access to my house too. The mitigation lies in defeating the most likely avenue of exploitation and that is password attacks from credential dumps hoping to strike gold from someone that reuses their password on several sites. The bad guys are looking for the &amp;lsquo;path of least resistance&amp;rsquo;, just don&amp;rsquo;t be that path! Using a password manager and some common sense will go a long way to protecting yourself online. It&amp;rsquo;s not a panacea but its better than the alternative.&lt;/p&gt;
&lt;h2 id="meta"&gt;Meta&lt;/h2&gt;
&lt;p&gt;You can find out more and get LessPass from &lt;a href="https://lesspass.com"&gt;here&lt;/a&gt;. The python package can be downloaded with &lt;code&gt;pip install lesspass&lt;/code&gt; and both Chrome and Firefox extensions can be found in their respective stores. If you use LessPass be sure to give them a &lt;a href="https://github.com/lesspass/lesspass"&gt;star here&lt;/a&gt; on Github.&lt;/p&gt;</description></item><item><title>On teaching</title><link>https://danielms.site/blog/on-teaching/</link><pubDate>Sat, 15 Dec 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/on-teaching/</guid><description>&lt;h1 id="on-teaching"&gt;on teaching&lt;/h1&gt;
&lt;p&gt;Recently it has befallen upon me to tutor my peers in programming concepts and fundamentals. It struck me that I don&amp;rsquo;t know how to properly impart my knowledge. Where do I start, with strings, or variables. When do I talk about types and equality. Is it better to teach while or for loops first? Classes, OOP, recursion, comprehensions, async! What is important now versus what is the minimum necessary to give them the basics required to go away and learn on their own. In the end, I found it does not matter. Start, and progress from there. Listen to them, and discuss things; dont&amp;rsquo;t dictate. Pay attention to what they are asking, and try your best to make the experience relevant for them.&lt;/p&gt;
&lt;p&gt;They are there to learn from you because you know something they don&amp;rsquo;t. Questioning whether you are ready to teach is just self-doubt. Feeling like you are a fraud, or that you will be outted as a pretender is that doubt manifesting itself within you. We have to let go of these feelings and be ready to engage with the community. No one knows everything, and you aren&amp;rsquo;t teaching someone who alreadys knows what you know. It&amp;rsquo;s also okay for a &amp;ldquo;student&amp;rdquo; to teach you something too, they aren&amp;rsquo;t trying to make you feel foolish so you shouldn&amp;rsquo;t take offence, or let it feed that monster called self-doubt. We are always learning, both student and teacher, mentor and mentee.&lt;/p&gt;</description></item><item><title>Python's bytes and strings</title><link>https://danielms.site/blog/python-bytes-and-strings/</link><pubDate>Tue, 23 Oct 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/python-bytes-and-strings/</guid><description>&lt;p&gt;&lt;img src="https://danielms.site/images/unicode_logic.png" alt="test"&gt;&lt;/p&gt;
&lt;h1 id="python-strings-and-bytes"&gt;Python: strings and bytes&lt;/h1&gt;
&lt;p&gt;Python 3 is the new python, and python 2 should be regarded as legacy. But,
it is still being used heavily and will likely live beyond its end of life. Recently,
a project forced me to use python 2.7 within a Centos environment and I hit some
small encoding issues.&lt;/p&gt;
&lt;h2 id="character-sequences"&gt;Character Sequences&lt;/h2&gt;
&lt;p&gt;In python there are two types that represent sequences of characters:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python 3&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bytes: raw 8 bit values&lt;/li&gt;
&lt;li&gt;Strings: unicode characters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Python 2&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Strings: raw 8 bit values&lt;/li&gt;
&lt;li&gt;Unicode: unicode characters&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python 3&amp;rsquo;s strings and python 2&amp;rsquo;s unicode instances do not have binary encoding.
Therefore, the user must implement the conversion manually by &lt;code&gt;encoding&lt;/code&gt; and
&lt;code&gt;decoding&lt;/code&gt; the between binary and unicode.&lt;/p&gt;
&lt;p&gt;It is important that when constructing our program to deal with conversion between
these data types that it is done at the furthest boundaries of our interfaces.
This is sometimes referred to as the &amp;lsquo;unicode sandwich&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/unicode_sandwhich.jpg" alt="unicode sandwich"&gt;&lt;/p&gt;
&lt;p&gt;This guarantee&amp;rsquo;s a level of compliance when accepting alternate encodings and
a much stricter output encoding scheme - generally &lt;code&gt;utf-8&lt;/code&gt;. We should not be doing
any encoding or decoding in the middle of our applications.&lt;/p&gt;
&lt;p&gt;This is of particular importance when dealing with file operations that are not
text. In python 3, file operations do much of the encoding and decoding for you,
returning &lt;code&gt;str&lt;/code&gt; instances on read and write. But, python 3 will use the system
default encoding where an encoding scheme is not specified. Much of the time
we will receive &lt;code&gt;utf-8&lt;/code&gt; but on some machines this is not guaranteed.&lt;/p&gt;
&lt;p&gt;If your application is expected to work across multiple operating systems and/or
versions, you should set the &lt;code&gt;encoding=&amp;lt;your-encoding type&amp;gt;&lt;/code&gt; keyword argument
manually to prevent hidden bugs in the future.&lt;/p&gt;
&lt;p&gt;Subtle differences exist between python 2 and 3.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;encoding.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;w&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In python 2 this will work as by default &lt;code&gt;open&lt;/code&gt; use binary encoding. However, python 3
uses &lt;code&gt;utf-8&lt;/code&gt; instead. If we tried to run this it will raise a &lt;code&gt;TypeError: must be str not bytes&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To make this work in both 2 and 3, we need to specify that it is binary.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;encoding.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;wb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urandom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Writing binary to a file must also be specified with &lt;code&gt;rb&lt;/code&gt; instead of &lt;code&gt;r&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;encoding.txt&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rb&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="notable-mentions"&gt;Notable mentions&lt;/h3&gt;
&lt;p&gt;Other interesting points between python 2&amp;rsquo;s &lt;code&gt;str&lt;/code&gt; and &lt;code&gt;unicode&lt;/code&gt;, and python 3&amp;rsquo;s
&lt;code&gt;str&lt;/code&gt; and &lt;code&gt;bytes&lt;/code&gt; are how they are compared, or typed. In python 2, if a
&lt;code&gt;str&lt;/code&gt; is ASCII (7-bits) it will pass an equality check with &lt;code&gt;unicode&lt;/code&gt; characters.
They can also be combined using the &lt;code&gt;+&lt;/code&gt; operator, and can be formatted using
&lt;code&gt;%s&lt;/code&gt; string formatting.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;u&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="c1"&gt;# true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="c1"&gt;# u&amp;#39;aa&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;as str a,b =&lt;/span&gt;&lt;span class="si"&gt;%s%s&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# as str a,b=aa&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means that in python 2 we could pass &lt;code&gt;unicode&lt;/code&gt; or a &lt;code&gt;str&lt;/code&gt; instance and expect
the same result - if 7-bit ASCII. In python 3 &lt;code&gt;str&lt;/code&gt; and &lt;code&gt;bytes&lt;/code&gt; never cannot
pass equality checks, not even empty strings.&lt;/p&gt;
&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;Legacy python is still a thing, so having an understanding of how the encoding
paradigms differ between each version is important. The differences in file operations
can have significant effects within your applications, and is quite prominent in
network level communications. Wherever possible work with &lt;code&gt;str&lt;/code&gt; types inside
your main business logic to alleviate any nasty surprises further down the line.&lt;/p&gt;
&lt;p&gt;And have a read over some these gems regarding &lt;code&gt;utf-8&lt;/code&gt;, which I deliberately
did not go over in any detail.&lt;/p&gt;
&lt;h3 id="references"&gt;references&lt;/h3&gt;
&lt;p&gt;Ned Batchelder&amp;rsquo;s &lt;a href="https://nedbatchelder.com/text/unipain/unipain.html#1"&gt;How Do I Stop The Pain&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Joel Spolsky&amp;rsquo;s &lt;a href="https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/"&gt;The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Sqlite3: A Primer</title><link>https://danielms.site/blog/sqlite3-primer/</link><pubDate>Sun, 09 Sep 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/sqlite3-primer/</guid><description>&lt;h1 id="sql-reference"&gt;SQL Reference&lt;/h1&gt;
&lt;h2 id="sqlite3-specific"&gt;SQLite3 Specific&lt;/h2&gt;
&lt;h3 id="transactions"&gt;Transactions&lt;/h3&gt;
&lt;p&gt;SQLite transactions are ACID (Atomic, Consistent, Isolated and Durable). This means that data is not lost in the event of crashes, power failures or operating system dumps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Atomic&lt;/strong&gt;
- Meaning changes cannot be broken down into smaller parts; either the entire transaction is commit or nothing at all.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistent&lt;/strong&gt;
- A transaction must ensure to change the database from one valid state to another. If a transaction results in invalid data, the database will revert its previous state.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Isolated&lt;/strong&gt;
- Transactions are isolated from one client to the next. When changes are made to the database the changes are only visible to that client until it is committed. If two people concurrently make changes to a database, they will be transacted sequentially. It does not ensure the order of transactions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Durability&lt;/strong&gt;
- If a transaction is successfully committed, the changes are permanent going forward, if it is interrupted during the commit it will revert to its previous state.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/acid.svg" alt="Julia Evans ACID drawing" title="ACID in cartoon format"&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please check out Julia Evans work at &lt;a href="https://jvns.ca"&gt;https://jvns.ca&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;SQLite3 uses auto-commit mode by default.&lt;/p&gt;
&lt;h3 id="select"&gt;Select&lt;/h3&gt;
&lt;p&gt;Used to query data from one or more tables. Example below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-SQL" data-lang="SQL"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column_list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table_list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;join_condition&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;row_filter&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OFFSET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;offset&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;group_filter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A breakdown of each clause:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ORDER BY&lt;/code&gt; sorts the result set,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;DISTINCT&lt;/code&gt; gets unique rows in a table,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;WHERE&lt;/code&gt; is a filter such as, &lt;code&gt;WHERE employees IS NULL&lt;/code&gt;,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;LIMIT OFFSET&lt;/code&gt; will return a set number of rows,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;GROUP BY&lt;/code&gt; sorts columns into groups and applies functions to each group,&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;HAVING&lt;/code&gt; is another filter that checks for presence of the operator.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;SELECT * FROM database_table&lt;/code&gt; is acceptable usage in testing or development but in production the query should be explicit.&lt;/p&gt;
&lt;h3 id="insert"&gt;Insert&lt;/h3&gt;
&lt;p&gt;Used to insert new data be it single row, multiple rows, and default data into tables.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-SQL" data-lang="SQL"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;table1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,..)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,...),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,...),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;,...);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above example shows how to insert multiple rows into a table.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-SQL" data-lang="SQL"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;artists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Buddy Rich&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Candido&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Charlie Byrd&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An example of how to insert into column &lt;code&gt;name&lt;/code&gt; within table &lt;code&gt;artists&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Data can also be inserted via a SELECT statement.&lt;/p&gt;
&lt;h3 id="update"&gt;Update&lt;/h3&gt;
&lt;p&gt;Update already existing data in a table.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-SQL" data-lang="SQL"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column_1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new_value_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column_2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;new_value_2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;search_condition&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;column_or_expression&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;row_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OFFSET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;the &lt;code&gt;UPDATE&lt;/code&gt; clause informs SQL that some part of the table is going to be amended. the &lt;code&gt;SET&lt;/code&gt; clause signifies and allows the updating of the column/s that precede it. On the left of the assignment operator (=) is the column to be updated and on the right the new value, expression or data. Whilst the &lt;code&gt;WHERE&lt;/code&gt; operator is optional if it is omitted all rows will be updated.&lt;/p&gt;
&lt;h3 id="delete"&gt;Delete&lt;/h3&gt;
&lt;p&gt;The delete statement allows for deletion of one, multiple or all rows within a table.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-SQL" data-lang="SQL"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;search_condition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Like updating a table, &lt;code&gt;WHERE&lt;/code&gt;, &lt;code&gt;ORDER BY&lt;/code&gt; and &lt;code&gt;LIMIT&lt;/code&gt; clauses can be applied to a delete operation. In addition deletes can be orchestrated through the &lt;code&gt;SELECT&lt;/code&gt; operator.&lt;/p&gt;
&lt;h2 id="joins"&gt;JOINS&lt;/h2&gt;
&lt;h3 id="inner-joins"&gt;Inner Joins&lt;/h3&gt;
&lt;p&gt;In relation databases, data is often distributed amongst many related tables. Foreign Keys are used to associate these tables.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;INNER JOIN&lt;/code&gt; clause will combine columns from correlated tables.&lt;/p&gt;
&lt;p&gt;In fig 1.1 Table A&amp;rsquo;s &amp;lsquo;f&amp;rsquo; column is compared with table B&amp;rsquo;s &amp;lsquo;f&amp;rsquo; column. If the value of the &amp;lsquo;f&amp;rsquo; column in the A table equals that of B&amp;rsquo;s &amp;lsquo;f&amp;rsquo; column it will return the match. More simply, &lt;code&gt;INNER JOIN&lt;/code&gt; clauses return rows from table A that have corresponding rows in the B table.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/SQLite-Inner-Join-Example.png" alt="Inner Join" title="example of inner join"&gt;&lt;/p&gt;
&lt;p&gt;Fig 1.1 Inner Join&lt;/p&gt;
&lt;p&gt;&lt;code&gt;INNER JOIN&lt;/code&gt; may connect more than two tables. This will require two inner join clauses form the &lt;code&gt;SELECT&lt;/code&gt; statement.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-SQL" data-lang="SQL"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;trackid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Track&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Album&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Artist&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;albumid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tracks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;albumid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;artists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;artists&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;artistid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;albums&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;artistid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example code of a query joining three tables by their Artist. (from Chinook.db found at SQLite&amp;rsquo;s website tutorial.)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/SQLite-Inner-Join-3-tables.jpg" alt="Inner Join Example" title="result set of inner join on three tables."&gt;&lt;/p&gt;
&lt;p&gt;Fig. 1.2 is the result set of the above query.&lt;/p&gt;
&lt;h3 id="full-outer-join"&gt;Full Outer Join&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;this command is not in SQLite3&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Theoretically, the result of a Right Join and Left Join with &lt;code&gt;NULL&lt;/code&gt; values for every column of the table that does not have a matching row.&lt;/p&gt;
&lt;p&gt;This is technically not available in SQLite3, however they do offer a workaround.&lt;/p&gt;
&lt;p&gt;Given that SQLite3 does not have &lt;code&gt;RIGHT JOIN&lt;/code&gt; or &lt;code&gt;FULL OUTER JOIN&lt;/code&gt; it uses both &lt;code&gt;UNION&lt;/code&gt; and &lt;code&gt;LEFT JOIN&lt;/code&gt; to emulate it.&lt;/p&gt;
&lt;p&gt;It achieves this by switching the position of the &lt;code&gt;LEFT JOIN&lt;/code&gt; clause over both columns. SQLite also uses &lt;code&gt;UNION ALL&lt;/code&gt; to duplicate rows from the result set of both queries. Finally, the use of a &lt;code&gt;WHERE&lt;/code&gt; clause will remove rows already included in the result set of the first &lt;code&gt;SELECT&lt;/code&gt; statement. See the below code for an example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-SQL" data-lang="SQL"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dogs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;UNION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ALL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dogs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="terms-definitions"&gt;Terms/ Definitions&lt;/h2&gt;
&lt;h3 id="primary-key"&gt;Primary Key&lt;/h3&gt;
&lt;p&gt;In order to qualify as a relational table, it must have a primary key.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;primary key&lt;/strong&gt; consists of one or more columns whose data contained within is used to uniquely identify each row in the table.
&lt;em&gt;metaphor&lt;/em&gt; If rows were mailboxes the primary key would be the street address.&lt;/p&gt;
&lt;p&gt;To be a true primary key it must be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unique (data, not the name),&lt;/li&gt;
&lt;li&gt;Must not be NULL or &amp;quot;&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tables must have primary keys, and these are stored in an index which is used to enforce the uniqueness requirement. Being indexed, accessing it does not necessitate scanning the entire table.&lt;/p&gt;
&lt;h3 id="foreign-key"&gt;Foreign Key&lt;/h3&gt;
&lt;p&gt;A &lt;strong&gt;foreign key&lt;/strong&gt; is one or more columns in a table that refers to the primary key or unique identifier in another table.&lt;/p&gt;
&lt;p&gt;They unlike primary keys &lt;strong&gt;can contain NULL, blank or duplicate values&lt;/strong&gt;. As they allow duplication it is best not to use a foreign key as a primary key, unless it is a one-to-one relationship.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/foreign_key.png" alt="input" title="example foreign key"&gt;&lt;/p&gt;
&lt;p&gt;In the above picture we have a Students and Cities table. PERSON_ID and CITY_ID are unique, and therefore make good Primary keys (which they are). Inside the Students table it is possible for several students to be born in the same city. As such the BIRTH_PLACE column is the foreign key to the Cities table. This ensures that there is relationship between the tables and that relation points to something unique in the other table.&lt;/p&gt;
&lt;h3 id="unions"&gt;Union&amp;rsquo;s&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;BLUF: combines result set of two or more queries into a single result set.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;UNION&lt;/code&gt; by default removes duplicate rows whereas &lt;code&gt;UNION ALL&lt;/code&gt; does not. As &lt;code&gt;UNION ALL&lt;/code&gt; does not remove duplicates it will process faster. Both statements have the following rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Must have the same number of columns,&lt;/li&gt;
&lt;li&gt;Corresponding columns must be same data type,&lt;/li&gt;
&lt;li&gt;The column names of the first query will determine the column of the combined result set,&lt;/li&gt;
&lt;li&gt;Any &lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;HAVING&lt;/code&gt; clauses are applied to each individual query, not the final result set,&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ORDER BY&lt;/code&gt; is applied to the combined result set, not the individual result sets.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="transaction"&gt;Transaction&lt;/h3&gt;
&lt;p&gt;The basis of all interactions with the database. Inserts, updates, deletes, commits and table creation and deletion are all transactions.&lt;/p&gt;</description></item><item><title>Linux Environment Variables &amp; PATH</title><link>https://danielms.site/blog/linux-environment-variables/</link><pubDate>Sat, 11 Aug 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/linux-environment-variables/</guid><description>&lt;h1 id="environment-variables"&gt;Environment Variables&lt;/h1&gt;
&lt;p&gt;Simply put, environment variables are a collection of key value strings held by the current shell or process. Each process has access to an array of environment variables held in its user-space memory.&lt;/p&gt;
&lt;p&gt;The environment variables are available to all applications, and allow these programs or scripts use them within the shell environment. They can allow custom modification to defaults that permeate throughout the user-space rather than be homed within each applications configuration.&lt;/p&gt;
&lt;p&gt;Each process that is created via &lt;code&gt;fork()&lt;/code&gt;, will inherit a copy of its parent&amp;rsquo;s environment. This ensures the communication and transfer of variables from the parent is a once-only one-way transaction. This is important to prevent updated environment variables from one child polluting the global user-space should it go wrong.&lt;/p&gt;
&lt;h1 id="path"&gt;PATH&lt;/h1&gt;
&lt;p&gt;Each environment will have access to several default or standard variables such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PATH&lt;/li&gt;
&lt;li&gt;HOME&lt;/li&gt;
&lt;li&gt;LOGNAME&lt;/li&gt;
&lt;li&gt;SHELL&lt;/li&gt;
&lt;li&gt;EDITOR&lt;/li&gt;
&lt;li&gt;MAIL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By typing &lt;code&gt;echo $HOME&lt;/code&gt; the users home location will be printed to the terminal. Environmental variables are &lt;strong&gt;case sensitive&lt;/strong&gt;, and by convention the key is written in capitals.&lt;/p&gt;
&lt;p&gt;echoing &lt;code&gt;$PATH&lt;/code&gt; on a unix platform will output a &lt;code&gt;:&lt;/code&gt; separated list of file paths such as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/usr/local/bin:/usr/local/sbin:/usr/bin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;PATH&lt;/code&gt; is set by system and user start-up scripts - the exact process can change between implementations.
Its function is to search in the &lt;code&gt;PATH&lt;/code&gt; locations for the name of any program being called for execution.
In linux everything is file, and when a file is set with the execution bit, that file can be called with the &lt;code&gt;exec()&lt;/code&gt; method.
What the &lt;code&gt;PATH&lt;/code&gt; does is search recursively through each location, from left to right as its printed, looking for that file if its not given with an absolute path.&lt;/p&gt;
&lt;h2 id="example"&gt;Example&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ which &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/bin/echo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls -l /bin/echo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rwxr-xr-x &lt;span class="m"&gt;1&lt;/span&gt; root root 30k Dec &lt;span class="m"&gt;29&lt;/span&gt; &lt;span class="m"&gt;2017&lt;/span&gt; /bin/echo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/home/userName/bin:/usr/local/bin:/usr/bin:/bin &lt;span class="c1"&gt;# /bin in PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Above we see that calling &lt;code&gt;which echo&lt;/code&gt; returns the location of &lt;code&gt;echo&lt;/code&gt; (in my distro it returns &lt;code&gt;echo: shell built-in command&lt;/code&gt; but &lt;code&gt;/bin/echo&lt;/code&gt; will call the file nonetheless).
Now when we call &lt;code&gt;echo&lt;/code&gt; which is not an absolute path, the &lt;code&gt;PATH&lt;/code&gt; variable is consulted. Moving left to right the system checks for an executable file named &lt;code&gt;echo&lt;/code&gt;, which is finds inside &lt;code&gt;/bin&lt;/code&gt;. Once found it executes and ceases the search. If we called &lt;code&gt;/bin/echo&lt;/code&gt; the &lt;code&gt;PATH&lt;/code&gt; would be searched.&lt;/p&gt;
&lt;h2 id="example-two"&gt;Example Two&lt;/h2&gt;
&lt;p&gt;In a real world case, I use Go&amp;rsquo;s &lt;code&gt;/bin&lt;/code&gt; location as an exemplar of why you may need to add to the &lt;code&gt;PATH&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;Dave Cheney has a pretty sweet Go knock off of an awesome package by the same name &lt;code&gt;httpstat&lt;/code&gt;. The TL;DR is that it gives you statistic on a site by simply calling &lt;code&gt;httpstat https://reorx.com&lt;/code&gt;. Astute readers will see that this requires &lt;code&gt;httpstat&lt;/code&gt; to be on the &lt;code&gt;PATH&lt;/code&gt; or it will generate an error.&lt;/p&gt;
&lt;p&gt;Fixing this was simple as we see below.&lt;/p&gt;
&lt;h1 id="creation--deletion"&gt;Creation &amp;amp; Deletion&lt;/h1&gt;
&lt;p&gt;Environment variables are key value pairs, case sensitive and by convention have the key written in caps.&lt;/p&gt;
&lt;h2 id="creation"&gt;Creation&lt;/h2&gt;
&lt;p&gt;Adding to your current shell environments list of variables is simple.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# method 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;VAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;foo &lt;span class="c1"&gt;# create key:value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; VAR &lt;span class="c1"&gt;# export is called on VAR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$VAR&lt;/span&gt; &lt;span class="c1"&gt;# echo VAR to terminal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;foo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# method 2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;VAR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;foo &lt;span class="c1"&gt;# combine export and variable assignment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$VAR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;foo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bourne shells accept the second method, and its probably the most common and convenient method. &lt;code&gt;export&lt;/code&gt; is unix command to set the attribute for a variable. See &lt;code&gt;man export&lt;/code&gt; for more information.&lt;/p&gt;
&lt;h2 id="deletion"&gt;Deletion&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;unset&lt;/span&gt; VAR
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$VAR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# will echo empty string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Continuing with the &lt;code&gt;httpstat&lt;/code&gt; example we will append the location of Go&amp;rsquo;s package binaries to our &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/go/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt; &lt;span class="c1"&gt;# now added to PATH&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once the variable is exported, it will only live within the current shell. If we close it down, that updated &lt;code&gt;PATH&lt;/code&gt; is now lost.&lt;/p&gt;
&lt;p&gt;To make the variables persistent we add them to our shell&amp;rsquo;s &lt;code&gt;.profile&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt; if using bash. In my case, I use Zsh so its added to &lt;code&gt;.zshrc&lt;/code&gt;. Calling &lt;code&gt;source .zshrc&lt;/code&gt; will reload &lt;code&gt;.zshrc&lt;/code&gt; and make the new variables available if not already. And, because each process inherits form the parent, removing it form the file will prevent it being added to the environment list if needed in the future.&lt;/p&gt;
&lt;h1 id="security-tidbits"&gt;Security tidbits&lt;/h1&gt;
&lt;p&gt;Of note linux implements a security measure within the Superusers &lt;code&gt;PATH&lt;/code&gt;. The current working directory is normally omitted from the &lt;code&gt;PATH&lt;/code&gt; meaning files must be called using &lt;code&gt;./&lt;/code&gt; as the prefix or as an absolute path. This prevents malicious users from placing an executable file with the same name as a system-wide executable such as &lt;code&gt;ls&lt;/code&gt; being run as root.&lt;/p&gt;
&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Environment variables allows the system to access a list of values for use in applications&lt;/li&gt;
&lt;li&gt;Users can set and unset variables&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PATH&lt;/code&gt; is important when you need to access an executable without calling its absolute path&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Python Object References</title><link>https://danielms.site/blog/python-object-references/</link><pubDate>Mon, 09 Jul 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/python-object-references/</guid><description>&lt;h1 id="python-object-reference-eli5"&gt;Python Object Reference ELI5&lt;/h1&gt;
&lt;hr&gt;
&lt;p&gt;I first learnt about variables through the analogy of &amp;ldquo;variables are
boxes&amp;rdquo; and that we assign things to those boxes Turns out, this isn&amp;rsquo;t
particularly helpful in objected orientated programming. This post is
about how python treats object assignment and some of the hidden
gotcha&amp;rsquo;s that can cause unintended errors along the way. Instead of
&amp;ldquo;boxes&amp;rdquo; it is better to think of variables as &amp;ldquo;labels&amp;rdquo; that we
attach to objects. And, as everything in python is an object its
important to remember that all objects have three things; identity, type
and values. Values are the only things that change once an object is
created, and it values that we often care about, and hence label.&lt;/p&gt;
&lt;h3 id="labels-not-boxes"&gt;Labels not boxes&lt;/h3&gt;
&lt;p&gt;Extending the &amp;ldquo;labels&amp;rdquo; metaphor a little we look at the assignment of
variables.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# we label the integer 2 as &amp;#39;a&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#39;a&amp;#39; is now labelled as &amp;#39;b&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="c1"&gt;# and &amp;#39;b&amp;#39; is now labelled as &amp;#39;c&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Above we can see that the object &lt;code&gt;2&lt;/code&gt;} is assigned to the
variable &amp;lsquo;a&amp;rsquo;. Each subsequent assignment thereafter is simply a
reference to the same object. When viewed through this lense you can
start to see how objects have labels. It is not feasible that the
&lt;code&gt;2&lt;/code&gt; can exist in three different boxes rather we visualise
&lt;code&gt;2&lt;/code&gt; having three sticky notes attached to it. If we changed
&lt;code&gt;a&lt;/code&gt; like this &lt;code&gt;a = 20&lt;/code&gt; then it is just a
matter of peeling off the sticky note with &lt;code&gt;a&lt;/code&gt; written on
it from &lt;code&gt;2&lt;/code&gt; and attaching it to &lt;code&gt;20&lt;/code&gt;. To
further aid in this thinking, always read assignments from right to
left. The right side is where the object is created or retrieved and the
left is what we bind to it (the label.. Enough you get it already!)&lt;/p&gt;
&lt;p&gt;When an object like &lt;code&gt;2&lt;/code&gt; has many labels we called this
&lt;em&gt;aliasing&lt;/em&gt;. Aliasing is an important concept to grasp, and to illustrate
why we will examine the identity of &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;,
and &lt;code&gt;c&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# original object&amp;#39;) # a id: 139886603774600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;b id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# alias of a&amp;#39;) # b id: 139886603774600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;c id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# copy of a&amp;#39;) # c id: 139886603774600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;All aliases of &lt;code&gt;a&lt;/code&gt; have the same identity which in python
is unique integer representing its C memory address. If any change were
to be made the identity integer would also change to reflect that.&lt;/p&gt;
&lt;h3 id="when-is--true"&gt;When is == true?&lt;/h3&gt;
&lt;p&gt;Let&amp;rsquo;s check out Equality and Identity (and aliases, too)&lt;/p&gt;
&lt;p&gt;An object&amp;rsquo;s identity never changes once it has been created. However
its values might, and generally this is what we care about more. Python
gives us the option to check either like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="c1"&gt;# compares the values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="c1"&gt;# compares the identities&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Lets extend this using a more complex example using some dictionaries.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;batman&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Bruce Wayne&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;job&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;crime fighter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;bruce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;batman&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batman&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;bruce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batman&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;bruce&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Both &lt;code&gt;batman&lt;/code&gt; and &lt;code&gt;bruce&lt;/code&gt; are equal in
identity, and their values. Suppose we have a vigilante crime fighter
out there pretending to be &lt;code&gt;batman&lt;/code&gt;, named
&lt;code&gt;manbat&lt;/code&gt;, does he have the same equality?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;manbat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Bruce Wayne&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;job&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;crime fighter&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batman&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;manbat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batman&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;manbat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this case, both &lt;code&gt;manbat&lt;/code&gt; and &lt;code&gt;batman&lt;/code&gt; share
equal values but not the same identity. &lt;code&gt;manbat&lt;/code&gt; is not an
alias of &lt;code&gt;bruce&lt;/code&gt; or &lt;code&gt;batman&lt;/code&gt;, and thus has his
own unique identity. This is because we created an entirely new identity
albeit with the same values as batman.&lt;/p&gt;
&lt;p&gt;Much of the time we care mostly about the values an object holds not its
identity but you will see &lt;code&gt;is&lt;/code&gt; in a lot during conditionals
such as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;something&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="alias-issues"&gt;Alias Issues&lt;/h3&gt;
&lt;p&gt;Something I didn&amp;rsquo;t realise until it came back to haunt me much later is
that aliases can have unintended side effects with mutable types. Let&amp;rsquo;s
say we have two lists, the original and its alias. The alias will have
items added to it but we want the original untouched for whatever
reason.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;orig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orig&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Looks good, we can now make changes to &lt;code&gt;new&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;FizzBuzz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [10, 20, 30, [100, 200], &amp;#39;FizzBuzz&amp;#39;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [10, 20, 30, [100, 200], &amp;#39;FizzBuzz&amp;#39;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After appending to &lt;code&gt;new&lt;/code&gt; it becomes apparent that this
change has affected both lists. This happens because the alias works two
way with mutable types. I think this is really important to know -
aliases are not copies!&lt;/p&gt;
&lt;h3 id="copies"&gt;Copies&lt;/h3&gt;
&lt;p&gt;If aliases aren&amp;rsquo;t copies then how do we copy?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;orig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# dict(x) also works this way&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;orig id:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# orig id: 140443406513496&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;new id:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# new id: 140443402343535&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By using the &lt;code&gt;list()&lt;/code&gt; class we successfully create two new
objects. Now if we append or remove items from either list it does not
propagate through. Except, it does sometimes.&lt;/p&gt;
&lt;p&gt;In this case we are only making a new copy of the overall object but not
any &lt;strong&gt;mutable&lt;/strong&gt; nested types within the copy. So while any changes made
within the first layer of the object are contained within the copy, any
mutable objects nested more deeply will be aliases.&lt;/p&gt;
&lt;p&gt;Confused, an example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;orig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;not nested&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [10, 20, 30, [100, 200]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [10, 20, 30, [100, 200], &amp;#39;not nested&amp;#39;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# first layer is not affected as it is a copy, not an alias&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;i am aliased to orig&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [10, 20, 30, [100, 200, &amp;#39;i am aliased to a&amp;#39;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# [10, 20, 30, [100, 200, &amp;#39;i am aliased to a&amp;#39;], &amp;#39;not nested&amp;#39;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While the &lt;code&gt;orig&lt;/code&gt; and &lt;code&gt;new&lt;/code&gt; are independent of
each other when making changes to the first layer of abstraction, any
mutable types within that are simply aliases of the copies source.&lt;/p&gt;
&lt;p&gt;Another example to check this out.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# before we started making alterations to the lists&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# 140443390926984&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# 140443392352593&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orig&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="c1"&gt;# 140443395483400&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="c1"&gt;# 140443395483400&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Inspecting the identities reveals that only the overall object's were
initialised as new objects but the nested types within were bound to the
original nested type - an alias!&lt;/p&gt;
&lt;p&gt;This is something to take into consideration when passing variables
around that have nested types. To circumvent this immutable types such
as tuples can be used in place.&lt;/p&gt;
&lt;p&gt;Python can do deep copies which will take care of this issue, but it has
its own drawbacks. Of which we not be discussed here as this post is
already quite long. See &lt;a href="https://realpython.com/copying-python-objects/"&gt;Dan
Bader's&lt;/a&gt; excellent post
for more information.&lt;/p&gt;
&lt;h3 id="wrapping-up"&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;In python all objects have a type, identity and values. Only the values
can change after it is created and knowing a little bit more about how
this works can help us prevent unintended bugs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;assignment does not create copies&lt;/li&gt;
&lt;li&gt;nested mutable types within shallow copies are aliases&lt;/li&gt;
&lt;li&gt;equality has two different checks; identity, and values&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Public Key Crypto: The Basics</title><link>https://danielms.site/blog/public-key-crypto-the-basics/</link><pubDate>Thu, 14 Jun 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/public-key-crypto-the-basics/</guid><description>&lt;h1 id="public-key-cryptography"&gt;Public Key Cryptography&lt;/h1&gt;
&lt;h2 id="my-simplest-explanation"&gt;My simplest explanation&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Public key cryptography is a method of using a different key
for the encryption and decryption of a file, message or other medium.&lt;/p&gt;
&lt;h2 id="public-key-cryptography-1"&gt;Public Key Cryptography&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;What problem does it solve?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If two parties wish to exchange information to one another but the
medium which they use to communicate is compromised, how do they share a
secret key to unlock their encrypted messages? Symmetric key
cryptography would make this untenable for the most part. But, if the
parties use two keys; one for encryption and one for decryption they can
circumvent the interception as having only one of the keys is not enough
to unlock the messages contents. This is also known as &lt;em&gt;asymmetric&lt;/em&gt;
cryptography.&lt;/p&gt;
&lt;p&gt;Public key cryptography uses two keys; a public and private key. The
public key can be transferred or published globally without worry, as it
requires the private key to unlock the cipher.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;how does it work?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We have two actors whom wish to send a secure message; &lt;em&gt;Bob&lt;/em&gt; and
&lt;em&gt;Alice&lt;/em&gt;. If Alice wants to send a message to Bob, first she finds Bob&amp;rsquo;s
public key either by looking it up globally, or getting it directly from
Bob. Alice now encrypts her message using Bob&amp;rsquo;s public key. This is safe
because Bob&amp;rsquo;s public key cannot decrypt this message, meaning it does
not matter if anyone else has Bob&amp;rsquo;s public key.&lt;/p&gt;
&lt;p&gt;When Bob receives Alice&amp;rsquo;s encrypted message, he uses his private key
-which only Bob has access to - and decrypts the message. Should Bob
wish to reply, he simply repeats the process, now using Alice&amp;rsquo;s public
key to encrypt his message to her.&lt;/p&gt;
&lt;p&gt;At it&amp;rsquo;s core, this is the principle of asymmetric or public key
encryption.&lt;/p&gt;
&lt;h2 id="digital-signatures"&gt;Digital Signatures&lt;/h2&gt;
&lt;p&gt;In the physical world, a signature is a form of validation. It certifies
that the signing party agrees to the document, and that they are who
they say they are. Unfortunately, it is easy to spoof or refute a
signature over the internet - how does one prove it was &lt;em&gt;them&lt;/em&gt; and not
someone else?&lt;/p&gt;
&lt;p&gt;Using public key cryptography we are able to digitally watermark an
object in a way that verifies the signature is from the party in
question. If Alice wants to digitally verify a document with her name,
she can sign it using public key cryptography.&lt;/p&gt;
&lt;p&gt;When Bob receives Alice&amp;rsquo;s encrypted message everyone can decrypt this
message as they can get her public key, but no-one else has her private
key. This means that no-one could of altered the message, or forged it
entirely because Alice is the sole owner of the private key.&lt;/p&gt;
&lt;p&gt;This is often referred to &lt;em&gt;non-repudiation&lt;/em&gt;. Meaning that at a later
time the author cannot refute the fact that they are the owner of the
message.&lt;/p&gt;
&lt;h2 id="can-someone-use-my-key"&gt;Can Someone Use My Key?&lt;/h2&gt;
&lt;p&gt;The entire principle of public key cryptography relies on trusting the
source of the keys. If a third party can steal or intercept the private
and public keys then they can masquerade as the legitimate recipient or
sender of the message.&lt;/p&gt;
&lt;p&gt;Two common examples are possible; interception, and theft.&lt;/p&gt;
&lt;p&gt;If a third party can intercept the public key before the legitimate
recipient and then forward on their own in its place, they will effect a
man-in-the-middle (MITM) attack. In this case, it would be possible for
the third party to read the messages, and reply on their behalf.&lt;/p&gt;
&lt;p&gt;Another possibility is theft. If a belligerent were able to break into a
key-store or server which has the private key stored on file, it is
possible to now read, write and intercept messages. More often this type
of intrusion will use that key to bypass other servers authentication
which in the case of passwordless-ssh may give an attacker access to
another server in the network.&lt;/p&gt;
&lt;p&gt;This issue of authentication is outside this post, but it is essential
to understand that asymmetric cryptography provides confidentially,
rather than authentication.&lt;/p&gt;</description></item><item><title>Base64 ELI5</title><link>https://danielms.site/blog/base64-eli5/</link><pubDate>Tue, 22 May 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/base64-eli5/</guid><description>&lt;h2 id="base64"&gt;Base64&lt;/h2&gt;
&lt;p&gt;Base64 is a common encoding scheme. But what is it, why 64 bits and how does it work?&lt;/p&gt;
&lt;h3 id="tldr"&gt;TL;DR&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Base64 is a binary to ASCII encoding that takes a byte (8 bits) and chunks its down into segments of 6 bits - six ones in binary equates to 64 which is where it derives its name.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="first-a-demo"&gt;first, a demo&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at an example using the three letter word &amp;ldquo;The&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;This tabulated data below shows how the word &amp;lsquo;The&amp;rsquo; is represented in both binary and base64. Each letter corresponds with an integer. For instance, &amp;lsquo;T&amp;rsquo; is mapped to the number 84. This is then converted into binary, and the entire word &amp;lsquo;The&amp;rsquo; can be expressed in three bytes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#ascii T (84) h (104) e (101) &amp;lt;-- ASCII plus base10 number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#binary 01010100 01101000 01100101 &amp;lt;-- three bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#base64 010101 000110 100001 100101 &amp;lt;-- four segments of six bits&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;While binary takes 8 bits, base64 only accepts 6. This means that the last two bits are clipped from the end and become the first two bits of the next segment.&lt;/p&gt;
&lt;p&gt;Now that we have base64 binary we can calculate the base10 integer. This is done so we can map that integer to its base64 character.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#base64 010101 000110 100001 100101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#total 21 06 33 37&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or, a graphical representation for mobile users.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/base64the.png" alt="Base 64 THE" title="The in base64 image"&gt;&lt;/p&gt;
&lt;p&gt;Character to value conversion chart for base64. By cross referencing the binary representation of the four segments (21, 06, 33, 37) we get the ASCII characters; &lt;code&gt;VGhl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/img/base64.png" alt="Base64 Chart" title="base64 char to int conversion chart"&gt;&lt;/p&gt;
&lt;h4 id="gotchas"&gt;Gotcha&amp;rsquo;s&lt;/h4&gt;
&lt;p&gt;In the example we used a three byte word which can neatly be broken down
into four segments of six. What if the word is four characters equalling 32
bits. Dividing 32 by 6 gives us 5 with a remaining two bits.&lt;/p&gt;
&lt;p&gt;We can see this clearly using &lt;code&gt;python&lt;/code&gt; (because, why not.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;5.33333333333&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In that case we need to pad out the base64 to indicate that the last
segment is not complete.&lt;/p&gt;
&lt;p&gt;The decimal representation of &amp;ldquo;Them&amp;rdquo; is &lt;code&gt;84 104 101 109&lt;/code&gt; or &lt;code&gt;VGhlbQ==&lt;/code&gt; in base64. As you can see there are two &lt;code&gt;=&lt;/code&gt; symbols tacked on to the end. This indicates that there is two empty segments of 6 bits at the end of the encoding.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#ascii T (84) h (104) e (101) m (109)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#binary 01010100 01101000 01100101 01101101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#base64 010101 000110 100001 100101 001101 010000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#total 21 06 33 37 13 16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Looks like that works out perfectly, so where does the extra two
segments of zero&amp;rsquo;s come in that generates the &lt;code&gt;=&lt;/code&gt;&amp;rsquo;s?&lt;/p&gt;
&lt;p&gt;The segmenting of eight bits into six bits cannot delimit or end half way
between a byte. Meaning the base64 encoding must continue until there is
no remainder. Let&amp;rsquo;s explore this again using just one character.&lt;/p&gt;
&lt;p&gt;In text format&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#ascii M (77)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#binary 01011101 00000000 00000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#base64 010111 010000 000000 000000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#total 19 16 00 00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A pretty picture, too.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/base64M.png" alt="Base 64 M" title="M in base64 image"&gt;&lt;/p&gt;
&lt;p&gt;I chose the letter &amp;lsquo;M&amp;rsquo; as it is easier to explain than &amp;lsquo;T&amp;rsquo;. Looking at it you can see that even with only one byte (read: character), it takes three bytes for base64&amp;rsquo;s segments of six to equally divide - (18 / 6 = 3). This is why the letter &amp;lsquo;M&amp;rsquo; in base64 would be padded with &lt;code&gt;==&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This example uses two equals signs but base64 can also be padded with just one as seen below.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/images/base64Ya.png" alt="Base 64 Ya" title="Ya in base64 image"&gt;&lt;/p&gt;
&lt;p&gt;If you are wondering about the &lt;code&gt;-1&lt;/code&gt; in the columns, ignore them, its just how the JavaScript that powers the encoder has been developed. See &lt;a href="https://codepen.io/lewistg/pen/MEQbmB"&gt;Ty Lewis&lt;/a&gt;&amp;rsquo; CodePen to see it in action.&lt;/p&gt;
&lt;p&gt;Base64 increases the size of the data transmission as more packets are required to be sent. So why use it? Base64&amp;rsquo;s genesis was in MIME (Multipurpose Internet Mail Extensions) where large streams of text are used to send emails and attachments.&lt;/p&gt;
&lt;p&gt;In the early days of email, everything was plain text but the inclusion of HTML and attachments created problems. Email is sent as a stream and streaming binary, or hexadecimal could of had unintended consequences as some systems or programs may interpret binary incorrectly. For instance, a null byte in some systems may indicate that the message has ended when in fact it has not.&lt;/p&gt;
&lt;p&gt;Today base64 is used everywhere on the net, mostly as a obfuscation technique. A poor one at that. Here is some more information on base64 in the modern web skewed towards infosec.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Jpaq0QkepgA"&gt;LiveOverFlow&lt;/a&gt; highlighting base64 patterns in hiding JSON data.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sans.org/reading-room/whitepapers/detection/base64-pwned-33759"&gt;SANS&lt;/a&gt; whitepaper on how base64 can get you pwned.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.malwarebytes.com/threat-analysis/2013/03/obfuscation-malwares-best-friend/"&gt;MalwareBytes&lt;/a&gt; looking at malware obfuscation techniques.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="more-information"&gt;More information?&lt;/h3&gt;
&lt;p&gt;As always &lt;a href="https://en.wikipedia.org/wiki/Base64"&gt;Wikipedia&lt;/a&gt; has an
excellent page on Base64.&lt;/p&gt;
&lt;p&gt;Shout out to &lt;a href="https://codepen.io/lewistg/pen/MEQbmB"&gt;Ty Lewis&lt;/a&gt; for his nice encoder which I have used for demonstration.&lt;/p&gt;</description></item><item><title>Flask, forms and http requests</title><link>https://danielms.site/blog/flask-forms-and-http-requests/</link><pubDate>Mon, 23 Apr 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/flask-forms-and-http-requests/</guid><description>&lt;h2 id="flask-forms-and-http-requests"&gt;Flask, Forms and HTTP Requests&lt;/h2&gt;
&lt;h3 id="preface"&gt;Preface&lt;/h3&gt;
&lt;p&gt;This week I have been building a small webapp that leverage&amp;rsquo;s an
external API to populate values in the user pages. And it pains me to
say that I spent a silly length of time debugging what was a rather easy
problem.&lt;/p&gt;
&lt;p&gt;The webapp uses Flask, jinja2 templates and the
&lt;a href="https://github.com/danielmichaels/fuelwatcher"&gt;fuelwatcher&lt;/a&gt; API. The
issue started when trying to institute a search bar that allows the user
to select a series of parameters.&lt;/p&gt;
&lt;p&gt;In the end the following issues were present:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My lack of understanding in regards to HTTP requests&lt;/li&gt;
&lt;li&gt;Not knowing how the Flask Request context functions&lt;/li&gt;
&lt;li&gt;Improper formatting of my HTML forms&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="learn-you-some-http-for-great-good"&gt;Learn You Some HTTP For Great Good&lt;/h3&gt;
&lt;p&gt;Foolishly, I thought I understood HTTP requests. Sometimes you need to
use your theoretical knowledge practically to fully grasp a concept. It
turns out I was mixing &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; when
submitting data to the server in conjunction with using HTML form
attributes incorrectly.&lt;/p&gt;
&lt;hr&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Get&lt;/th&gt;
					&lt;th&gt;Post&lt;/th&gt;
					&lt;th&gt;&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1.&lt;/td&gt;
					&lt;td&gt;Sends Parameters in URL&lt;/td&gt;
					&lt;td&gt;Sends Parameters in body&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2.&lt;/td&gt;
					&lt;td&gt;Used for fetching documents&lt;/td&gt;
					&lt;td&gt;Used for updating data&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3.&lt;/td&gt;
					&lt;td&gt;Has max length URL limitations&lt;/td&gt;
					&lt;td&gt;No max length (theoretically)&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4.&lt;/td&gt;
					&lt;td&gt;Often Cached&lt;/td&gt;
					&lt;td&gt;Server does not cache&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;5.&lt;/td&gt;
					&lt;td&gt;Idempotent&lt;/td&gt;
					&lt;td&gt;Not idempotent&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;6.&lt;/td&gt;
					&lt;td&gt;Should not change server data&lt;/td&gt;
					&lt;td&gt;Can change server data&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;p&gt;A key point for said webapp is item number 1; I was sending parameters
in the URL when they really needed to be sent via the body. Expanding on
this a little, in a &lt;code&gt;GET&lt;/code&gt; request the parameters are
transmitted using URI schema friendly syntax (see wiki
&lt;a href="https://en.wikipedia.org/wiki/Uniform_Resource_Identifier"&gt;here&lt;/a&gt;). It
is common to see query delimiters in the URL such as &lt;code&gt;&amp;amp;&lt;/code&gt;,&lt;code&gt;=&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;#&lt;/code&gt; and should
of been a clue in the debugging process. Transmitting a
&lt;code&gt;GET&lt;/code&gt; request like this fetches data from another resource,
where in contrast my data was needed to execute a function within the
server application to pull information from a third party.&lt;/p&gt;
&lt;h3 id="flask-requests"&gt;Flask Requests&lt;/h3&gt;
&lt;p&gt;This is deep subject but is tightly coupled to the previous point. The
request object in flask gives access to the global &lt;em&gt;request&lt;/em&gt; object.
Meaning it parses the incoming request data for you. This is important
because it is checking for a &lt;code&gt;method&lt;/code&gt; attribute within that
request. Depending on which &lt;code&gt;method&lt;/code&gt; is sent will affect
how it/ you should check for the request object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; If you incorrectly set the method to &lt;code&gt;GET&lt;/code&gt; when
its actually a &lt;code&gt;POST&lt;/code&gt; you will have problems.&lt;/p&gt;
&lt;p&gt;To receive the request object and parse for the data you want the
following should be used:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# displaying both POST and GET for clarity&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# POST request // Use &amp;#39;form&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/test&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;attribute to be parsed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="c1"&gt;# or render_template with select=select etc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# GET request // Use &amp;#39;args&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# here we want to get the value of user (i.e. ?user=some-value)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# or how ever you want to use that data&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Given these snippets you can see how mixing &lt;code&gt;GET&lt;/code&gt; with
&lt;code&gt;POST&lt;/code&gt; will lead to calling the wrong request method.&lt;/p&gt;
&lt;h3 id="html-form-tags-for-dummies"&gt;HTML form tags for Dummies&lt;/h3&gt;
&lt;p&gt;HTML, basic right? Don&amp;rsquo;t let hubris fool you, not learning HTML deeply
is foolish. To be fair, this is the first app I have created that has
not required the use of &lt;code&gt;flask-wtf&lt;/code&gt;, which kindly generates
forms and their tags for you.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- standard form tags --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;{{ url_for(&amp;#39;app.function&amp;#39;)}}&amp;#34;&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;post&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Simple, right? Not if you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mix up the HTTP request&lt;/li&gt;
&lt;li&gt;Use the wrong flask request method&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t use an attribute which you can parse in using
&lt;code&gt;request.form.get('attribute')&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tightly coupled (I think there is a SOLID principle about this&amp;hellip;) and
frustrating when you don&amp;rsquo;t connect the dots.&lt;/p&gt;
&lt;p&gt;The last piece of the puzzle was the simplest of all, I had not set an
attribute that the request object could get. In this example a
&lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; tag was used and simply appending
&lt;code&gt;name=attribute_to_be_parsed&lt;/code&gt; worked.&lt;/p&gt;
&lt;p&gt;For clarity, first the html.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;&amp;lt;!-- html --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;#39;item1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="na"&gt;attribute&lt;/span&gt; &lt;span class="na"&gt;flask&lt;/span&gt; &lt;span class="na"&gt;looks&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;{%&lt;/span&gt; &lt;span class="na"&gt;for&lt;/span&gt; &lt;span class="na"&gt;item&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt; &lt;span class="err"&gt;%}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="na"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ item }} &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="na"&gt;what&lt;/span&gt; &lt;span class="na"&gt;we&lt;/span&gt; &lt;span class="na"&gt;get&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt; &lt;span class="na"&gt;the&lt;/span&gt; &lt;span class="na"&gt;response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="na"&gt;option&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {% endfor}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;select&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the flask part.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# python: flask&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;item_to_get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;item1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;--&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="n"&gt;parsing&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;hellip; My face when it worked.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/img/its_alive.jpg" alt="Face of surprise!"&gt;&lt;/p&gt;
&lt;h3 id="learn-from-your-mistakes"&gt;Learn from your mistakes&lt;/h3&gt;
&lt;p&gt;Everyone makes mistakes, learn from it, be humble and don&amp;rsquo;t do it again.&lt;/p&gt;</description></item><item><title>Uploading to PyPI</title><link>https://danielms.site/blog/uploading-to-pypi/</link><pubDate>Sat, 07 Apr 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/uploading-to-pypi/</guid><description>&lt;p&gt;&lt;strong&gt;UPDATE 27/01/2019: This area of python and the PyPI is under going rapid
development and as such the following may have parts which are no longer in date.
In time I will update this post to reflect these changes.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="python-package-index"&gt;Python Package Index&lt;/h2&gt;
&lt;p&gt;Python has a wonderful community and package ecosystem. It currently has
over 130,000 packages for download and a large variety to choose from.
To download a python package via the &lt;code&gt;pip&lt;/code&gt; command, the
package must be uploaded to the Python Package Index, or
&lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt;. Going forward it may be referred to as the
&amp;ldquo;warehouse&amp;rdquo; as PyPI is going through an upgrade of its infrastructure
and for the better.&lt;/p&gt;
&lt;h3 id="have-application-now-what"&gt;Have application, now what?&lt;/h3&gt;
&lt;p&gt;PyPI offers two servers for the uploading of python packages; testing
and production.&lt;/p&gt;
&lt;p&gt;Sending the package to the test server is a great idea as it allows you
to download your tarball onto any system for testing. This allows you to
do a few novel things like sharing it with friends or co-workers,
spinning up virtual machine&amp;rsquo;s with different operating systems or
installing it into separate virtual environments with different versions
of python.&lt;/p&gt;
&lt;p&gt;This article does assume you are using a version control system, and in
particular GitHub but this is not a requirement for PyPI.&lt;/p&gt;
&lt;h3 id="productionise-your-code"&gt;Productionise your code&lt;/h3&gt;
&lt;p&gt;Before looking at how to upload your modules, first it must be made
ready for release into the wild.&lt;/p&gt;
&lt;h3 id="directory-structure"&gt;Directory Structure&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root-dir/ # The directory which all your files live.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; setup.py # covered below (Required)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; setup.cfg # if using markdown rather than ReStructuredText
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; LICENSE.txt # should be required!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; README.md # Also should be required!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; tests/ # tests are a good idea
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; test.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; your-package/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; __init__.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; awesome.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; wicked.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you look at any great package such as
&lt;a href="https://github.com/requests/requests"&gt;Requests&lt;/a&gt; or
&lt;a href="https://github.com/nicolargo/glances"&gt;Glances&lt;/a&gt; you will see a similar
(although much more intricate) structure. The key files we &lt;strong&gt;need&lt;/strong&gt; are
&lt;code&gt;setup.py&lt;/code&gt; and &lt;code&gt;setup.cfg&lt;/code&gt; if using Markdown.&lt;/p&gt;
&lt;h3 id="setuppy"&gt;Setup.py&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# setup.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;codecs&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ensure consistent encoding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;setuptools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# always prefer over distutils&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;VERSION&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0.1.0&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://github.com/username/package&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;DOWNLOAD_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/tarball/&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;here&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;README.md&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;long_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;yourpackage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;yourpackage&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;blurb that users first see to decide if interesting&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;long_description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;long_description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;long_description_content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text/markdown&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Optional&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;author_email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Optional&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;download_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DOWNLOAD_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;classifiers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 3 - Alpha&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 4 - Beta&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 5 - Production/Stable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Development Status :: 3 - Alpha&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Intended Audience :: Developers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;License :: OSI Approved :: MIT License&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Programming Language :: Python :: 3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Programming Language :: Python :: 3.5&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;Programming Language :: Python :: 3.6&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The information contained within &lt;code&gt;setup.py&lt;/code&gt; and in
particular its &lt;code&gt;setup()&lt;/code&gt; function is what creates the
package&amp;rsquo;s metadata for parsing by PyPI once uploaded.&lt;/p&gt;
&lt;p&gt;This is a stripped down version of my own &lt;code&gt;setup.py&lt;/code&gt;. Many
tutorials which are older will use &lt;code&gt;distutils&lt;/code&gt; but PyPI&amp;rsquo;s
&lt;a href="https://github.com/pypa/sampleproject"&gt;example&lt;/a&gt; structure explicitly
states to favour the newer &lt;code&gt;setuptools&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;By using a context manager and the &lt;code&gt;codecs.open&lt;/code&gt; method we
can read the README.md file for use in the
&lt;code&gt;long_description&lt;/code&gt; parameter within &lt;code&gt;setup()&lt;/code&gt;.
This is a requirement for the PyPI server as it by default only parses
ReStructuredText. If using README.rst, this can be ignored. Further, the
&lt;code&gt;long_description_content_type='text/markdown'&lt;/code&gt; must be
included or it will not format the content correctly. This is a very
recent addition to PyPI - March 2018.&lt;/p&gt;
&lt;p&gt;Also, the &lt;code&gt;version&lt;/code&gt; is what sets the tarball filename, so
creating an easy to adjust global variable makes it a lot easier to
amend this file when updating your package. As you can see there is
three locations that need to be updated for it to function correctly.
(Thanks to [Dan Bader] (&lt;a href="https://dbader.org"&gt;https://dbader.org&lt;/a&gt;) for the
idea.)&lt;/p&gt;
&lt;p&gt;Please refer to the example page for more information, particularly if
your package is more complex than just a few modules.&lt;/p&gt;
&lt;h3 id="setupcfg"&gt;Setup.cfg&lt;/h3&gt;
&lt;p&gt;This is just required for Markdown parsing. As you may see it might just
be easier to utilise the default supported .rst files. Something I may
do in the future.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;metadata&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;description-file &lt;span class="o"&gt;=&lt;/span&gt; README.md
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="python-setuppy-sdist"&gt;Python setup.py sdist&lt;/h3&gt;
&lt;p&gt;Running this command will invoke the &lt;code&gt;setup.py&lt;/code&gt; and create
a folder called &lt;code&gt;dist/&lt;/code&gt; inside your root directory. This is
where your application&amp;rsquo;s tarball will now live. It is also a good time
to create or update your git tags for your repository.&lt;/p&gt;
&lt;p&gt;FYI, once you create a local tag it must be pushed to the remote.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git tag X.Y.Z -m &lt;span class="s2"&gt;&amp;#34;Add a message such as; First!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push X.Y.Z &lt;span class="c1"&gt;# preferred option&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push --tags &lt;span class="c1"&gt;# less preferred as it pushs ALL tags to the remote server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="upload-y-u-no-easy"&gt;Upload: y u no easy&lt;/h3&gt;
&lt;p&gt;In theory uploading to PyPI is just that simple. Unfortunately it isn&amp;rsquo;t
that easy and a lot of the helpful blogs and references out there aren&amp;rsquo;t
current with the new standards. As always the official user guide
(&lt;a href="https://packaging.python.org/tutorials/distributing-packages/#uploading-your-project-to-pypi"&gt;here&lt;/a&gt;)
is the holy grail but isn&amp;rsquo;t the easiest reading when completely unsure!&lt;/p&gt;
&lt;h3 id="1-register"&gt;1. Register&lt;/h3&gt;
&lt;p&gt;To upload anything to PyPI you must first register with it. And if you
want to make use of the testing server, you must register with it
separately. Although they use the same software, each server uses a
separate database and this is why two different sign up&amp;rsquo;s are required.
They can be found here for &lt;a href="https://pypi.org/account/register/"&gt;Live&lt;/a&gt;
and &lt;a href="https://test.pypi.org/account/register/"&gt;Test&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="2-create-pypirc"&gt;2. Create .pypirc&lt;/h3&gt;
&lt;p&gt;This file allows your development machine to talk to the PyPI servers.
It should look something like this.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ~/.pypirc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;distutils&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;index-servers &lt;span class="o"&gt;=&lt;/span&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pypi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pypitest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;pypi&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://upload.pypi.org/legacy/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;username
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;pypitest&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://test.pypi.org/legacy/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;username
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is current to today&amp;rsquo;s date but the repository url may change as the
PyPI warehouse continues its evolution. The file &lt;strong&gt;must&lt;/strong&gt; be located in
the home directory. Both username and password can be set in this file,
or in environment variables.&lt;/p&gt;
&lt;h3 id="3-install-twine"&gt;3. Install Twine&lt;/h3&gt;
&lt;p&gt;What is it and why use it? Twine is a package written by the PyPI
maintainers that uses SSL by default when sending information to their
endpoint. Python versions before 2.7.9 and 3.2 do not use this by
default and spill user credentials over the air. Also twine separates
the creation of the package tarball and uploading into two logical
commands; setuptools does not - they are done in the same invocation.&lt;/p&gt;
&lt;p&gt;Tarball? To send your package to the server it first must be compressed
into a single file. So basically, you zip your files to send and when
&lt;code&gt;pip install xxxx&lt;/code&gt; is called your tarball is downloaded and
unzipped at the end user. This is an important point because any changes
you make after creating your tarball are not included in the package so
you will need to rezip it to include them.&lt;/p&gt;
&lt;p&gt;Personally, I install twine on the system interpreter and update it
along with setup tools frequently.&lt;/p&gt;
&lt;h3 id="4-upload"&gt;4. Upload!&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;twine upload -r pypitest dist/package-version-i-select-explicitly.0.1.0.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; Uploading distributions to https://test.pypi.org/legacy/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; Enter your password:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt;&amp;gt;&amp;gt; Uploading package-version-i-select-explicitly.0.1.0.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Using the &lt;code&gt;-r&lt;/code&gt; flag allows you to set which server to send
the file too. This file name is setup in the &lt;code&gt;.pypirc&lt;/code&gt; file
and if you have setup a username it will not prompt you for it.
Likewise, it will not prompt for a password should you choose to enter
that, and if you do consider &lt;code&gt;chmod 600&lt;/code&gt; on that file for
security reasons.&lt;/p&gt;
&lt;p&gt;In many examples you may see something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;twine upload -r pypitest dist/*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will upload all of your tarball&amp;rsquo;s located in the
&lt;code&gt;dist/&lt;/code&gt; directory. I personally choose which distribution
to upload. Either, or. Once done, goto the test PyPI and check to see
that it looks as expected, or as previously stated download the test
file and check its functioning as expected.&lt;/p&gt;
&lt;p&gt;This can be done like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install --index-url https://test.pypi.org/simple/ yourPackageName
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once happy send that baby to the production PyPI by repeating the
commands but this time specifying the &lt;code&gt;pypi&lt;/code&gt; server like
so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;twine upload -r pypi dist/package-version-i-select-explicitly.0.1.0.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You now have a production package in the wild. Check out your code at
&lt;a href="https://libraries.io/"&gt;Libraries.io&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="5-help-pypi"&gt;5. Help PyPI&lt;/h3&gt;
&lt;p&gt;None of this would be possible with the tireless work of the Python
Software Foundation and the handful of volunteers that make PyPI a
reality. Join the PSF and maybe consider a donation, or convince your
employer to contribute if they rely on python software! Without PSF and
PyPI we wouldn&amp;rsquo;t have python as we know it today. Please visit and sign
up here: &lt;a href="https://psfmember.org/"&gt;PSF&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Mentoring at CoderDojo</title><link>https://danielms.site/blog/mentoring-at-coderdojo/</link><pubDate>Mon, 26 Mar 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/mentoring-at-coderdojo/</guid><description>&lt;h2 id="coderdojo"&gt;CoderDojo&lt;/h2&gt;
&lt;p&gt;Today I volunteered my time at a local CoderDojo as a Mentor. The basic
premise of a CoderDojo is it&amp;rsquo;s a place where children between 7 and 17
can learn programming, robotics or basically any technology that might
inspire them. The kids are called &amp;rsquo;ninjas&amp;rsquo; whilst the program
co-ordinators are known as champions, and people who help out in their
area of expertise are the mentors.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="what-did-we-do"&gt;What did we do?&lt;/h3&gt;
&lt;p&gt;There was around 10-15 kids there and an almost fifty percent split
between boys and girls. Most of them were coding away on their laptops
using Scratch. Some of them had pretty good little programs; games,
stories and creative landscapes being the general theme. They also had a
&lt;a href="http://learn.makeblock.com/en/ultimate2/"&gt;Makeblock&lt;/a&gt; robot which the
kids could build into ten different configurations. It reminded me of
the K&amp;rsquo;nex toys I had as a kid only way cooler.&lt;/p&gt;
&lt;h3 id="windows-sucks-when-licences-expire"&gt;Windows sucks&amp;hellip; when licences expire&lt;/h3&gt;
&lt;p&gt;It was great to see that the library (where this dojo is housed) were
able to secure some retired laptops from the local government. But also
so painful to be a part of because they, as expected, were all Windows
based. Almost all had expired licences, missing product keys, or were
locked down with administrator access for which no password was
supplied. Thankfully, they will all now become the proud new emissaries
of linux - although one old laptop was struggling to run with Ubuntu 16.
Arch, anyone? &lt;code&gt;:smirk:&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Also, I tip my hat to those who donate to charitable causes such as
CoderDojo and give away their old laptops. Especially in suburbs where
being gifted a functional laptop can be like winning the lottery.&lt;/p&gt;
&lt;h3 id="coderdojos-one-rule-be-cool"&gt;CoderDojo&amp;rsquo;s one rule: &lt;a href="https://www.youtube.com/watch?v=k5ciSFjEN1c"&gt;Be Cool!&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Click the link for a few minute video that summarises the ethos and
idea&amp;rsquo;s behind the program.&lt;/p&gt;</description></item><item><title>TIL How Network Time Protocol Works</title><link>https://danielms.site/blog/til-how-network-time-protocol-works/</link><pubDate>Fri, 23 Mar 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/til-how-network-time-protocol-works/</guid><description>&lt;h2 id="network-time-protocol"&gt;Network Time Protocol&lt;/h2&gt;
&lt;p&gt;So today I watched a &lt;a href="https://www.youtube.com/watch?v=MDmNvVG9AnQ"&gt;talk&lt;/a&gt;
on NTP and it was amazing. I will do my best to summarise the core parts
in this post.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="what-is-it"&gt;What is it?&lt;/h3&gt;
&lt;p&gt;Network Time Protocol allows clocks between computers to be synchronised
over the internet or local area network. It was developed by [David L.
Mills]
(&lt;a href="https://en.wikipedia.org/wiki/David_L._Mills"&gt;https://en.wikipedia.org/wiki/David_L._Mills&lt;/a&gt;) in
1985 and is currently in its fourth version. Accurate to a few
milliseconds of Coordinated Universal Time, NTP is a fundamental
instrument in modern networking.&lt;/p&gt;
&lt;h3 id="why-do-we-need-it"&gt;Why do we need it?&lt;/h3&gt;
&lt;p&gt;Its pretty important that we all agree on what time it is. It is even
more important that banks and other financial institutions agree on a
universal time stamp when conducting transactions, especially if they
are ordered chronologically. Things like SSL certificates could be
spoofed or bypassed if NTP did not ensure a universal time. There is
probably many other security related issues that NTP solves but I think
the point is clear, its a necessary protocol.&lt;/p&gt;
&lt;h3 id="how-does-it-get-the-right-time"&gt;How does it get the &amp;ldquo;right&amp;rdquo; time?&lt;/h3&gt;
&lt;p&gt;Most people have heard of [atomic clocks]
(&lt;a href="https://en.wikipedia.org/wiki/Atomic_clock"&gt;https://en.wikipedia.org/wiki/Atomic_clock&lt;/a&gt;) or GPS
time. Basically, these methods are considered as accurate as humans can
calculate to &amp;lsquo;real&amp;rsquo; time. It would be simplistic to say that NTP polls
these sources for the current time - but nonetheless that&amp;rsquo;s the basic
premise.&lt;/p&gt;
&lt;p&gt;Diving deeper, NTP actually uses things called a &amp;lsquo;Stratum&amp;rsquo;. An atomic
clock is a Stratum 0, also known as a reference clock. The Stratum&amp;rsquo;s go
from 0 all the way to 16. Stratum 1 is the closest an NTP server can get
to the reference clock, and is usually within a few milliseconds. These
are referred to as primary time servers. Each Stratum refers to the
preceding number, and polls that server for its time. Stratum 3 will
synchronise to a Stratum 2 server for example. They can and often do
peer with servers in their own Stratum as a sanity check and backup. Of
note Stratum 16 means &amp;lsquo;unsynchronised&amp;rsquo;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="the-process"&gt;The Process&lt;/h3&gt;
&lt;p&gt;Roughly the process follows something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ask for the time,&lt;/li&gt;
&lt;li&gt;get the roundtrip times,&lt;/li&gt;
&lt;li&gt;figure out if you trust the response,&lt;/li&gt;
&lt;li&gt;make any adjustments,&lt;/li&gt;
&lt;li&gt;repeat every 64 seconds, forever.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="roundtrip-times"&gt;Roundtrip Times&lt;/h4&gt;
&lt;p&gt;After asking the time from a server, NTP needs to factor in how long it
took to get the response back because the time from when it sent the
response to receiving it will now be out of sync.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# The four timestamps needed for calculating the time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;t1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; timestamp of request packet
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;t2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; timestamp when server received packet
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;t3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; timestamp of servers reply transmission
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;t4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; clients response packet reception timestamp
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Example of NTP packet&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;13:11:58.155997 IP &lt;span class="o"&gt;(&lt;/span&gt;tos 0x0, ttl 56, id 42684, offset 0, flags &lt;span class="o"&gt;[&lt;/span&gt;none&lt;span class="o"&gt;]&lt;/span&gt;, proto UDP &lt;span class="o"&gt;(&lt;/span&gt;17&lt;span class="o"&gt;)&lt;/span&gt;, length 76&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cpe-110-141-196-84.vic.asp.telstra.net.ntp &amp;gt; client.local.lan.ntp: &lt;span class="o"&gt;[&lt;/span&gt;udp sum ok&lt;span class="o"&gt;]&lt;/span&gt; NTPv4, length &lt;span class="m"&gt;48&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Server, Leap indicator: &lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt;, Stratum &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;primary reference&lt;span class="o"&gt;)&lt;/span&gt;, poll &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;1024s&lt;span class="o"&gt;)&lt;/span&gt;, precision -23
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Root Delay: 0.000000, Root dispersion: 0.001953, Reference-ID: PPS^@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Reference Timestamp: 3730684254.163282214 &lt;span class="o"&gt;(&lt;/span&gt;2018/03/22 13:10:54&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# t1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Originator Timestamp: 3730684318.091658531 &lt;span class="o"&gt;(&lt;/span&gt;2018/03/22 13:11:58&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# t2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Receive Timestamp: 3730684318.125586175 &lt;span class="o"&gt;(&lt;/span&gt;2018/03/22 13:11:58&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# t3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Transmit Timestamp: 3730684318.125623665 &lt;span class="o"&gt;(&lt;/span&gt;2018/03/22 13:11:58&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# t4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Originator - Receive Timestamp: +0.033927643
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Originator - Transmit Timestamp: +0.033965133
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To calculate the current time from the servers response NTP does the
following calculation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;t4 - t1 = roundtrip time
roundtrip time / 2 = one-way latency
t3 + one-way latency = current time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next part of this is whether or not the client trusts the NTP
server. This is done in a few ways. Firstly, by sending out several
queries to several servers rather than trusting that the response from
one server is correct. NTP then favours the lowest latency and discards
any outliers. Secondly, NTP uses some statistical analysis of its
responses over a period of minutes to determines who is accurate and who
isn&amp;rsquo;t based off those statistics.&lt;/p&gt;
&lt;h4 id="make-adjustments"&gt;Make Adjustments&lt;/h4&gt;
&lt;p&gt;What NTP tries to never do is go backwards in time. Sometimes it has to
and we will get to that. But for the most part what it does it &amp;lsquo;slew&amp;rsquo;
the clock. Simply it just slows or speeds up the clock to match the
correct time and does this in a gradual way with small increments.&lt;/p&gt;
&lt;p&gt;At its max adjustment speed of 500ppm it would take 2000 seconds to make
an adjustment of just one second!&lt;/p&gt;
&lt;p&gt;Given the slow slew rate, slewing is capped to 128 milliseconds.
Anything above that cannot be slewed and must be &amp;lsquo;stepped&amp;rsquo; or jumped to
the correct time, be it forward or backward. This does not happen often
except in cases such as bringing a machine back online after maintenance
or during initial setup. Any machine that is over 1000 seconds out must
be manually configured within that threshold or it will not be able to
receive adjustments.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s Network Time Protocol in a nutshell. I had never paid much heed
to NTP prior to &lt;a href="https://twitter.com/jpotischj"&gt;Joel Potischman&amp;rsquo;s&lt;/a&gt; talk
at !!Con. He gave a great talk and it only goes for ten minutes and uses
some good graphs and visualisations that are missing from this post. If
you want to see NTP in action on your computer you can use
&lt;code&gt;tcpdump -vv port 123&lt;/code&gt; or check it out in wireshark. Whilst
writing this I found a bad response from one server that was +1023
seconds out and thus dropped as an outlier - so it does happen.&lt;/p&gt;</description></item><item><title>Scapy Snippets</title><link>https://danielms.site/blog/scapy-snippets/</link><pubDate>Wed, 28 Feb 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/scapy-snippets/</guid><description>&lt;h3 id="how-to-get-rssi-from-wlan-packet"&gt;How to get RSSI from WLAN packet&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;scapy.layers.dot11&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RadioTap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dot11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_rssi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;haslayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RadioTap&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;packet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dbm_antsignal&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;caveat: currently only tested on Ralink: RT5370 chipset&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Linux file permissions</title><link>https://danielms.site/blog/linux-file-permissions/</link><pubDate>Sun, 04 Feb 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/linux-file-permissions/</guid><description>&lt;h2 id="linux-chmod-and-chown"&gt;Linux: Chmod and Chown&lt;/h2&gt;
&lt;p&gt;This is a short introduction to basic Linux file permissions and how to set them correctly.
As a beginner, junior or experienced developer and/or administrator, you are going to need this!&lt;/p&gt;
&lt;h3 id="chmod-change-file-mode-bits"&gt;&lt;strong&gt;Chmod: Change File Mode Bits&lt;/strong&gt;&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;15:45:18 user@example temporary 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;total 12K
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drwxr-xr-x &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; users 4.0K Feb &lt;span class="m"&gt;1&lt;/span&gt; 14:29 .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drwx------ &lt;span class="m"&gt;33&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; users 4.0K Feb &lt;span class="m"&gt;3&lt;/span&gt; 15:45 ..
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rw-r--r-- &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; users &lt;span class="m"&gt;0&lt;/span&gt; Jan &lt;span class="m"&gt;21&lt;/span&gt; 12:14 &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rw-r--r-- &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; users &lt;span class="m"&gt;0&lt;/span&gt; Jan &lt;span class="m"&gt;21&lt;/span&gt; 12:14 &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rw-r--r-- &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; users &lt;span class="m"&gt;16&lt;/span&gt; Feb &lt;span class="m"&gt;1&lt;/span&gt; 14:29 file.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the above example output from left to right we have: filemode, number
of links, user, group, size, date last modified, name of file.&lt;/p&gt;
&lt;p&gt;We will focus on the filemode for now.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;drwxr-xr-x
0123456789 &amp;lt;-- this is our reference marker.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;0: is either &lt;code&gt;-&lt;/code&gt; meaning a file, or &lt;code&gt;d&lt;/code&gt;
signifying that it is a directory.&lt;/p&gt;
&lt;p&gt;1-3: represents the user or owner of the file.&lt;/p&gt;
&lt;p&gt;4-6: is reference to the group that owns the file.&lt;/p&gt;
&lt;p&gt;7-9: everybody else, or other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change the filemodes:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The command &lt;code&gt;chmod&lt;/code&gt; is used to change the mode files of
files. Its basic syntax is as follows:&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;chmod u+x file.txt&lt;/code&gt; the &lt;code&gt;u&lt;/code&gt; signifies &lt;code&gt;user&lt;/code&gt; and the &lt;code&gt;+&lt;/code&gt;
means add and the &lt;code&gt;x&lt;/code&gt; refers to execute. This can be
stacked, and used to takeaway permissions.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chmod -R g+rwx directory&lt;/code&gt; would recursively
(&lt;code&gt;-R&lt;/code&gt;) change all &lt;code&gt;group&lt;/code&gt; files within
&lt;code&gt;directory&lt;/code&gt; to read, write and execute.&lt;/p&gt;
&lt;p&gt;Importantly, if the user or group &lt;strong&gt;does not&lt;/strong&gt; own the file or directory
then the command &lt;code&gt;sudo&lt;/code&gt; must be used. Otherwise it is not
needed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Octal:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the above examples we used &lt;code&gt;rwx&lt;/code&gt; to note what
permissions we wanted to add or remove from a file. Another method is to
use the &lt;em&gt;octal&lt;/em&gt; notation; numeral.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chmod 777 file.txt&lt;/code&gt; would mean that we are giving
&lt;code&gt;rwx&lt;/code&gt; to user, group and other. How does 7 equal
&lt;code&gt;rwx&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;This is because &lt;code&gt;4&lt;/code&gt; gives read access, &lt;code&gt;2&lt;/code&gt; is
write and &lt;code&gt;1&lt;/code&gt;, execute. When using octal we add the numbers
together, so if we wanted read, write and execute we simply add 4,2,1
which equals 7.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chmod 765 xxx.txt&lt;/code&gt; would mean: - user: read, write and
execute -group: read and write - other: read and execute&lt;/p&gt;
&lt;p&gt;Using this is very simple, but how do I remove permissions? Previously,
we would &lt;code&gt;chmod u-x&lt;/code&gt; i.e. we used the &lt;code&gt;-&lt;/code&gt; to
signify removal of privilege. Using octal we just set new filemode to
what we want and it will add or subtract the mode accordingly. An
example below:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chmod a+rwx&lt;/code&gt; == give read, write and execute to all
(user,group a)nd other) &lt;em&gt;or&lt;/em&gt; &lt;code&gt;777&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But if we only wanted the user to have &lt;code&gt;rwx&lt;/code&gt; and everyone
else read and write we could call: &lt;code&gt;chmod 755&lt;/code&gt;. To use
non-octal here would be &lt;code&gt;chmod go-x&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Important note: As with all things linux there are many more advanced
features. We are just touching the surface here.&lt;/p&gt;
&lt;h3 id="chown-change-file-owner-and-group"&gt;Chown: Change File Owner and Group&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;chown&lt;/code&gt; command deals with changing the ownership of
files and directories.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;drwxrwxr-x 1 test users 14.0K Jan 21 12:14 dir_1

-rw-r--r-- 1 admin root 16 Feb 1 14:29 file.txt
[filemode] [owner] [grp] 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Above we have a break down of the important parts of our
&lt;code&gt;ls -la&lt;/code&gt; output.&lt;/p&gt;
&lt;p&gt;In the faked output we have &lt;code&gt;admin&lt;/code&gt; in the first position
which is representing the owner/ creator of &lt;code&gt;file.txt&lt;/code&gt;. In
postilion two we have the group that &lt;code&gt;file.txt&lt;/code&gt; belongs to,
in this case its &lt;code&gt;root&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The owner of a file can make changes to the filemode and ownership of a
file without super user privileges. However, if another user wanted to
change the ownership they would require this access.&lt;/p&gt;
&lt;p&gt;To make a change is as simple as
&lt;code&gt;chown [user]:[group] [file/s]&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo chown root file.txt # 1.
$ ls -l 
-rw-r--r-- 1 root root 16 Feb 1 14:29 file.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The simplest usage of &lt;code&gt;chown&lt;/code&gt; is the command plus the new
owner and file or directory to be affected.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo chown -R admin dir_1 # 2.
$ ls -l
drwxrwxr-x 1 admin users 14.0K Jan 21 12:14 dir_1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we wanted to make the changes to all files and directories inside a
directory we can add &lt;code&gt;-R&lt;/code&gt;. This is a recursive function
with the same syntax as &lt;code&gt;chmod&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo chown admin:admin file.txt 
$ ls -l
-rw-r--r-- 1 admin admin 16 Feb 1 14:29 file.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To specify a new owner and group the use of &lt;code&gt;:&lt;/code&gt; between the
owner and group can be used. &lt;code&gt;admin:users&lt;/code&gt;, &lt;code&gt;root:root&lt;/code&gt;
and so on.&lt;/p&gt;</description></item><item><title>SSH in a nutshell</title><link>https://danielms.site/blog/ssh-in-a-nutshell/</link><pubDate>Sun, 28 Jan 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/ssh-in-a-nutshell/</guid><description>&lt;h2 id="ssh"&gt;SSH&lt;/h2&gt;
&lt;h3 id="definition"&gt;Definition&lt;/h3&gt;
&lt;p&gt;Straight from the man pages:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;SSH (SSH client) is a program for logging into a remote machine and
for executing commands on a remote machine. It is intended to provide
secure encrypted communications between two untrusted hosts over an
insecure network. X11 connections, arbitrary TCP ports and UNIX-domain
sockets can also be forwarded over the secure channel.&lt;/em&gt;&lt;/p&gt;
&lt;h4 id="setup-old-school-method"&gt;Setup (Old School Method)&lt;/h4&gt;
&lt;p&gt;On a new machine we need to setup up ssh. This includes installation if
its not installed by default (i.e. Arch Linux). It also includes the
creation of our private and public keys (maybe write a post about the
basics of pgp).&lt;/p&gt;
&lt;p&gt;To create a new key we use &lt;code&gt;ssh-keygen&lt;/code&gt;. Taken straight
from the man pages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ssh-keygen&lt;/strong&gt; generates, manages and converts authentication keys
for ssh. It can create keys for use by SSH protocols versions one
and two. Protocol one is depreciated and should not be used.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When creating a key we need to specify the type. To do this we use the
&lt;code&gt;-t&lt;/code&gt; flag followed by one of the following specifications:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RSA, DSA, ECDSA, ED25519&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If no flag is present it will default to RSA which is what we want.&lt;/p&gt;
&lt;p&gt;Also important is the number of bits in the key to generate. By default
it will generate a key length of 2048 bits. In this article we will not
discuss non-RSA configurations which require different lengths (refer to
the man pages for more info). Generally it is recommended to use 4096
bits of entropy, to do that we use the &lt;code&gt;-b&lt;/code&gt; followed by the
number, in this case 4096.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;ssh-keygen -t rsa -b 4096&lt;/code&gt; will create the ssh
key.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://danielms.site/img/sshcmdline.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;It will ask for a password, this is up to the user but it does not limit
your ability to login without entering password as we discuss below. It
does however, offer protection should your device be stolen or
compromised. The password can be bruteforced but it will give you time
to manually for to your administered servers and delete the public key
that matches the now stolen private key.&lt;/p&gt;
&lt;h4 id="key-based-authentication"&gt;Key Based Authentication&lt;/h4&gt;
&lt;p&gt;Key based authentication allows seamless interaction between disparate
hosts over the network. Generally, to ssh into a remote server the
client will need to enter the servers password. Using ssh keys (unless
they too are configured with a password) negates this.&lt;/p&gt;
&lt;p&gt;Below we see how to transfer the clients public key to the remote
server.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;scp ~/.ssh/id_rsa.pub user@remote.server.org:/home/user/.ssh/clients_public_key.pub
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;scp&lt;/code&gt; is the Secure Copy program. It uses ssh for the data
transfer and provides the same security. The basic syntax is to call
&lt;code&gt;scp&lt;/code&gt; then append the file to be copied, followed by the
destination to send it to. The destination is punctuated by a colon
&lt;code&gt;:&lt;/code&gt; coupled with the location on the remote server in which
the file should be saved to (as seen above).&lt;/p&gt;
&lt;p&gt;Next we need to authorize the key we have sent to the remote server. If
we do not authorize it, it will not allow us to login using the key,
i.e. we will be prompted for a password each time. To do that we can
input the following:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cat ~/.ssh/name_of_new_key.pub &amp;gt;&amp;gt; ~/.ssh/authorized_keys&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Firstly, in linux we do not need to append a file name at the end so
this works as expected. And secondly, the &lt;code&gt;&amp;gt;&amp;gt;&lt;/code&gt; will create
the file for us if it does not already exist.&lt;/p&gt;
&lt;p&gt;Finally, it is important to set permissions on the new directory and its
keys. Permissions is another topic, but we want the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod 700 /home/user
chmod 700 /home/user/.ssh
chmod 600 /home/user/.ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We want our &lt;code&gt;/home&lt;/code&gt; and &lt;code&gt;/home/.ssh&lt;/code&gt; to have
read, write and execute only by the user and
&lt;code&gt;../authorized_keys&lt;/code&gt; only read and writable by the user.
This is on the remote server. We can also now delete the public key from
within &lt;code&gt;~/.ssh/&lt;/code&gt; as it is saved to the authorized keys
although this is optional.&lt;/p&gt;
&lt;h4 id="setup-v2-new-school"&gt;Setup V2 (New School)&lt;/h4&gt;
&lt;p&gt;After explaining the old way or more traditional (sometimes better) way
we turn to &lt;code&gt;ssh-copy-id&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To do all of that in one command we can just enter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh-copy-id user@remote_server
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And, it will do everything in about 2 seconds (after you authenticate
with the remotes password). To test it without committing anything use
the &lt;code&gt;-n&lt;/code&gt; flag (dry run). It is important to know what is
happening in the background though, so do the old school method to get a
better feel for linux &amp;ndash; if you are old hat, just use this.&lt;/p&gt;
&lt;h4 id="further-security-measures"&gt;Further Security Measures&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Disable Password Authentication&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After we have setup key based authentication on the remote server we may
want to close off all non-key authenticated access via ssh. This means
that only those with authorized keys will be able to access the server.
In a home network this may be overkill (dependent on the situation or
security posture/threat model you need, or face) but if you are using
ssh to administer a VPS this should be the minimum standard.&lt;/p&gt;
&lt;p&gt;To do this we need to edit our &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;
&lt;strong&gt;back it up first!&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To do this we just change &lt;code&gt;PasswordAuthentication&lt;/code&gt; to
&lt;code&gt;no&lt;/code&gt; inside the file. After the change we need to restart
the ssh daemon to make the change immediate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Change SSH Default Port&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Many of the automated botnets automatically search the web looking for
open ports, and exploiting them by entering common default credentials.
Whilst changing the ssh port from 22 to something else is security by
obscurity it does enough to make elevate you above the &amp;rsquo;low hanging
fruit&amp;rsquo; category.&lt;/p&gt;
&lt;p&gt;To alter the default port the &lt;code&gt;sshd_config&lt;/code&gt; needs to be
edited.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#Port 22 &amp;lt;---- Uncomment this and change port
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You need to be root to do this. And the ssh daemon must be restarted.
&lt;code&gt;systemctl restart sshd.service&lt;/code&gt; on systemd.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Auto Deny/Fail2Ban&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To access a host via ssh it needs to be open over the network. This is
adds risk and creates an attack vector. &lt;em&gt;fail2ban&lt;/em&gt; is a service that
attempts to mitigate this by altering your firewalls configuration after
a set number of unsuccessful login attempts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Port Knocking&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A method to surreptitiously open the port to ssh. By making connection
attempts (&amp;ldquo;knocking&amp;rdquo;) on ports in a predefined manner the firewall
allows access to ssh. Again, this is security through obscurity but
coupled with several strategies does help to harden the system. Nmap can
ascertain if a host has port knocking enabled.&lt;/p&gt;</description></item><item><title>Rsync cheatsheet</title><link>https://danielms.site/blog/rsync-cheatsheet/</link><pubDate>Wed, 24 Jan 2018 00:00:00 +0000</pubDate><guid>https://danielms.site/blog/rsync-cheatsheet/</guid><description>&lt;p&gt;This is a short primer on the most simple of rsync&amp;rsquo;s capabilities.&lt;/p&gt;
&lt;p&gt;Rsync is a fast and extraordinarily versatile file copying tool. It uses
a delta transfer algorithm, which reduces the amount of data sent over
the network by sending only the differences between the source and
destination files. It can contact remote systems via SSH or through a
rsync daemon over TCP.&lt;/p&gt;
&lt;h3 id="basic-syntax"&gt;Basic Syntax&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;-r&lt;/code&gt;: The &amp;lsquo;r&amp;rsquo; flag denotes that the operation is recursive.
It will copy across all files and folders inside the source directory.
However, if the source destination does not have a trailing slash it
will copy the folder, rather than the files inside the folder.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-a&lt;/code&gt; Archive mode is superior to recursive mode as it will
sustain symbolic links, special and device files, modification times,
group, owner and permissions.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--dry-run&lt;/code&gt; or &lt;code&gt;-n&lt;/code&gt; As a sanity check it is
worth checking that your command is going to do what you &lt;em&gt;think&lt;/em&gt; it is
going to do. The dry run will not execute the command. It can (read:
should) be coupled with the next command.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-v&lt;/code&gt;: Verbose will print out the what actions were
undertaken by the command. If &lt;code&gt;-n&lt;/code&gt; is not coupled with a
verbose flag it will print nothing to the screen.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--delete&lt;/code&gt;: Will remove extra items in the destination
folder that do not exist in the source directory. &lt;strong&gt;Caution:&lt;/strong&gt; This can
lead to complete deletion of the destination folder if incorrectly
implemented.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-z&lt;/code&gt;: When transferring across the network rsync provides a
compression option to save on bandwidth.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-P&lt;/code&gt;: Outputs a progress bar to the terminal.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;-H&lt;/code&gt;: Preserves hard links - very useful for back ups and snap shots as files that don&amp;rsquo;t change are often hard linked to the original file.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--stats&lt;/code&gt;: will output a summary of what was sent. Use &lt;code&gt;--stats -h&lt;/code&gt; to get a human readable output as seen below.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rsync with --stats -h&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sending incremental file list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Number of files: 2,265 &lt;span class="o"&gt;(&lt;/span&gt;reg: 2,264, dir: 1&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Number of created files: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Number of deleted files: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Number of regular files transferred: &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Total file size: 100.20M bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Total transferred file size: &lt;span class="m"&gt;0&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Literal data: &lt;span class="m"&gt;0&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Matched data: &lt;span class="m"&gt;0&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;File list size: 65.50K
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;File list generation time: 0.001 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;File list transfer time: 0.000 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Total bytes sent: 108.52K
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Total bytes received: &lt;span class="m"&gt;17&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sent 108.52K bytes received &lt;span class="m"&gt;17&lt;/span&gt; bytes 19.73K bytes/sec
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;total size is 100.20M speedup is 923.17
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--exclude=important_file.txt&lt;/code&gt;: Can be used to omit files
or directories from being synced.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--exclude=backups/ --include=backups/most_recent&lt;/code&gt;: Inside
the exclusion we can explicitly include certain file, folders or
patterns that fall inside the broader exclude.&lt;/p&gt;
&lt;p&gt;example:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -azvnP source_dir/ destination_dir/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="syncing-across-remote-systems"&gt;Syncing Across Remote Systems&lt;/h3&gt;
&lt;p&gt;Syncing can be either a &amp;ldquo;pull&amp;rdquo; or &amp;ldquo;push&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;In this example we are&lt;/em&gt; copying the directory, not just its contents and sending it from the local system to a remote system.&lt;/p&gt;
&lt;p&gt;To copy the entire folder and not just the files within, we must &lt;strong&gt;omit&lt;/strong&gt; the trailing slash.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;push&amp;rdquo;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -a ~/local_source_dir username@remote_host:/home/username/destination_dir
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we are retrieving data from a remote server and pulling it down to our local system. Again, we are taking the &lt;em&gt;entire&lt;/em&gt; directory so there&amp;rsquo;s no trailing slash.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;pull&amp;rdquo;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rsync -a username@remote_host:/home/username/directory_we_want where_we_want_it_locally
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="ssh"&gt;SSH&lt;/h3&gt;
&lt;p&gt;Syncing between systems is made much easier if key based authentication
is enabled. If not, the user will be prompted with a password.&lt;/p&gt;
&lt;p&gt;To use &lt;code&gt;rsync&lt;/code&gt; over a &lt;code&gt;ssh&lt;/code&gt; connection we need to specify the protocol &lt;code&gt;rsync&lt;/code&gt; needs to use.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rsync -aznvPH -e &amp;quot;ssh -i ~/ec2_keyfile.pem&amp;quot; user@remote:/home/folder /tmp/local_system/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-e&lt;/code&gt; flag above, tells &lt;code&gt;rsync&lt;/code&gt; to use a command, or in this case, another protocol to tunnel over.&lt;/p&gt;
&lt;p&gt;We wrap the &lt;code&gt;-e&lt;/code&gt; flag command in quotations to encapsulate our private key - this is particularly useful when accessing AWS. Any &lt;code&gt;.ssh/config&lt;/code&gt; settings are also respected by &lt;code&gt;rsync&lt;/code&gt; making commonly accessed systems far easier to use.&lt;/p&gt;</description></item></channel></rss>