<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://bigkevmcd.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://bigkevmcd.github.io/" rel="alternate" type="text/html" /><updated>2025-10-20T08:22:23+00:00</updated><id>https://bigkevmcd.github.io/feed.xml</id><title type="html">Cerebrations</title><subtitle>Random writings from a software craftsman.
</subtitle><entry><title type="html">SUSE Hackweek 2024</title><link href="https://bigkevmcd.github.io/software/2024/11/22/hackweek.html" rel="alternate" type="text/html" title="SUSE Hackweek 2024" /><published>2024-11-22T16:22:37+00:00</published><updated>2024-11-22T16:22:37+00:00</updated><id>https://bigkevmcd.github.io/software/2024/11/22/hackweek</id><content type="html" xml:base="https://bigkevmcd.github.io/software/2024/11/22/hackweek.html"><![CDATA[<p>Since the <a href="https://www.linkedin.com/posts/richardsonalexis_hi-everyone-i-am-very-sad-to-announce-activity-7160295096825860096-ZS67">collapse of WeaveWorks</a> at the end of 2023, things have been hectic.</p>

<p>I spent a couple of months interviewing, and eventually landed up at SUSE working on Rancher.</p>

<p>In some ways, Rancher is similar to Weave GitOps, but without almost all of the GitOps, and a lot less declarative, most things are done as “ClickOps”.</p>

<p>It’s been tough finding a place at SUSE, the Rancher codebase is not what I’d write, and there are bits that are painfully bad, but there are a lot of smart folks working on it, hammering it into shape.</p>

<p>Hackweek is an annual thing at SUSE (we had a very similar week at Heroku) in which we get to hack on whatever we want.</p>

<p>I opted to work on a number of different things this year:</p>

<ul>
  <li>Getting <a href="https://github.com/gitops-tools/gitopssets-controller/">gitopssets</a> going again, it’s been in maintenance mode since last December.</li>
  <li>Replacing the old <a href="https://github.com/bigkevmcd/tekton-polling-operator">Tekton Poller</a> which I wrote about in <a href="/tekton/polling/2020/07/23/tekton-poller.html">July 2020</a>.</li>
  <li>Writing <a href="https://tinygo.org/">TinyGo</a> for my <a href="https://www.solder.party/docs/keyboard-featherwing/">Keyboard Featherwing</a> I write a lot of Go, but I’ve never written any “Tiny” Go for embedded devices, despite having written a fair bit of Arduino C++ and some CircuitPython.</li>
  <li>Implementing a small Forth interpreter.</li>
</ul>

<p>That’s a lot of things to get on with…how’d it go?</p>

<h2 id="gitopssets">GitOpsSets</h2>

<p>GitOpsSets had been in maintenance-only for almost a year, only dependencies being updated.</p>

<p>I had forked it when WeaveWorks folded, and rearranged the packages, and some minor tweaks, but I hadn’t actually run it, so…getting it going took a little bit of effort, but it’s working now.</p>

<p>I fixed the <code class="language-plaintext highlighter-rouge">GitRepository</code> generator to work with post 2.2 Flux.</p>

<p>The <a href="https://github.com/gitops-tools/gitopssets-controller/compare/main...cel-repeats">CEL integration branch</a> which would replace JSON-path in places where it makes sense is still active, I will do the work to get this to land, it will benefit from recent work I’ve done elsewhere in the CEL ecosystem.</p>

<p>I started a few separate explorations in GitOpsSets to see what is possible.</p>

<h3 id="configurable-cluster-generation">Configurable Cluster generation</h3>

<p>Is it possible to generate from either GitOps Clusters (WeaveWorks) or Rancher Clusters?</p>

<p>I got quite far down the route, replacing the mechanism for querying clusters, and allowing a GVK to be provided. I stopped at the point where I realised that this could go in a few different directions, and one of them was a generic resource querying mechanism.</p>

<p>The caveat is that it becomes harder to select “ready” clusters, I will come back to this, and maybe it’s time to give up on WeaveWorks GitopsClusters (but there is now movement in the weave-gitops repo!), I’ll figure out the right way forward for this, as it’s a core element of the cluster-reflector, cluster-bootstrapper and cluster-generator workflow that I think is crucial for managing clusters at scale.</p>

<h3 id="generation-from-keycloak">Generation from Keycloak</h3>

<p>Over the past few years, I’ve seen a large number of enterprise users running Keycloak, while I’m not its biggest fan, it definitely does the job for folks, and so, can we generate from Keycloak users and groups within a realm?</p>

<p>This would allow generation of users and groups by querying they Keycloak API.</p>

<p>Ideally this would allow declarative generation of RBAC-type resources, e.g. RoleBindings by querying Keycloak as the source of truth.</p>

<p>I need to figure out what a generic version of this looks like, for example, is it valuable to expose a <code class="language-plaintext highlighter-rouge">UsersGenerator</code> which can query Keycloak and other things like OpenLDAP?</p>

<h3 id="external-generators">External generators</h3>

<p>I made a start on external-generators, i.e. support for referencing generators which are <code class="language-plaintext highlighter-rouge">Services</code> that accept a specific format of request, and return a specific response format.</p>

<p>I’ve got the API defined, and implemented an example, I just need to hook this up to the generator resolution mechanism, effectively you should be able to reference an external generator by name, and the code will find a <code class="language-plaintext highlighter-rouge">Generator</code> resource that is an API endpoint that we can submit the generator parameters to, and receive a response from.</p>

<p>This is very, very similar to the <a href="https://tekton.dev/docs/triggers/clusterinterceptors/">approach in Tekton</a>.</p>

<p>Most of this is already available via the APIClient generator in GitOpsSets, but external generators could integrate paginated responses in a standardised way which would be much more complicated to add to the APIClient generator.</p>

<p>I’m not even particularly wedded to the idea of an external <code class="language-plaintext highlighter-rouge">Generator</code> resource type, this might just be possible via an <code class="language-plaintext highlighter-rouge">ExternalGenerator</code> generator, which takes a URL and queries it rather than going via a <code class="language-plaintext highlighter-rouge">Generator</code> resource.</p>

<p>If done well, this might negate the need for a specific <code class="language-plaintext highlighter-rouge">Keycloak</code> generator, as it could just be an external generator.</p>

<h2 id="gitpoller">GitPoller</h2>

<p>Four and a half years ago I wrote a <a href="https://github.com/bigkevmcd/tekton-polling-operator">tool</a> to allow you to trigger Tekton pipelines by polling a Git API for changes.</p>

<p>This was written because I saw a lot of requests by folks who wanted to use Tekton Triggers, but couldn’t easily expose an <a href="https://tekton.dev/docs/triggers/eventlisteners/">EventListener</a> to the internet.</p>

<p>The design for the tool was not great, it was too tightly coupled to Tekton Pipelines, which added maintenance cost, at the time I thought this would reduce the cost of setting it up, but…the tradeoff wasn’t worth it.</p>

<p>I had started working on a replacement fairly early on, and after some experimentation I had decided on the shape of <a href="https://github.com/gitops-tools/gitpoller-controller/commit/461a4219077c6740303040c6469d1a5b0eb424d3">it</a> with a reduction in the coupling the goal.</p>

<p>GitPoller had sat unreleased for the best part of three years, it needed a bit of modernisation, and translation of the original experimental code into more modern thinking.</p>

<p>It polls an upstream GitHub or GitLab repository, and when the commit changes it sends a <a href="https://cloudevents.io/">CloudEvent</a> with the response body from the detected change, this required a <a href="https://github.com/tektoncd/triggers/pull/1469">change</a> to Tekton Triggers to respond appropriately to CloudEvents.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">polling.gitops.tools/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">PolledRepository</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">polledrepository-sample</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">poller-demo</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">url</span><span class="pi">:</span> <span class="s">https://github.com/bigkevmcd/go-demo.git</span>
  <span class="na">ref</span><span class="pi">:</span> <span class="s">main</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">github</span>
  <span class="na">frequency</span><span class="pi">:</span> <span class="s">5m</span>
  <span class="na">endpoint</span><span class="pi">:</span> <span class="s">https://el-polling-listener.poller-demo.svc.cluster.local:8080</span>
</code></pre></div></div>

<p>This example will poll the main branch in that repo every 5 minutes, and send notifications to the tekton event listener in the endpoint.</p>

<p>It isn’t complete yet, there are a few things I need to do including providing support for custom CAs so that you can talk to a private GitHub/GitLab installation, but instead of talking to Tekton Pipelines, it now talks to Tekton Triggers, so you can use the full power of Triggers to drive your pipelines, including parsing the body using CEL.</p>

<h2 id="tinygo">TinyGo</h2>

<p>I’ve written a fair bit of Arduino code, and some CircuitPython too.</p>

<p>I have a <strong>lot</strong> of micro-controllers, they’re really useful for teaching kids how computers work, and building small projects.</p>

<p>Despite this, I have never written any Go for a micro-controller, despite having looked at TinyGo several times.</p>

<p>I decided to change that, and I set a goal of getting some code to drive my <a href="https://www.solder.party/docs/keyboard-featherwing/">Keyboard Featherwing</a> which is a small keyboard/screen based board that accepts an Adafruit Feather micro-controller.</p>

<p>The device has an SD Card, screen with touch-capability, a 35 button keyboard, and 5 buttons including a joystick.</p>

<p>Debugging embedded code is hard, I can’t just write a test and stick strategic <code class="language-plaintext highlighter-rouge">log.Printf</code> calls in to see what’s failing, it needs to be compiled and flashed to the device and then the serial device monitored for those same logging messages.</p>

<p>I did manage to get the touch screen working, it’s a <a href="https://www.ti.com/product/TSC2004">TI 2004</a> and there’s Arduino code to work from.</p>

<p>The conversion from Arduino C++ to Go wasn’t very hard, it took a few hours.</p>

<div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">"TSC2004.h"</span><span class="cp">
</span>
<span class="n">TSC2004</span> <span class="n">ts</span><span class="p">;</span>

<span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">9600</span><span class="p">);</span>

  <span class="n">ts</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">loop</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ts</span><span class="p">.</span><span class="n">touched</span><span class="p">())</span> <span class="p">{</span>
    <span class="k">return</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">const</span> <span class="n">TS_Point</span> <span class="n">p</span> <span class="o">=</span> <span class="n">ts</span><span class="p">.</span><span class="n">getPoint</span><span class="p">();</span>

  <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"("</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", "</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">", "</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">z</span><span class="p">);</span>
  <span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s">")"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Gets converted to this Go:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"fmt"</span>
	<span class="s">"machine"</span>
	<span class="s">"time"</span>

	<span class="s">"github.com/bigkevmcd/tiny-go/tsc2004"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">machine</span><span class="o">.</span><span class="n">I2C0</span><span class="o">.</span><span class="n">Configure</span><span class="p">(</span><span class="n">machine</span><span class="o">.</span><span class="n">I2CConfig</span><span class="p">{</span>
		<span class="n">SDA</span><span class="o">:</span> <span class="n">machine</span><span class="o">.</span><span class="n">SDA_PIN</span><span class="p">,</span>
		<span class="n">SCL</span><span class="o">:</span> <span class="n">machine</span><span class="o">.</span><span class="n">SCL_PIN</span><span class="p">,</span>
	<span class="p">})</span>

	<span class="n">tsc</span> <span class="o">:=</span> <span class="n">tsc2004</span><span class="o">.</span><span class="n">NewI2C</span><span class="p">(</span><span class="n">machine</span><span class="o">.</span><span class="n">I2C0</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">tsc</span><span class="o">.</span><span class="n">Begin</span><span class="p">();</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"failed to initialise"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="k">for</span> <span class="p">{</span>
		<span class="n">touched</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">tsc</span><span class="o">.</span><span class="n">Touched</span><span class="p">()</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"error reading touch state "</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
			<span class="k">continue</span>
		<span class="p">}</span>

		<span class="k">if</span> <span class="o">!</span><span class="n">touched</span> <span class="p">{</span>
			<span class="k">continue</span>
		<span class="p">}</span>

		<span class="n">point</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">tsc</span><span class="o">.</span><span class="n">Point</span><span class="p">()</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
			<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="s">"error reading touch point "</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
			<span class="k">continue</span>
		<span class="p">}</span>

		<span class="n">fmt</span><span class="o">.</span><span class="n">Println</span><span class="p">(</span><span class="n">point</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The Go code is a little bit messier because of the error handling, but the C++ code isn’t doing <em>any</em> error handling, I know which I prefer.</p>

<p>This is basically talking <a href="https://tinygo.org/docs/concepts/peripherals/i2c/">I2C</a> to the correct port and accessing the registers to send appropriate events.</p>

<p>I have started on code to read the keyboard, and hopefully I can get that working, which would let me connect it to my other project…</p>

<h2 id="implementing-a-small-forth-interpreter">Implementing a small Forth interpreter</h2>

<p>I like the simplicity and explicitness of Forth, it works nicely on very low power machines…thus making it ideal for running on micro-controllers.</p>

<p>It’s a rite of passage for Forth developers to write their own Forth, to understand the combination of parsing, dictionaries and stacks that combine to make an implementation.</p>

<p>The TSC2004 code I struggled to TDD, because I didn’t really know where I was going with the code, I did design it in a way that does make it easy to test (and I’ve written tests).</p>

<p>The tiny-forth code is TDDed, it’s nice to actually create some code from scratch, with no framework or anything, just straight up Go stdlib code.</p>

<p>While the dictionary and stack are fairly easy, I use stacks a fair bit in my Go code for things, and linked-lists are trivial, it turns out the parsing and tokenisation of stdin is the hardest part.</p>

<p>While it’s trivial to read a line and easy enough to tokenise it and my tests can use <code class="language-plaintext highlighter-rouge">strings.NewReader()</code> as a stand-in for the console, it turns out parsing Forth input isn’t quite that simple, especially once you get past the first steps of parsing tokens and figuring out if they’re known words or integers to be put on the stack.</p>

<p>Parsing strings, and floats and other things adds complexity, and I haven’t gotten as far as “defining words” i.e. <code class="language-plaintext highlighter-rouge">CREATE...DOES&gt;</code>.</p>

<p>It’s still really interesting, and there’s not a massive amount of code (which is still littered with TODOs that mark out design decisions not yet solidified).</p>

<h2 id="summary">Summary</h2>

<p>All in all it’s been a fun week, I enjoyed all the projects I worked on.</p>

<p>It’s easy to forget how much I prefer working on TDD written code, the designs are flexible, changing things is easier, and I find that I make more explicit design decisions.</p>

<p>For example, these are two of the outstanding <strong>TODOs</strong>.</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">newInterpreter</span><span class="p">(</span><span class="n">output</span> <span class="n">consoleOutput</span><span class="p">)</span> <span class="o">*</span><span class="n">forthInterpreter</span> <span class="p">{</span>
	<span class="n">i</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">forthInterpreter</span><span class="p">{</span><span class="n">stack</span><span class="o">:</span> <span class="n">newStack</span><span class="p">[</span><span class="kt">int</span><span class="p">](),</span> <span class="n">output</span><span class="o">:</span> <span class="n">output</span><span class="p">}</span>
	<span class="n">i</span><span class="o">.</span><span class="n">addWord</span><span class="p">(</span><span class="s">"+"</span><span class="p">,</span> <span class="n">i</span><span class="o">.</span><span class="n">word_add</span><span class="p">)</span>
    <span class="c">// `add.addWord` adds more words which are methods on the `forthInterpreter`</span>
    <span class="c">// struct. </span>
<span class="p">}</span>

<span class="c">// TODO: Shift this to a separate dictionary struct?</span>
<span class="k">func</span> <span class="p">(</span><span class="n">i</span> <span class="o">*</span><span class="n">forthInterpreter</span><span class="p">)</span> <span class="n">find</span><span class="p">(</span><span class="n">s</span> <span class="kt">string</span><span class="p">)</span> <span class="o">*</span><span class="n">executionToken</span> <span class="p">{</span>
    <span class="c">// Iterate through the linked-list of execution tokens.</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// TODO: Should these be standalone functions that accept an interpreter?</span>
<span class="k">func</span> <span class="p">(</span><span class="n">i</span> <span class="o">*</span><span class="n">forthInterpreter</span><span class="p">)</span> <span class="n">word_add</span><span class="p">()</span> <span class="kt">error</span> <span class="p">{</span>
</code></pre></div></div>

<p>Both of them have design implications, not major ones, but I can refactor with the tests behind me to ensure I don’t break anything.</p>

<p>I have decided to take on maintenance of GitOpsSets again - I really, really want to land support for SSA and the external generation mechanism.</p>

<p>I’ve watched as folks fork the old Tekton Poller, and <em>probably</em> realise that it needs too much work to get going while I had a replacement in progress and it needed a push to get it over the line.</p>

<p>The work to harden the GitPoller shouldn’t be massive, and I can do that in a few evenings, it turns out to be pretty useful for testing things in Tekton, because you don’t need to change anything to trigger a push, just delete and recreate the GitPoller.</p>

<p>I enjoyed the TinyGo, it’s slightly different from the main Go compiler, but the same techniques for testing it work, so once I get the hang of it, I should be able to TDD it as everything else.  Integration tests will be a bit trickier, but I think they’re doable.</p>

<p>I did write a little bit of Zig, reimplementing a simple CPU in Zig, it’s definitely different from most of my current toolkit of languages, but I didn’t write a lot of code - I can see me coming back to this.</p>

<div class="language-zig highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">test</span> <span class="s">"SyntheticCpu step"</span> <span class="p">{</span>
    <span class="k">const</span> <span class="n">multiply</span><span class="p">:</span> <span class="p">[]</span><span class="k">const</span> <span class="kt">u8</span> <span class="o">=</span> <span class="o">&amp;.</span><span class="p">{</span> <span class="mi">0x04</span><span class="p">,</span> <span class="mi">0x00</span><span class="p">,</span> <span class="mi">0xc9</span><span class="p">,</span> <span class="mi">0xff</span><span class="p">,</span> <span class="mi">0xff</span><span class="p">,</span> <span class="mi">0xff</span><span class="p">,</span> <span class="mi">0x12</span><span class="p">,</span> <span class="mi">0x00</span><span class="p">,</span> <span class="mi">0xff</span><span class="p">,</span> <span class="mi">0xff</span><span class="p">,</span> <span class="mi">0xff</span><span class="p">,</span> <span class="mi">0xff</span><span class="p">,</span> <span class="mi">0x08</span><span class="p">,</span> <span class="mi">0x00</span><span class="p">,</span> <span class="mi">0x00</span><span class="p">,</span> <span class="mi">0x00</span> <span class="p">};</span>
    <span class="k">var</span> <span class="n">cpu</span> <span class="o">=</span> <span class="n">SyntheticCpu</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="n">multiply</span><span class="p">);</span>
    <span class="k">try</span> <span class="n">expectEqual</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">cpu</span><span class="p">.</span><span class="py">ip</span><span class="p">);</span>

    <span class="n">cpu</span><span class="p">.</span><span class="nf">step</span><span class="p">();</span>

    <span class="k">try</span> <span class="n">expectEqual</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="n">cpu</span><span class="p">.</span><span class="py">ip</span><span class="p">);</span>
    <span class="k">try</span> <span class="n">expectEqual</span><span class="p">(</span><span class="mi">0xc9</span><span class="p">,</span> <span class="n">cpu</span><span class="p">.</span><span class="py">regs</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>

    <span class="n">cpu</span><span class="p">.</span><span class="nf">step</span><span class="p">();</span>

    <span class="k">try</span> <span class="n">expectEqual</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="n">cpu</span><span class="p">.</span><span class="py">ip</span><span class="p">);</span>
    <span class="k">try</span> <span class="n">expect</span><span class="p">(</span><span class="n">cpu</span><span class="p">.</span><span class="py">flags</span><span class="p">[</span><span class="n">CPU_FLAG_OF</span><span class="p">]</span> <span class="o">==</span> <span class="kc">false</span><span class="p">);</span>
    <span class="k">try</span> <span class="n">expectEqual</span><span class="p">(</span><span class="mi">55</span><span class="p">,</span> <span class="n">cpu</span><span class="p">.</span><span class="py">regs</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Finally, the Forth interpreter might fall by the wayside, it was an interesting experiment, but there are already micro-controller Forths, if anything has to get dropped from this week, that’ll be the thing.</p>

<p>I really enjoyed Hackweek, and I’m looking forward to getting back to customer problems, hopefully finally get a few branches over the line.</p>]]></content><author><name></name></author><category term="software" /><summary type="html"><![CDATA[Since the collapse of WeaveWorks at the end of 2023, things have been hectic.]]></summary></entry><entry><title type="html">How to Design Programs - in Forth</title><link href="https://bigkevmcd.github.io/forth/2022/11/05/forth-htdp.html" rel="alternate" type="text/html" title="How to Design Programs - in Forth" /><published>2022-11-05T10:22:55+00:00</published><updated>2022-11-05T10:22:55+00:00</updated><id>https://bigkevmcd.github.io/forth/2022/11/05/forth-htdp</id><content type="html" xml:base="https://bigkevmcd.github.io/forth/2022/11/05/forth-htdp.html"><![CDATA[<p><a href="https://htdp.org/">How to Design Programs</a> is a classic text, illustrating how to implement small projects, specifically breaking them down into small functions.</p>

<p>It’s originally written for <a href="https://en.wikipedia.org/wiki/Scheme_(programming_language)">Scheme</a> which is definitely a high-level language.</p>

<p>When I first started writing software for a living, I wrote a fair bit of code in C and 8086 Assembly, specifically I wrote code for interfacing with serial terminals, this is before the Internet, and before TCP/IP become ubiquitous and so “green screens” were common in settings, which were <a href="https://en.wikipedia.org/wiki/TTY">TTYs</a> connected to Unix systems via terminal interfacing hardware.</p>

<p>At this time, C was the dominant language for this kind of work, with some bits dropping into assembly where the C wasn’t quite fast enough, which on base level 80286 and 802386 hardware of the era, wasn’t that uncommon.</p>

<p>Around this time, I learned of a language called <a href="https://en.wikipedia.org/wiki/Forth_(programming_language)">Forth</a>, and I remember experimenting with <a href="https://pygmy.utoh.org/pygmyforth.html">Pygmy for DOS</a> mostly out of curiosity, it was easy enough to talk to the <a href="https://en.wikipedia.org/wiki/16550_UART">8250/16550</a> serial devices that were prevalent pre-USB.</p>

<p>Forth is an interesting language, it’s designed to be very low-level, for interfacing with hardware, and so, like C it’s easy to drop into assembly.</p>

<p>The syntax is definitely unlike anything I’m familiar with, everything is done with <a href="https://en.wikipedia.org/wiki/Reverse_Polish_notation">RPN</a> and there are multiple stacks in use to use for passing parameters around, this use of the stack leads to a lot of stack manipulation, and designing small functions involves designing the layout of the variables on the stack, and how they’re moved around in the code, this leads to small functions as it becomes increasingly hard to reason about the state of the stack in code as functions get larger.</p>

<p>I’m not going to go into Forth syntax too heavily here, the best books on the subject are freely available:</p>

<ul>
  <li>Starting Forth by Leo Brodie https://www.forth.com/starting-forth/</li>
  <li>Thinking Forth by Leo Brodie https://thinking-forth.sourceforge.net/</li>
</ul>

<p>Looking at the <a href="https://htdp.org/2003-09-26/Book/curriculum-Z-H-5.html">examples</a> in the HtDP book…</p>

<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="nf">area-of-disk</span> <span class="nv">r</span><span class="p">)</span> 
  <span class="p">(</span><span class="nb">*</span> <span class="mf">3.14</span> <span class="p">(</span><span class="nb">*</span> <span class="nv">r</span> <span class="nv">r</span><span class="p">)))</span>
</code></pre></div></div>
<p>This declares a function <code class="language-plaintext highlighter-rouge">area-of-disk</code> that takes a parameter <em>r</em> it then multiplies <em>r</em> by itself, and then by <code class="language-plaintext highlighter-rouge">3.14</code> which is an approximation of <em>Pi</em>.</p>

<p>The simple approach to this looks like this:</p>

<pre><code class="language-forth">: area-of-disk ( n -- n )
  dup * 314 * 100 / ;
</code></pre>
<p>Trying this in Gforth</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: area-of-disk ( n1 -- n2 )  compiled
  dup * 314 * 100 / ;  ok
5 area-of-disk . 78  ok
</code></pre></div></div>
<p>In the example, I’m pushing <em>5</em> onto the stack, then calling <code class="language-plaintext highlighter-rouge">area-of-disk</code> which executes and pushes the result onto the stack, running <code class="language-plaintext highlighter-rouge">.</code> pops the top element off the stack, with the result <em>78</em>.</p>

<p>Looking at each operation, and how it affects the stack:</p>

<table>
  <thead>
    <tr>
      <th>operation</th>
      <th>stack</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5</td>
      <td>5</td>
    </tr>
    <tr>
      <td>area-of-disk</td>
      <td>5</td>
    </tr>
    <tr>
      <td>dup</td>
      <td>5 5</td>
    </tr>
    <tr>
      <td>*</td>
      <td>25</td>
    </tr>
    <tr>
      <td>314</td>
      <td>25 314</td>
    </tr>
    <tr>
      <td>*</td>
      <td>7850</td>
    </tr>
    <tr>
      <td>100</td>
      <td>7850 100</td>
    </tr>
    <tr>
      <td>/</td>
      <td>78</td>
    </tr>
  </tbody>
</table>

<p>In the book, the result should be <em>78.5</em>, but this code uses purely integer arithmetic which isn’t ideal.</p>

<p>Reimplementing this in Forth to use the floating-point stack:</p>

<pre><code class="language-forth">: farea-of-disk ( r1 -- r2 )
  fdup f* pi f* ;
</code></pre>
<p>Trying this in Gforth looks slightly different:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: farea-of-disk ( r1 -- r2 )  compiled
  fdup f* pi f* ;  ok
  ok
5e farea-of-disk f. 78.5398163397448  ok
</code></pre></div></div>
<p>The stack operations look almost identical:</p>

<table>
  <thead>
    <tr>
      <th>operation</th>
      <th>stack</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5e</td>
      <td>5.0</td>
    </tr>
    <tr>
      <td>farea-of-disk</td>
      <td>5.0</td>
    </tr>
    <tr>
      <td>fdup</td>
      <td>5.0 5.0</td>
    </tr>
    <tr>
      <td>f*</td>
      <td>25.0</td>
    </tr>
    <tr>
      <td>pi</td>
      <td>25 pi</td>
    </tr>
    <tr>
      <td>f*</td>
      <td>78.53981633974</td>
    </tr>
  </tbody>
</table>

<p>This definitely looks like a more acceptable result.</p>

<p>There are two user stacks in Gforth, the <em>stack</em> and the <em>floating-point stack</em>, these are maintained separately, and the <code class="language-plaintext highlighter-rouge">e</code> notation pushes the number 5.0 onto the <em>floating-point stack</em>.</p>

<p>There is a <a href="https://www.complang.tuwien.ac.at/forth/gforth/Docs-html/Floating-Point.html#Floating-Point">mirrored set of functionality</a> for operating on the <em>floating-point stack</em>.</p>

<p><code class="language-plaintext highlighter-rouge">pi</code> is defined as a floating-point constant with <code class="language-plaintext highlighter-rouge">3.14159265358979</code>.</p>

<p>The next part involves calculating the area of a ring, which is one circle inside of another:</p>

<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">define</span> <span class="p">(</span><span class="nf">area-of-ring</span> <span class="nv">outer</span> <span class="nv">inner</span><span class="p">)</span> 
  <span class="p">(</span><span class="nb">-</span> <span class="p">(</span><span class="nf">area-of-disk</span> <span class="nv">outer</span><span class="p">)</span>
     <span class="p">(</span><span class="nf">area-of-disk</span> <span class="nv">inner</span><span class="p">)))</span>
</code></pre></div></div>

<p>Reimplementing this in Forth is fairly simple and short:</p>

<pre><code class="language-forth">: farea-of-ring ( r1 r2 -- r3 )
 farea-of-disk fswap farea-of-disk fswap f- ;
</code></pre>

<p>This time there’s a bit of stack rearrangement to get this to work:</p>

<table>
  <thead>
    <tr>
      <th>operation</th>
      <th>stack</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5e</td>
      <td>5.0</td>
    </tr>
    <tr>
      <td>3e</td>
      <td>5.0  3.0</td>
    </tr>
    <tr>
      <td>farea-of-ring</td>
      <td>5.0 3.0</td>
    </tr>
    <tr>
      <td>farea-of-disk</td>
      <td>5.0 28.2743338823081</td>
    </tr>
    <tr>
      <td>fswap</td>
      <td>28.2743338823081 5.0</td>
    </tr>
    <tr>
      <td>farea-of-disk</td>
      <td>28.2743338823081 78.5398163397448</td>
    </tr>
    <tr>
      <td>fswap</td>
      <td>78.5 28.26</td>
    </tr>
    <tr>
      <td>f-</td>
      <td>50.24</td>
    </tr>
  </tbody>
</table>

<p>Running the code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Gforth 0.7.3, Copyright (C) 1995-2008 Free Software Foundation, Inc.
Gforth comes with ABSOLUTELY NO WARRANTY; for details type `license'
Type `bye' to exit
: farea-of-disk ( r1 -- r2 )  compiled
  fdup f* pi f* ;  ok
: farea-of-ring ( r1 r2 -- r3 )  compiled
 farea-of-disk fswap farea-of-disk fswap f- ;  ok
5e 3e farea-of-ring f. 50.2654824574367  ok
</code></pre></div></div>
<p>There are two <code class="language-plaintext highlighter-rouge">fswaps</code> in this code, <code class="language-plaintext highlighter-rouge">fswap</code> swaps the top two elements on the floating-point stack.`</p>

<p>Without this, the second <code class="language-plaintext highlighter-rouge">area-of-disk</code> would use the top element on the stack, which would be the result of <code class="language-plaintext highlighter-rouge">area-of-disk</code> on the first item.</p>

<p>The second <code class="language-plaintext highlighter-rouge">fswap</code> orders the areas of the two disks for the <code class="language-plaintext highlighter-rouge">f-</code> call.</p>

<p>This could be avoided by changing the ordering of the parameters on the stack, but this would put the onus on the caller to remember that the inner circle radius comes first which is not ideal.</p>]]></content><author><name></name></author><category term="forth" /><summary type="html"><![CDATA[How to Design Programs is a classic text, illustrating how to implement small projects, specifically breaking them down into small functions.]]></summary></entry><entry><title type="html">Refactoring to Test</title><link href="https://bigkevmcd.github.io/refactoring/go/testing/2022/02/06/refactoring-to-test.html" rel="alternate" type="text/html" title="Refactoring to Test" /><published>2022-02-06T11:20:43+00:00</published><updated>2022-02-06T11:20:43+00:00</updated><id>https://bigkevmcd.github.io/refactoring/go/testing/2022/02/06/refactoring-to-test</id><content type="html" xml:base="https://bigkevmcd.github.io/refactoring/go/testing/2022/02/06/refactoring-to-test.html"><![CDATA[<p>I do a lot of exploratory work poking around in Kubernetes clusters, and Git
repositories for information to radiate information about GitOps deployments.</p>

<p>Currently, I’m exploring git repository directory structures, trying to detect
structures in the files and directories, in the past I’ve looked at ways of
describing this in a “manifest”, and while I think this is useful, it leads to
duplication of the data.</p>

<p>So, what can we figure out from a git repository?</p>

<p>If a git repository contains this simplified directory structure, I want to
detect the single app <code class="language-plaintext highlighter-rouge">demo-app</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apps
└── demo-app
</code></pre></div></div>

<p>To do this, I created a tarball of this, because I’m working with Flux
<a href="https://fluxcd.io/docs/components/source/gitrepositories/">GitRepository</a>
objects, and write this test…</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">TestFindApplications</span><span class="p">(</span><span class="n">t</span> <span class="o">*</span><span class="n">testing</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">ts</span> <span class="o">:=</span> <span class="n">httptest</span><span class="o">.</span><span class="n">NewServer</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">FileServer</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">Dir</span><span class="p">(</span><span class="s">"testdata/archives"</span><span class="p">)))</span>
	<span class="n">t</span><span class="o">.</span><span class="n">Cleanup</span><span class="p">(</span><span class="k">func</span><span class="p">()</span> <span class="p">{</span>
		<span class="n">ts</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
	<span class="p">})</span>

	<span class="n">apps</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">FindApplications</span><span class="p">(</span><span class="n">ts</span><span class="o">.</span><span class="n">URL</span><span class="o">+</span><span class="s">"/first.tar.gz"</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
	<span class="p">}</span>
	<span class="n">want</span> <span class="o">:=</span> <span class="n">sets</span><span class="o">.</span><span class="n">NewString</span><span class="p">(</span><span class="s">"demo-app"</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">diff</span> <span class="o">:=</span> <span class="n">cmp</span><span class="o">.</span><span class="n">Diff</span><span class="p">(</span><span class="n">want</span><span class="p">,</span> <span class="n">apps</span><span class="p">);</span> <span class="n">diff</span> <span class="o">!=</span> <span class="s">""</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Fatalf</span><span class="p">(</span><span class="s">"failed to parse apps:</span><span class="se">\n</span><span class="s">%s"</span><span class="p">,</span> <span class="n">diff</span><span class="p">)</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This basically sets up a <a href="https://pkg.go.dev/net/http/httptest#Server">Server</a> to serve the generated tarball from a <em>testdata</em> directory.</p>

<p>The simplest thing I could write to implement this looks like this:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">FindApplications</span><span class="p">(</span><span class="n">archiveURL</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="n">sets</span><span class="o">.</span><span class="n">String</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">Get</span><span class="p">(</span><span class="n">archiveURL</span><span class="p">)</span>
	<span class="c">// error handling trimmed for brevity</span>
	<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>

	<span class="n">gzipReader</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">gzip</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="p">)</span>
	<span class="c">// error handling trimmed for brevity</span>

	<span class="n">tarReader</span> <span class="o">:=</span> <span class="n">tar</span><span class="o">.</span><span class="n">NewReader</span><span class="p">(</span><span class="n">gzipReader</span><span class="p">)</span>
	<span class="n">apps</span> <span class="o">:=</span> <span class="n">sets</span><span class="o">.</span><span class="n">NewString</span><span class="p">()</span>
	<span class="k">for</span> <span class="no">true</span> <span class="p">{</span>
		<span class="n">header</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">tarReader</span><span class="o">.</span><span class="n">Next</span><span class="p">()</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="n">io</span><span class="o">.</span><span class="n">EOF</span> <span class="p">{</span>
			<span class="k">break</span>
		<span class="p">}</span>
		<span class="c">// error handling trimmed for brevity</span>
		<span class="k">switch</span> <span class="n">header</span><span class="o">.</span><span class="n">Typeflag</span> <span class="p">{</span>
		<span class="k">case</span> <span class="n">tar</span><span class="o">.</span><span class="n">TypeDir</span><span class="o">:</span>
			<span class="k">if</span> <span class="n">strings</span><span class="o">.</span><span class="n">HasPrefix</span><span class="p">(</span><span class="n">header</span><span class="o">.</span><span class="n">Name</span><span class="p">,</span> <span class="s">"apps"</span><span class="p">)</span> <span class="p">{</span>
				<span class="n">appPath</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">TrimPrefix</span><span class="p">(</span><span class="n">header</span><span class="o">.</span><span class="n">Name</span><span class="p">,</span> <span class="s">"apps"</span><span class="p">)</span>
				<span class="n">pathElements</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">appPath</span><span class="p">,</span> <span class="s">"/"</span><span class="p">)</span>
				<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">pathElements</span><span class="p">)</span> <span class="o">==</span> <span class="m">2</span> <span class="p">{</span>
					<span class="n">apps</span><span class="o">.</span><span class="n">Insert</span><span class="p">(</span><span class="n">pathElements</span><span class="p">[</span><span class="m">1</span><span class="p">])</span>
				<span class="p">}</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">apps</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
</code></pre></div></div>
<p><strong>I am removing most error handling and logging from this code to clarify it</strong></p>

<p>I could download the tarball and unarchive it to a temporary directory, but I’ll
parse the streamed tarball in this case.</p>

<p>This gets the test passing, great!</p>

<p>From here, we need tests for error handling, what happens if the file is a 404,
or isn’t a valid GZIP stream?</p>

<p>But, the main goal of this code, is parsing directory trees, and I don’t want to
create tarballs for every case I want to test…it’s too expensive.</p>

<p>I could write a tarball generation mechanism, and I might need to, but the first
thing I really care about, is making sure that it parses the correct things from
the names (the code doesn’t read the files that are in the archive yet).</p>

<p>So, I refactor the <code class="language-plaintext highlighter-rouge">FindApplications</code> function, to separate out the
functionality, this gets me code that looks like this:</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">FindApplications</span><span class="p">(</span><span class="n">archiveURL</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="n">sets</span><span class="o">.</span><span class="n">String</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="c">// identical to the earlier code, opening the stream and ddecoding</span>
	<span class="k">return</span> <span class="n">parseTarball</span><span class="p">(</span><span class="n">tarReader</span><span class="p">,</span> <span class="n">appsPrefix</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">parseTarball</span><span class="p">(</span><span class="n">tarReader</span> <span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Reader</span><span class="p">,</span> <span class="n">base</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="n">sets</span><span class="o">.</span><span class="n">String</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">apps</span> <span class="o">:=</span> <span class="n">sets</span><span class="o">.</span><span class="n">NewString</span><span class="p">()</span>
	<span class="k">for</span> <span class="no">true</span> <span class="p">{</span>
		<span class="n">header</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">tarReader</span><span class="o">.</span><span class="n">Next</span><span class="p">()</span>
		<span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="n">io</span><span class="o">.</span><span class="n">EOF</span> <span class="p">{</span>
			<span class="k">break</span>
		<span class="p">}</span>
		<span class="c">// trim error handling</span>
		<span class="k">switch</span> <span class="n">header</span><span class="o">.</span><span class="n">Typeflag</span> <span class="p">{</span>
		<span class="c">// case tar.TypeReg:</span>
		<span class="k">case</span> <span class="n">tar</span><span class="o">.</span><span class="n">TypeDir</span><span class="o">:</span>
			<span class="k">if</span> <span class="n">strings</span><span class="o">.</span><span class="n">HasPrefix</span><span class="p">(</span><span class="n">header</span><span class="o">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">appsPrefix</span><span class="p">)</span> <span class="p">{</span>
				<span class="n">appPath</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">TrimPrefix</span><span class="p">(</span><span class="n">header</span><span class="o">.</span><span class="n">Name</span><span class="p">,</span> <span class="n">appsPrefix</span><span class="p">)</span>
				<span class="n">pathElements</span> <span class="o">:=</span> <span class="n">strings</span><span class="o">.</span><span class="n">Split</span><span class="p">(</span><span class="n">appPath</span><span class="p">,</span> <span class="s">"/"</span><span class="p">)</span>
				<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">pathElements</span><span class="p">)</span> <span class="o">==</span> <span class="m">2</span> <span class="p">{</span>
					<span class="n">apps</span><span class="o">.</span><span class="n">Insert</span><span class="p">(</span><span class="n">pathElements</span><span class="p">[</span><span class="m">1</span><span class="p">])</span>
				<span class="p">}</span>
			<span class="p">}</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">apps</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
</code></pre></div></div>
<p>My tests still pass, but now I can isolate the behaviour of the directory
parsing.</p>

<p>All I need from the <code class="language-plaintext highlighter-rouge">*tar.Reader</code> is this simple interface.</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">tarIterator</span> <span class="k">interface</span> <span class="p">{</span>
	<span class="n">Next</span><span class="p">()</span> <span class="p">(</span><span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Header</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Because of this, I can build a fake tar reader…</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">fakeTarReader</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="n">headers</span> <span class="p">[]</span><span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Header</span>
	<span class="n">sync</span><span class="o">.</span><span class="n">Mutex</span>
<span class="p">}</span>

<span class="c">// Next() implements the tarReader interface.</span>
<span class="k">func</span> <span class="p">(</span><span class="n">s</span> <span class="o">*</span><span class="n">fakeTarReader</span><span class="p">)</span> <span class="n">Next</span><span class="p">()</span> <span class="p">(</span><span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Header</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">s</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
	<span class="k">defer</span> <span class="n">s</span><span class="o">.</span><span class="n">Unlock</span><span class="p">()</span>
	<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span> <span class="o">==</span> <span class="m">0</span> <span class="p">{</span>
		<span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">io</span><span class="o">.</span><span class="n">EOF</span>
	<span class="p">}</span>
	<span class="n">r</span> <span class="o">:=</span> <span class="n">s</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span><span class="o">-</span><span class="m">1</span><span class="p">]</span>
	<span class="n">s</span><span class="o">.</span><span class="n">headers</span> <span class="o">=</span> <span class="n">s</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="m">0</span> <span class="o">:</span> <span class="nb">len</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">headers</span><span class="p">)</span><span class="o">-</span><span class="m">1</span><span class="p">]</span>
	<span class="k">return</span> <span class="n">r</span><span class="p">,</span> <span class="no">nil</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This is a simple <a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)">stack</a> implementation which is a technique I use a fair bit in test fakes.</p>

<p>I can then cheaply set up a set of table tests…</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="n">Test_parseTarball</span><span class="p">(</span><span class="n">t</span> <span class="o">*</span><span class="n">testing</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="p">{</span>
	<span class="n">parseTests</span> <span class="o">:=</span> <span class="p">[]</span><span class="k">struct</span> <span class="p">{</span>
		<span class="n">headers</span> <span class="p">[]</span><span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Header</span>
		<span class="n">prefix</span>  <span class="kt">string</span>
		<span class="n">want</span>    <span class="n">sets</span><span class="o">.</span><span class="n">String</span>
	<span class="p">}{</span>
		<span class="p">{</span>
			<span class="n">headers</span><span class="o">:</span> <span class="p">[]</span><span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Header</span><span class="p">,</span>
			<span class="n">want</span><span class="o">:</span>    <span class="n">sets</span><span class="o">.</span><span class="n">NewString</span><span class="p">(</span><span class="s">"demo-app"</span><span class="p">),</span>
			<span class="n">prefix</span><span class="o">:</span>  <span class="s">"apps"</span><span class="p">,</span>
		<span class="p">},</span>
		<span class="p">{</span>
			<span class="n">headers</span><span class="o">:</span> <span class="p">[]</span><span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Header</span><span class="p">,</span>
			<span class="n">want</span><span class="o">:</span>    <span class="n">sets</span><span class="o">.</span><span class="n">NewString</span><span class="p">(</span><span class="s">"test-app"</span><span class="p">),</span>
			<span class="n">prefix</span><span class="o">:</span>  <span class="s">"team/apps"</span><span class="p">,</span>
		<span class="p">},</span>
	<span class="p">}</span>

	<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">tt</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">parseTests</span> <span class="p">{</span>
		<span class="n">t</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"test-%d"</span><span class="p">,</span> <span class="n">i</span><span class="p">),</span> <span class="k">func</span><span class="p">(</span><span class="n">t</span> <span class="o">*</span><span class="n">testing</span><span class="o">.</span><span class="n">T</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">reader</span> <span class="o">:=</span> <span class="o">&amp;</span><span class="n">fakeTarReader</span><span class="p">{</span><span class="n">headers</span><span class="o">:</span> <span class="n">tt</span><span class="o">.</span><span class="n">headers</span><span class="p">}</span>
			<span class="n">apps</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">parseTarball</span><span class="p">(</span><span class="n">reader</span><span class="p">,</span> <span class="n">tt</span><span class="o">.</span><span class="n">prefix</span><span class="p">)</span>
			<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
				<span class="n">t</span><span class="o">.</span><span class="n">Fatal</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
			<span class="p">}</span>
			<span class="k">if</span> <span class="n">diff</span> <span class="o">:=</span> <span class="n">cmp</span><span class="o">.</span><span class="n">Diff</span><span class="p">(</span><span class="n">tt</span><span class="o">.</span><span class="n">want</span><span class="p">,</span> <span class="n">apps</span><span class="p">);</span> <span class="n">diff</span> <span class="o">!=</span> <span class="s">""</span> <span class="p">{</span>
				<span class="n">t</span><span class="o">.</span><span class="n">Fatalf</span><span class="p">(</span><span class="s">"failed to parse apps:</span><span class="se">\n</span><span class="s">%s"</span><span class="p">,</span> <span class="n">diff</span><span class="p">)</span>
			<span class="p">}</span>
		<span class="p">})</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The second test, with the <code class="language-plaintext highlighter-rouge">team/apps</code> prefix fails, and I’m into the classic TDD
“write a failing test”, “make the tests pass”, “refactor” cycle.</p>

<p>There’s a lot of <a href="https://wiki.c2.com/?UnitTestingNonPublicMemberFunctions">discussion</a> about whether or not you should test “private methods”, but I’m pragmatic :-)</p>

<p>In this case, it’s not <a href="https://go.dev/tour/basics/3">exported</a>, and so, I don’t feel too bad, but this is a tricky line to tread in TDD, as this test is strongly tied to the underlying tar parsing.</p>]]></content><author><name></name></author><category term="refactoring" /><category term="go" /><category term="testing" /><summary type="html"><![CDATA[I do a lot of exploratory work poking around in Kubernetes clusters, and Git repositories for information to radiate information about GitOps deployments.]]></summary></entry><entry><title type="html">Refactoring Pharo (Part 3)</title><link href="https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/25/pharo-videostore-part-3.html" rel="alternate" type="text/html" title="Refactoring Pharo (Part 3)" /><published>2021-02-25T07:26:30+00:00</published><updated>2021-02-25T07:26:30+00:00</updated><id>https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/25/pharo-videostore-part-3</id><content type="html" xml:base="https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/25/pharo-videostore-part-3.html"><![CDATA[<p>Continuing with the Video store refactoring from the book…</p>

<h2 id="moving-amountfor">Moving amountFor:</h2>

<p>After the success of extracting <code class="language-plaintext highlighter-rouge">amountFor:</code> I opted to use the refactoring
browser’s “Move to another class” functionality to move the method across to
<code class="language-plaintext highlighter-rouge">Rental</code>.</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo08.png" alt="Refactoring menu" /></p>

<p>After choosing the destination class…</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo09.png" alt="Choose destination" /></p>

<p>I ended up with <code class="language-plaintext highlighter-rouge">amountFor:</code> on the Rental class.</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo10.png" alt="Browsing method" /></p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo11.png" alt="Migrated method" /></p>

<p>Unfortunately, this broke my tests:</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo12.png" alt="Broken tests" /></p>

<p>The Pharo traceback was fairly clear:</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo13.png" alt="Broken tests" /></p>

<p>But, by clicking on the <em>Create</em> button I got to fix it fairly easily.</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo14.png" alt="Broken tests" /></p>

<p>And adding an interim method to keep my tests passing:</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo15.png" alt="Broken tests" /></p>]]></content><author><name></name></author><category term="refactoring" /><category term="pharo" /><category term="smalltalk" /><summary type="html"><![CDATA[Continuing with the Video store refactoring from the book…]]></summary></entry><entry><title type="html">Refactoring Pharo (Part 2)</title><link href="https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/24/pharo-videostore-part-2.html" rel="alternate" type="text/html" title="Refactoring Pharo (Part 2)" /><published>2021-02-24T06:08:42+00:00</published><updated>2021-02-24T06:08:42+00:00</updated><id>https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/24/pharo-videostore-part-2</id><content type="html" xml:base="https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/24/pharo-videostore-part-2.html"><![CDATA[<p>In <a href="/refactoring/pharo/smalltalk/2021/02/23/pharo-videostore.html">part 1</a> of this series, I
reimplemented the Video Store exercise in Pharo Smalltalk, this didn’t take
long, it’s not many lines of code.</p>

<p><em>On to the refactoring…</em></p>

<h2 id="extracting-the-amount-calculation">Extracting the amount calculation</h2>

<p>The first step, is to extract the logic that calculates the price of a Rental.</p>

<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Customer</span> <span class="nf">&gt;&gt;</span> <span class="nf">amountFor:</span> <span class="nv">aRental</span> <span class="p">[</span>
	<span class="c">"I calculate the cost to rent the movie for a period."</span>

	<span class="p">|</span> <span class="nv">thisAmount</span> <span class="nf">|</span>
	<span class="nv">thisAmount</span> <span class="o">:=</span> <span class="m">0</span><span class="p">.</span>
	<span class="nv">aRental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#regular</span> <span class="nb">ifTrue:</span> <span class="p">[</span> <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="m">2</span> <span class="p">].</span>
	<span class="p">(</span><span class="nv">aRental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#regular</span> <span class="nf">and:</span> <span class="p">[</span> <span class="nv">aRental</span> <span class="nf">daysRented</span> <span class="nf">&gt;</span> <span class="m">2</span> <span class="p">])</span> 
		<span class="nb">ifTrue:</span> <span class="p">[</span> <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="p">(</span><span class="nv">aRental</span> <span class="nf">daysRented</span> <span class="nf">-</span> <span class="m">2</span> <span class="nf">*</span> <span class="m">1.5</span><span class="p">)</span> <span class="p">].</span>

	<span class="nv">aRental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#new</span> <span class="nb">ifTrue:</span> <span class="p">[</span> 
		<span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="nv">aRental</span> <span class="nf">daysRented</span> <span class="nf">*</span> <span class="m">3</span> <span class="p">].</span>

	<span class="nv">aRental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#childrens</span> <span class="nb">ifTrue:</span> <span class="p">[</span> 
		<span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="m">1.5</span> <span class="p">].</span>
	<span class="p">(</span><span class="nv">aRental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#childrens</span> <span class="nf">and:</span> <span class="p">[</span> <span class="nv">aRental</span> <span class="nf">daysRented</span> <span class="nf">&gt;</span> <span class="m">3</span> <span class="p">])</span> 
		<span class="nb">ifTrue:</span> <span class="p">[</span> <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="p">(</span><span class="nv">aRental</span> <span class="nf">daysRented</span> <span class="nf">-</span> <span class="m">3</span><span class="p">)</span> <span class="nf">*</span> <span class="m">1.5</span> <span class="p">].</span>
	<span class="o">^</span> <span class="nv">thisAmount</span>
<span class="p">]</span>
</code></pre></div></div>

<p>This leads to a small change in the <code class="language-plaintext highlighter-rouge">statement</code> method:</p>

<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code>		  <span class="nv">rentals</span> <span class="nf">do:</span> <span class="p">[</span> <span class="o">:</span><span class="nv">rental</span> <span class="p">|</span> 
			  <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="bp">self</span> <span class="nf">amountFor:</span> <span class="nv">rental</span><span class="p">.</span>
</code></pre></div></div>

<p>All the tests pass at this point.</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo02.png" alt="Tests passing" /></p>

<p>At this point in the book, Fowler talks about the Smalltalk refactoring browser,
and how he was <em>“looking forward to a Java version!”</em>.  I’ll be honest, the
state of the art in refactoring tools is definitely now in the Java camp.</p>

<p>But Pharo does have a <a href="https://wiki.c2.com/?RefactoringBrowser">refactoring browser</a>, so, I reverted the change above and used it:</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo03.png" alt="Selecting the code to be refactored" /></p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo04.png" alt="Creating the method" /></p>

<p>This lead to the same result (minus the comment of course).</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo05.png" alt="Refactored method" /></p>

<h2 id="renaming-result">Renaming result</h2>

<p>The next step is renaming <code class="language-plaintext highlighter-rouge">thisAmount</code> to <code class="language-plaintext highlighter-rouge">result</code>.</p>

<p>This is trivial with the refactoring browser (although, ‘Rename temp’ is not obvious).</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo06.png" alt="Rename temp" /></p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo07.png" alt="Enter the new name" /></p>

<p>This was a nice step forward.</p>]]></content><author><name></name></author><category term="refactoring" /><category term="pharo" /><category term="smalltalk" /><summary type="html"><![CDATA[In part 1 of this series, I reimplemented the Video Store exercise in Pharo Smalltalk, this didn’t take long, it’s not many lines of code.]]></summary></entry><entry><title type="html">Refactoring Pharo (Part 1)</title><link href="https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/23/pharo-videostore.html" rel="alternate" type="text/html" title="Refactoring Pharo (Part 1)" /><published>2021-02-23T19:12:07+00:00</published><updated>2021-02-23T19:12:07+00:00</updated><id>https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/23/pharo-videostore</id><content type="html" xml:base="https://bigkevmcd.github.io/refactoring/pharo/smalltalk/2021/02/23/pharo-videostore.html"><![CDATA[<p>I’ve written about <a href="/design/kotlin/2019/05/08/birthday-greetings.html">Smalltalk</a>
before, after my adventures with GNU Smalltalk, I looked at Pharo, which is
definitely a much more traditional Smalltalk environment.</p>

<p><img src="https://bigkevmcd.github.io/img/pharo/pharo01.png" alt="Pharo" /></p>

<p>I had a go at the VideoStore refactoring exercise from the original <a href="https://www.informit.com/store/refactoring-improving-the-design-of-existing-code-9780201485677">Refactoring
book</a>.</p>

<p>(It was revisited in 2018 in JavaScript for the <a href="https://martinfowler.com/articles/refactoring-video-store-js/">2nd Edition</a>)</p>

<p>It involves refactoring a method for generating a statement for a customers
video rentals (yeah, I guess they were still a thing in 1999).</p>

<p>I’ll be honest, it’s hard to replicate the code from the book, not because
Smalltalk isn’t as expressive as the Java from the book, but because it’s hard
to write the code that is a switch statement in the book in Smalltalk without
the urge to replace it with something better…</p>

<p>The original Java…</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">Customer</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="nc">String</span> <span class="nf">statement</span><span class="o">()</span> <span class="o">{</span>
        <span class="kt">double</span> <span class="n">totalAmount</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
        <span class="kt">int</span> <span class="n">frequentRenterPoints</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
        <span class="nc">Enumeration</span> <span class="n">rentals</span> <span class="o">=</span> <span class="n">_rentals</span><span class="o">.</span><span class="na">elements</span><span class="o">();</span>
        <span class="nc">String</span> <span class="n">result</span> <span class="o">=</span> <span class="s">"Rental Record for "</span> <span class="o">+</span> <span class="n">getName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"\n"</span><span class="o">;</span>

        <span class="k">while</span> <span class="o">(</span><span class="n">rentals</span><span class="o">.</span><span class="na">hasMoreElements</span><span class="o">())</span> <span class="o">{</span>
            <span class="kt">double</span> <span class="n">thisAmount</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
            <span class="nc">Rental</span> <span class="n">each</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Rental</span><span class="o">)</span> <span class="n">rentals</span><span class="o">.</span><span class="na">nextElement</span><span class="o">();</span>

            <span class="c1">//determine amounts for each line</span>
            <span class="k">switch</span> <span class="o">(</span><span class="n">each</span><span class="o">.</span><span class="na">getMovie</span><span class="o">().</span><span class="na">getPriceCode</span><span class="o">())</span> <span class="o">{</span>
                <span class="k">case</span> <span class="nc">Movie</span><span class="o">.</span><span class="na">REGULAR</span><span class="o">:</span> <span class="n">thisAmount</span> <span class="o">+=</span> <span class="mi">2</span><span class="o">;</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">each</span><span class="o">.</span><span class="na">getDaysRented</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="o">)</span>
                        <span class="n">thisAmount</span> <span class="o">+=</span> <span class="o">(</span><span class="n">each</span><span class="o">.</span><span class="na">getDaysRented</span><span class="o">()</span> <span class="o">-</span> <span class="mi">2</span><span class="o">)</span> <span class="o">*</span> <span class="mf">1.5</span><span class="o">;</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="nc">Movie</span><span class="o">.</span><span class="na">NEW_RELEASE</span><span class="o">:</span>
                    <span class="n">thisAmount</span> <span class="o">+=</span> <span class="n">each</span><span class="o">.</span><span class="na">getDaysRented</span><span class="o">()</span> <span class="o">*</span> <span class="mi">3</span><span class="o">;</span>
                    <span class="k">break</span><span class="o">;</span>
                <span class="k">case</span> <span class="nc">Movie</span><span class="o">.</span><span class="na">CHILDRENS</span><span class="o">:</span>
                    <span class="n">thisAmount</span> <span class="o">+=</span> <span class="mf">1.5</span><span class="o">;</span>
                    <span class="k">if</span> <span class="o">(</span><span class="n">each</span><span class="o">.</span><span class="na">getDaysRented</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">3</span><span class="o">)</span>
                        <span class="n">thisAmount</span> <span class="o">+=</span> <span class="o">(</span><span class="n">each</span><span class="o">.</span><span class="na">getDaysRented</span><span class="o">()</span> <span class="o">-</span> <span class="mi">3</span><span class="o">)</span> <span class="o">*</span> <span class="mf">1.5</span><span class="o">;</span>
                    <span class="k">break</span><span class="o">;</span>
            <span class="o">}</span>
            <span class="c1">// add frequent renter points</span>
            <span class="n">frequentRenterPoints</span> <span class="o">++;</span>
            <span class="c1">// add bonus for a two day new release rental</span>
            <span class="k">if</span> <span class="o">((</span><span class="n">each</span><span class="o">.</span><span class="na">getMovie</span><span class="o">().</span><span class="na">getPriceCode</span><span class="o">()</span> <span class="o">==</span> <span class="nc">Movie</span><span class="o">.</span><span class="na">NEW_RELEASE</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">each</span><span class="o">.</span><span class="na">getDaysRented</span><span class="o">()</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="o">)</span>
                <span class="n">frequentRenterPoints</span> <span class="o">++;</span>
            <span class="c1">//show figures for this rental</span>
            <span class="n">result</span> <span class="o">+=</span> <span class="s">"\t"</span> <span class="o">+</span> <span class="n">each</span><span class="o">.</span><span class="na">getMovie</span><span class="o">().</span><span class="na">getTitle</span><span class="o">()+</span> <span class="s">"\t"</span> <span class="o">+</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">thisAmount</span><span class="o">)</span> <span class="o">+</span> <span class="s">"\n"</span><span class="o">;</span>
            <span class="n">totalAmount</span> <span class="o">+=</span> <span class="n">thisAmount</span><span class="o">;</span>
        <span class="o">}</span>
        <span class="c1">//add footer lines</span>
        <span class="n">result</span> <span class="o">+=</span> <span class="s">"Amount owed is "</span> <span class="o">+</span> <span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">totalAmount</span><span class="o">)</span> <span class="o">+</span> <span class="err">\</span><span class="n">n</span><span class="s">";
        result += "</span><span class="nc">You</span> <span class="n">earned</span> <span class="s">" + String.valueOf(frequentRenterPoints) + "</span> <span class="n">frequent</span> <span class="n">renter</span> <span class="n">points</span><span class="err">"</span><span class="o">;</span>
        <span class="k">return</span> <span class="n">result</span><span class="o">;</span>
    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>Which is fairly simply translated into Smalltalk (I used symbols instead of Enums for the rental types).</p>

<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Customer</span> <span class="nf">&gt;&gt;</span> <span class="nv">statement</span> <span class="p">[</span>
	<span class="c">"I calculate and return a statement showing the costs of renting films."</span>
	<span class="p">|</span> <span class="nv">totalAmount</span> <span class="nf">points</span> <span class="nf">thisAmount</span> <span class="nf">|</span>
	<span class="nv">totalAmount</span> <span class="o">:=</span> <span class="m">0</span><span class="p">.</span>
	<span class="nv">points</span> <span class="o">:=</span> <span class="m">0</span><span class="p">.</span>
	<span class="o">^</span> <span class="nc">String</span> <span class="nf">streamContents:</span> <span class="p">[</span> <span class="o">:</span><span class="nv">aStream</span> <span class="p">|</span> 
		  <span class="nv">aStream</span> <span class="nf">&lt;&lt;</span> <span class="p">(</span><span class="s">'Rental record for {1}'</span> <span class="nf">format:</span> <span class="p">{</span> <span class="nv">name</span> <span class="p">});</span> <span class="nf">cr</span><span class="p">.</span>

		  <span class="nv">rentals</span> <span class="nf">do:</span> <span class="p">[</span> <span class="o">:</span><span class="nv">rental</span> <span class="p">|</span> 
			  <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="m">0</span><span class="p">.</span>		  
			  <span class="nv">rental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#regular</span> <span class="nb">ifTrue:</span> <span class="p">[</span> 
				  <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="m">2</span> <span class="p">].</span>
			  <span class="p">(</span><span class="nv">rental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#regular</span> <span class="nf">and:</span> <span class="p">[</span> <span class="nv">rental</span> <span class="nf">daysRented</span> <span class="nf">&gt;</span> <span class="m">2</span> <span class="p">])</span> 
				  <span class="nb">ifTrue:</span> <span class="p">[</span> 
				  <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="p">(</span><span class="nv">rental</span> <span class="nf">daysRented</span> <span class="nf">-</span> <span class="m">2</span> <span class="nf">*</span> <span class="m">1.5</span><span class="p">)</span> <span class="p">].</span>

			  <span class="nv">rental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#new</span> <span class="nb">ifTrue:</span> <span class="p">[</span> 
				  <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="nv">rental</span> <span class="nf">daysRented</span> <span class="nf">*</span> <span class="m">3</span> <span class="p">].</span>

			  <span class="nv">rental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#childrens</span> <span class="nb">ifTrue:</span> <span class="p">[</span> 
				  <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="m">1.5</span> <span class="p">].</span>
			  <span class="p">(</span><span class="nv">rental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#childrens</span> <span class="nf">and:</span> <span class="p">[</span> 
				   <span class="nv">rental</span> <span class="nf">daysRented</span> <span class="nf">&gt;</span> <span class="m">3</span> <span class="p">])</span> <span class="nb">ifTrue:</span> <span class="p">[</span> 
				  <span class="nv">thisAmount</span> <span class="o">:=</span> <span class="nv">thisAmount</span> <span class="nf">+</span> <span class="p">(</span><span class="nv">rental</span> <span class="nf">daysRented</span> <span class="nf">-</span> <span class="m">3</span><span class="p">)</span> <span class="nf">*</span> <span class="m">1.5</span> <span class="p">].</span>

			  <span class="nv">aStream</span>
				  <span class="nf">tab</span><span class="p">;</span>
				  <span class="nf">nextPutAll:</span> <span class="nv">rental</span> <span class="nf">movie</span> <span class="nf">title</span><span class="p">;</span>
				  <span class="nf">tab</span><span class="p">;</span>
				  <span class="nf">tab</span><span class="p">;</span>
				  <span class="nf">nextPutAll:</span> <span class="nv">thisAmount</span> <span class="nf">printString</span><span class="p">;</span>
				  <span class="nf">cr</span><span class="p">.</span>
			  <span class="nv">totalAmount</span> <span class="o">:=</span> <span class="nv">totalAmount</span> <span class="nf">+</span> <span class="nv">thisAmount</span><span class="p">.</span>

			  <span class="nv">points</span> <span class="o">:=</span> <span class="nv">points</span> <span class="nf">+</span> <span class="m">1</span><span class="p">.</span>

			  <span class="p">(</span><span class="nv">rental</span> <span class="nf">movie</span> <span class="nf">priceCode</span> <span class="nf">=</span> <span class="ss">#new</span> <span class="nf">and:</span> <span class="p">[</span> <span class="nv">rental</span> <span class="nf">daysRented</span> <span class="nf">&gt;</span> <span class="m">1</span> <span class="p">])</span> 
				  <span class="nb">ifTrue:</span> <span class="p">[</span> <span class="nv">points</span> <span class="o">:=</span> <span class="nv">points</span> <span class="nf">+</span> <span class="m">1</span> <span class="p">]</span>
			<span class="p">].</span>

		  <span class="nv">aStream</span> <span class="nf">&lt;&lt;</span> <span class="p">(</span><span class="s">'Amount owed is ${1}'</span> <span class="nf">format:</span> <span class="p">{</span> <span class="nv">totalAmount</span> <span class="p">});</span> <span class="nf">cr</span><span class="p">.</span>
		  <span class="nv">aStream</span> <span class="nf">&lt;&lt;</span> <span class="p">(</span><span class="s">'You earned {1} frequent renter points'</span> <span class="nf">format:</span> <span class="p">{</span> <span class="nv">points</span> <span class="p">});</span> <span class="nf">cr</span>
	<span class="p">]</span>
<span class="p">]</span>
</code></pre></div></div>

<p>Of course, it starts with tests…</p>

<div class="language-smalltalk highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CustomerTest</span> <span class="nf">&gt;&gt;</span> <span class="nv">testWithoutAnyRentals</span> <span class="p">[</span>
	<span class="bp">self</span>
		<span class="nf">assert:</span> <span class="nv">customer</span> <span class="nf">statement</span>
		<span class="nf">equals:</span>
			<span class="s">'Rental record for Martin'</span> <span class="nf">,</span> <span class="nc">String</span> <span class="nf">cr</span> <span class="nf">,</span> <span class="s">'Amount owed is $0'</span>
			<span class="nf">,</span> <span class="nc">String</span> <span class="nf">cr</span> <span class="nf">,</span> <span class="s">'You earned 0 frequent renter points'</span> <span class="nf">,</span> <span class="nc">String</span> <span class="nf">cr</span>
<span class="p">]</span>

<span class="nf">CustomerTest</span> <span class="nf">&gt;&gt;</span> <span class="nv">testWithOneRegularRentalForThreeDays</span> <span class="p">[</span>
	<span class="p">|</span> <span class="nv">statement</span> <span class="nf">|</span>
	<span class="nv">customer</span> <span class="nf">addRental:</span> <span class="p">(</span><span class="nc">Rental</span> <span class="nf">withMovie:</span> <span class="nv">regularMovie</span> <span class="nf">daysRented:</span> <span class="m">3</span><span class="p">).</span>

	<span class="nv">statement</span> <span class="o">:=</span> <span class="nv">customer</span> <span class="nf">statement</span><span class="p">.</span>

	<span class="bp">self</span> <span class="nf">assert:</span> <span class="p">(</span><span class="nv">statement</span> <span class="nf">includesSubstring:</span> <span class="s">'Amount owed is $3.5'</span><span class="p">).</span>
	<span class="bp">self</span> <span class="nf">assert:</span>
		<span class="p">(</span><span class="nv">statement</span> <span class="nf">includesSubstring:</span> <span class="s">'You earned 1 frequent renter points'</span><span class="p">)</span>
<span class="p">]</span>

<span class="nf">CustomerTest</span> <span class="nf">&gt;&gt;</span> <span class="nv">testWithOneRegularRentalForTwoDays</span> <span class="p">[</span>
	<span class="p">|</span> <span class="nv">statement</span> <span class="nf">|</span>
	<span class="nv">customer</span> <span class="nf">addRental:</span> <span class="p">(</span><span class="nc">Rental</span> <span class="nf">withMovie:</span> <span class="nv">regularMovie</span> <span class="nf">daysRented:</span> <span class="m">2</span><span class="p">).</span>

	<span class="nv">statement</span> <span class="o">:=</span> <span class="nv">customer</span> <span class="nf">statement</span><span class="p">.</span>

	<span class="bp">self</span> <span class="nf">assert:</span> <span class="p">(</span><span class="nv">statement</span> <span class="nf">includesSubstring:</span> <span class="s">'Amount owed is $2'</span><span class="p">).</span>
	<span class="bp">self</span> <span class="nf">assert:</span>
		<span class="p">(</span><span class="nv">statement</span> <span class="nf">includesSubstring:</span> <span class="s">'You earned 1 frequent renter points'</span><span class="p">)</span>
<span class="p">]</span>
</code></pre></div></div>

<p>I’ll look at how to step through the same refactoring process as Martin Fowler
covers in the book, using Pharo in a series of steps.</p>]]></content><author><name></name></author><category term="refactoring" /><category term="pharo" /><category term="smalltalk" /><summary type="html"><![CDATA[I’ve written about Smalltalk before, after my adventures with GNU Smalltalk, I looked at Pharo, which is definitely a much more traditional Smalltalk environment.]]></summary></entry><entry><title type="html">Flux to Tekton</title><link href="https://bigkevmcd.github.io/tekton/flux/notifications/2020/12/06/flux-tekton.html" rel="alternate" type="text/html" title="Flux to Tekton" /><published>2020-12-06T07:21:32+00:00</published><updated>2020-12-06T07:21:32+00:00</updated><id>https://bigkevmcd.github.io/tekton/flux/notifications/2020/12/06/flux-tekton</id><content type="html" xml:base="https://bigkevmcd.github.io/tekton/flux/notifications/2020/12/06/flux-tekton.html"><![CDATA[<p>Back in August, Weaveworks <a href="https://www.weave.works/blog/gitops-with-flux-v2">announced</a> Fluxv2, which is a
toolkit-style reboot of their <a href="https://www.weave.works/oss/flux/">Flux</a> GitOps tooling.</p>

<p>I like the approach they’re taking, each of the parts is combined with their
<a href="https://toolkit.fluxcd.io/guides/installation/">flux</a> tool to build GitOps
workflows, but they’re useful standalone too if you want to build your own
workflow.</p>

<p>Not unreasonably, it doesn’t come with any sort of “pipeline” mechanism for
building CI or CD pipelines, but with the <a href="https://toolkit.fluxcd.io/components/notification/controller/">Notification Controller</a> you
can drive Tekton pipelines, for example, maybe you want to trigger some process when a new commit is available.</p>

<p>The following configuration assumes you’re reasonably familiar with Tekton.</p>

<h2 id="requirements">Requirements</h2>

<ol>
  <li>Install Flux from the <a href="https://toolkit.fluxcd.io/guides/installation/#bootstrap">Bootstrap</a> instructions.</li>
  <li>
    <p>Install Tekton Pipeline using the “Installation One-liner”.</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl apply <span class="nt">-f</span> https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.18.1/release.yaml
</code></pre></div>    </div>
  </li>
  <li>
    <p>Install Tekton Triggers.</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl apply <span class="nt">-f</span> https://storage.googleapis.com/tekton-releases/triggers/previous/v0.10.1/release.yaml
</code></pre></div>    </div>
  </li>
</ol>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>flux bootstrap github <span class="nt">--owner</span><span class="o">=</span><span class="nv">$GITHUB_USER</span> <span class="nt">--repository</span><span class="o">=</span>fleet-infra <span class="nt">--branch</span><span class="o">=</span>main <span class="nt">--path</span><span class="o">=</span>staging-cluster <span class="nt">--personal</span>
► connecting to github.com
✔ repository created
✔ repository cloned
✚ generating manifests
✔ components manifests pushed
► installing components <span class="k">in </span>flux-system namespace
&lt;snip&gt;
✔ <span class="nb">install </span>completed
► configuring deploy key
✔ deploy key configured
► generating <span class="nb">sync </span>manifests
✔ <span class="nb">sync </span>manifests pushed
► applying <span class="nb">sync </span>manifests
◎ waiting <span class="k">for </span>cluster <span class="nb">sync</span>
✔ bootstrap finished
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl apply <span class="nt">-f</span> https://storage.googleapis.com/tekton-releases/pipeline/previous/v0.18.1/release.yaml
namespace/tekton-pipelines created
&lt;snip&gt;
service/tekton-pipelines-webhook created
</code></pre></div></div>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl apply <span class="nt">-f</span> https://storage.googleapis.com/tekton-releases/triggers/previous/v0.10.1/release.yaml
podsecuritypolicy.policy/tekton-triggers created
&lt;snip&gt;
deployment.apps/tekton-triggers-webhook created
</code></pre></div></div>

<h2 id="configuring-tekton">Configuring Tekton</h2>

<p>For this example, I’m going to create a really simple Pipeline which is triggered by notifications from the <a href="https://github.com/fluxcd/source-controller">Source Controller</a>.</p>

<p>The <a href="https://toolkit.fluxcd.io/components/notification/event/">event documentation</a> could do with a JSON example for folks who don’t read Go natively, but the requests look like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST / HTTP/1.1
Host: example.com
Accept-Encoding: gzip
Content-Length: 452
Content-Type: application/json
Gotk-Component: source-controller
User-Agent: Go-http-client/1.1
</code></pre></div></div>

<p>With a body that looks like this:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"involvedObject"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"kind"</span><span class="p">:</span><span class="s2">"GitRepository"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"namespace"</span><span class="p">:</span><span class="s2">"flux-system"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="s2">"flux-system"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"uid"</span><span class="p">:</span><span class="s2">"cc4d0095-83f4-4f08-98f2-d2e9f3731fb9"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"apiVersion"</span><span class="p">:</span><span class="s2">"source.toolkit.fluxcd.io/v1beta1"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"resourceVersion"</span><span class="p">:</span><span class="s2">"56921"</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="nl">"severity"</span><span class="p">:</span><span class="s2">"info"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"timestamp"</span><span class="p">:</span><span class="s2">"2020-11-27T15:52:21Z"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"message"</span><span class="p">:</span><span class="s2">"Fetched revision: main/731f7eaddfb6af01cb2173e18f0f75b0ba780ef1"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"reason"</span><span class="p">:</span><span class="s2">"info"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"reportingController"</span><span class="p">:</span><span class="s2">"source-controller"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"reportingInstance"</span><span class="p">:</span><span class="s2">"source-controller-7c7b47f5f-8bhrp"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This event is <a href="https://github.com/fluxcd/source-controller/blob/v0.4.1/controllers/gitrepository_controller.go#L146">emitted</a> when the Source Controller detects a change to the watched GitRepository.</p>

<h3 id="tekton-pipeline">Tekton Pipeline</h3>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">tekton.dev/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Pipeline</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">flux-demo-pipeline</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">params</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">message</span>
      <span class="na">description</span><span class="pi">:</span> <span class="s">incoming notification message</span>
      <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repository</span>
      <span class="na">description</span><span class="pi">:</span> <span class="s">The name of the notification GitRepository</span>
      <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">namespace</span>
      <span class="na">description</span><span class="pi">:</span> <span class="s">The namespace of the notification GitRepository</span>
      <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
  <span class="na">tasks</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">task-echo-message</span>
      <span class="na">taskSpec</span><span class="pi">:</span>
        <span class="na">params</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">message</span>
            <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repository</span>
            <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">namespace</span>
            <span class="na">type</span><span class="pi">:</span> <span class="s">string</span>
        <span class="na">steps</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">echo-pipeline</span>
            <span class="na">image</span><span class="pi">:</span> <span class="s">alpine</span>
            <span class="na">command</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="s">echo</span>
            <span class="na">args</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="s2">"</span><span class="s">notification</span><span class="nv"> </span><span class="s">from</span><span class="nv"> </span><span class="s">$(params.namespace)/$(params.repository)"</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">echo-message</span>
            <span class="na">image</span><span class="pi">:</span> <span class="s">alpine</span>
            <span class="na">command</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="s">echo</span>
            <span class="na">args</span><span class="pi">:</span>
              <span class="pi">-</span> <span class="s2">"</span><span class="s">$(params.message)"</span>
      <span class="na">params</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">message</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s">$(params.message)</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repository</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s">$(params.repository)</span>
        <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">namespace</span>
          <span class="na">value</span><span class="pi">:</span> <span class="s">$(params.namespace)</span>
</code></pre></div></div>

<p>This is a really simple Pipeline that accepts a param with a message to be
echoed to the logs, and the name of the affected <a href="https://toolkit.fluxcd.io/components/source/gitrepositories/">GitRepository</a> CR, you could
parse out the branch/revision and fetch the GitRepository to do more work, or
perhaps these could be provided in the Metadata field of the event, which would
definitely simplify the process.</p>

<h3 id="triggering-the-pipeline-from-an-eventlistener">Triggering the Pipeline from an EventListener</h3>

<p>I created a simple EventListener to receive the notifications.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">triggers.tekton.dev/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">EventListener</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">flux-event-listener</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">serviceAccountName</span><span class="pi">:</span> <span class="s">flux-notifications-sa</span>
  <span class="na">triggers</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">flux-notification-trigger</span>
      <span class="na">interceptors</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">cel</span><span class="pi">:</span>
            <span class="na">filter</span><span class="pi">:</span> <span class="s">header.canonical('Gotk-Component') == 'source-controller' &amp;&amp;</span>
              <span class="s">body.involvedObject.kind == 'GitRepository'</span>
      <span class="na">template</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">flux-notification-template</span>
      <span class="na">bindings</span><span class="pi">:</span>
        <span class="pi">-</span> <span class="na">ref</span><span class="pi">:</span> <span class="s">flux-notification-binding</span>
</code></pre></div></div>
<p>This is matching on notifications from the <a href="https://toolkit.fluxcd.io/components/source/controller/">Source Controller</a>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">triggers.tekton.dev/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">TriggerTemplate</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">flux-notification-template</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">params</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">message</span>
      <span class="na">description</span><span class="pi">:</span> <span class="s">message</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repository</span>
      <span class="na">description</span><span class="pi">:</span> <span class="s">the notifying repository</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">namespace</span>
      <span class="na">description</span><span class="pi">:</span> <span class="s">the namespace for the notifying repository</span>
  <span class="na">resourcetemplates</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">apiVersion</span><span class="pi">:</span> <span class="s">tekton.dev/v1beta1</span>
      <span class="na">kind</span><span class="pi">:</span> <span class="s">PipelineRun</span>
      <span class="na">metadata</span><span class="pi">:</span>
        <span class="na">annotations</span><span class="pi">:</span>
        <span class="na">name</span><span class="pi">:</span> <span class="s">flux-notification-run-$(uid)</span>
      <span class="na">spec</span><span class="pi">:</span>
        <span class="na">params</span><span class="pi">:</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">message</span>
            <span class="na">value</span><span class="pi">:</span> <span class="s">$(tt.params.message)</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repository</span>
            <span class="na">value</span><span class="pi">:</span> <span class="s">$(tt.params.repository)</span>
          <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">namespace</span>
            <span class="na">value</span><span class="pi">:</span> <span class="s">$(tt.params.namespace)</span>
        <span class="na">pipelineRef</span><span class="pi">:</span>
          <span class="na">name</span><span class="pi">:</span> <span class="s">flux-demo-pipeline</span>
</code></pre></div></div>

<p>This template is creating a PipelineRun with a <code class="language-plaintext highlighter-rouge">generateName</code> with the three parameters that the Pipeline requires from the TriggerBinding below:.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">triggers.tekton.dev/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">TriggerBinding</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">flux-notification-binding</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">params</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">message</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">$(body.message)</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repository</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">$(body.involvedObject.name)</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">namespace</span>
      <span class="na">value</span><span class="pi">:</span> <span class="s">$(body.involvedObject.namespace)</span>
</code></pre></div></div>

<p>There is some permission configuration to allow an EventListener to run in a namespace, this is a long set of configuration items, which while important for getting the EventListener to work isn’t really connected to setting up the notifications, I’ve provided the <a href="https://bigkevmcd.github.io/files/flux-tekton/permissions.yaml">yaml here</a>.</p>

<h2 id="configuring-flux">Configuring Flux</h2>

<p>First off, this needs a random token secret for the receiver, I use this script to generate a secret with a token:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="c"># Put this in generate_secret.sh and chmod +x</span>
<span class="nv">TOKEN</span><span class="o">=</span><span class="si">$(</span><span class="nb">head</span> <span class="nt">-c</span> 12 /dev/urandom | shasum | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s1">' '</span> <span class="nt">-f1</span><span class="si">)</span>
kubectl create secret generic <span class="nv">$1</span> <span class="nt">-n</span> flux-system <span class="se">\</span>
  <span class="nt">--from-literal</span><span class="o">=</span><span class="nv">token</span><span class="o">=</span><span class="nv">$TOKEN</span>
</code></pre></div></div>

<p>Creating a secret is as simple as:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>./generate_secret.sh generic-receiver-token
secret/generic-receiver-token created
</code></pre></div></div>

<h3 id="creating-a-receiver">Creating a Receiver</h3>

<p>Toolkit components send HTTP requests to an HTTP endpoint that is part of the notification-controller, so I have to configure a receiver for this:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">notification.toolkit.fluxcd.io/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Receiver</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">generic-receiver</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">flux-system</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">generic</span>
  <span class="na">resources</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">GitRepository</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">flux-system</span>
      <span class="na">namespace</span><span class="pi">:</span> <span class="s">flux-system</span>
  <span class="na">secretRef</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">generic-receiver-token</span>
</code></pre></div></div>

<p>I also need a <a href="https://toolkit.fluxcd.io/components/notification/provider/">Provider</a>, this is the element that defines how messages transmitted, there are providers for Slack and GitHub, but for this case, I’ll use a generic webhook provider which just sends an HTTP body.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">notification.toolkit.fluxcd.io/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Provider</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">tekton-el-provider</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">flux-system</span>  
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">generic</span>
  <span class="na">address</span><span class="pi">:</span> <span class="s">http://el-flux-event-listener.default.svc.cluster.local:8080/</span>
</code></pre></div></div>

<p>This is using standard Kubernetes DNS resolution to direct events to the Tekton EventListener defined above, when Tekton Triggers creates an EventListener process to receive HTTP requests, it creates a Service and Deployment with the prefix <code class="language-plaintext highlighter-rouge">el-</code> and the name of the EventListener object, and I placed it in the <code class="language-plaintext highlighter-rouge">default</code> namespace for this demo.</p>

<p>Next, there’s an <code class="language-plaintext highlighter-rouge">Alert</code> which connects the notification to the <code class="language-plaintext highlighter-rouge">Provider</code>.</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">notification.toolkit.fluxcd.io/v1beta1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Alert</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">tekton-alert</span>
  <span class="na">namespace</span><span class="pi">:</span> <span class="s">flux-system</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">providerRef</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">tekton-el-provider</span>
  <span class="na">eventSeverity</span><span class="pi">:</span> <span class="s">info</span>
  <span class="na">eventSources</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">kind</span><span class="pi">:</span> <span class="s">GitRepository</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">flux-system</span>
      <span class="na">namespace</span><span class="pi">:</span> <span class="s">flux-system</span>
</code></pre></div></div>

<p>This configures an alert for info-level notifications for the GitRepository object that’s created during the installation of flux, to be sent via the Provider defined above.</p>

<p>Modifying a file in the repository pointed to by the flux-system <code class="language-plaintext highlighter-rouge">GitRepository</code> triggers a notification.</p>

<p>I can see the logs of the notification being triggered:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl logs deploy/notification-controller <span class="nt">-n</span> flux-system
<span class="o">{</span><span class="s2">"level"</span>:<span class="s2">"info"</span>,<span class="s2">"ts"</span>:<span class="s2">"2020-12-05T13:15:53.235Z"</span>,<span class="s2">"logger"</span>:<span class="s2">"event-server"</span>,<span class="s2">"msg"</span>:<span class="s2">"Dispatching event"</span>,<span class="s2">"object"</span>:<span class="s2">"flux-system/flux-system"</span>,<span class="s2">"kind"</span>:<span class="s2">"GitRepository"</span>,<span class="s2">"message"</span>:<span class="s2">"Fetched revision: main/6fca464c6c124156b9cb3913c229b59148323703"</span><span class="o">}</span>
<span class="o">{</span><span class="s2">"level"</span>:<span class="s2">"info"</span>,<span class="s2">"ts"</span>:<span class="s2">"2020-12-05T13:15:56.408Z"</span>,<span class="s2">"logger"</span>:<span class="s2">"event-server"</span>,<span class="s2">"msg"</span>:<span class="s2">"Discarding event, no alerts found for the involved object"</span>,<span class="s2">"object"</span>:<span class="s2">"flux-system/flux-system"</span>,<span class="s2">"kind"</span>:<span class="s2">"Kustomization"</span><span class="o">}</span>
</code></pre></div></div>

<p>And in the logs of the EventListener, the EventListener is logging out that it’s creating a new resource called <code class="language-plaintext highlighter-rouge">flux-notification-run-rqswx</code>.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl logs deploy/el-flux-event-listener
<span class="o">{</span><span class="s2">"level"</span>:<span class="s2">"info"</span>,<span class="s2">"ts"</span>:<span class="s2">"2020-12-05T13:15:53.265Z"</span>,<span class="s2">"logger"</span>:<span class="s2">"eventlistener"</span>,<span class="s2">"caller"</span>:<span class="s2">"sink/sink.go:237"</span>,<span class="s2">"msg"</span>:<span class="s2">"ResolvedParams : [{Name:message Value:Fetched revision: main/6fca464c6c124156b9cb3913c229b59148323703} {Name:repository Value:flux-system} {Name:namespace Value:flux-system}]"</span>,<span class="s2">"knative.dev/controller"</span>:<span class="s2">"eventlistener"</span>,<span class="s2">"/triggers-eventid"</span>:<span class="s2">"wvd9c"</span>,<span class="s2">"/trigger"</span>:<span class="s2">"flux-notification-trigger"</span><span class="o">}</span>
<span class="o">{</span><span class="s2">"level"</span>:<span class="s2">"info"</span>,<span class="s2">"ts"</span>:<span class="s2">"2020-12-05T13:15:53.268Z"</span>,<span class="s2">"logger"</span>:<span class="s2">"eventlistener"</span>,<span class="s2">"caller"</span>:<span class="s2">"resources/create.go:95"</span>,<span class="s2">"msg"</span>:<span class="s2">"Generating resource: kind: &amp;APIResource{Name:pipelineruns,Namespaced:true,Kind:PipelineRun,Verbs:[delete deletecollection get list patch create update watch],ShortNames:[pr prs],SingularName:pipelinerun,Categories:[tekton tekton-pipelines],Group:tekton.dev,Version:v1beta1,StorageVersionHash:RcAKAgPYYoo=,}, name: flux-notification-run-rqswx"</span>,<span class="s2">"knative.dev/controller"</span>:<span class="s2">"eventlistener"</span><span class="o">}</span>
<span class="o">{</span><span class="s2">"level"</span>:<span class="s2">"info"</span>,<span class="s2">"ts"</span>:<span class="s2">"2020-12-05T13:15:53.268Z"</span>,<span class="s2">"logger"</span>:<span class="s2">"eventlistener"</span>,<span class="s2">"caller"</span>:<span class="s2">"resources/create.go:103"</span>,<span class="s2">"msg"</span>:<span class="s2">"For event ID </span><span class="se">\"</span><span class="s2">wvd9c</span><span class="se">\"</span><span class="s2"> creating resource tekton.dev/v1beta1, Resource=pipelineruns"</span>,<span class="s2">"knative.dev/controller"</span>:<span class="s2">"eventlistener"</span><span class="o">}</span>
</code></pre></div></div>

<p>And finally, I get to see the message from the event:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ tkn pipelinerun logs --last
[task-echo-message : echo-pipeline] notification from flux-system/flux-system

[task-echo-message : echo-message] Fetched revision: main/6fca464c6c124156b9cb3913c229b59148323703
</code></pre></div></div>

<p>Right now, the Notifications Controller is functional, and hopefully it will become easier to integrate components.</p>]]></content><author><name></name></author><category term="tekton" /><category term="flux" /><category term="notifications" /><summary type="html"><![CDATA[Back in August, Weaveworks announced Fluxv2, which is a toolkit-style reboot of their Flux GitOps tooling.]]></summary></entry><entry><title type="html">Introducing tekton-poller</title><link href="https://bigkevmcd.github.io/tekton/polling/2020/07/23/tekton-poller.html" rel="alternate" type="text/html" title="Introducing tekton-poller" /><published>2020-07-23T18:04:05+00:00</published><updated>2020-07-23T18:04:05+00:00</updated><id>https://bigkevmcd.github.io/tekton/polling/2020/07/23/tekton-poller</id><content type="html" xml:base="https://bigkevmcd.github.io/tekton/polling/2020/07/23/tekton-poller.html"><![CDATA[<p>I’ve written a fair bit about Tekton, but one of the perennial problems that I
hear from users is that they can’t expose an event-listener to the internet.</p>

<p>Yes, opening firewall ports in the modern enterprise is often a trial of
patience, so exposing your Tekton <a href="https://github.com/tektoncd/triggers/blob/master/docs/eventlisteners.md">EventListener</a> to GitHub is often not possible.</p>

<p>For this reason, I created <a href="https://github.com/bigkevmcd/tekton-poller-operator">tekton-poller-operator</a>.</p>

<p>It’s not a perfect replacement for an EventListener, but if you want to drive <a href="https://github.com/tektoncd/pipeline/blob/master/docs/pipelines.md">Pipelines</a> then this might help you!.</p>

<p>This is still in early stages, so I’m looking for feedback.</p>

<h2 id="the-obligatory-yaml">The obligatory YAML</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">apiVersion</span><span class="pi">:</span> <span class="s">polling.tekton.dev/v1alpha1</span>
<span class="na">kind</span><span class="pi">:</span> <span class="s">Repository</span>
<span class="na">metadata</span><span class="pi">:</span>
  <span class="na">name</span><span class="pi">:</span> <span class="s">example-repository</span>
<span class="na">spec</span><span class="pi">:</span>
  <span class="na">url</span><span class="pi">:</span> <span class="s">https://github.com/my-org/my-repo.git</span>
  <span class="na">ref</span><span class="pi">:</span> <span class="s">main</span>
  <span class="na">frequency</span><span class="pi">:</span> <span class="s">5m</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s">github</span> <span class="c1"># can also be gitlab</span>
  <span class="na">pipelineRef</span><span class="pi">:</span>
    <span class="na">name</span><span class="pi">:</span> <span class="s">github-poll-pipeline</span>
    <span class="na">namespace</span><span class="pi">:</span> <span class="s">test-ns</span> <span class="c1"># optional: if provided, the pipelinerun will be created in this namespace to reference the pipeline.</span>
    <span class="na">params</span><span class="pi">:</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">sha</span>
      <span class="na">expression</span><span class="pi">:</span> <span class="s">commit.sha</span>
    <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repoURL</span>
      <span class="na">expression</span><span class="pi">:</span> <span class="s">repoURL</span>
</code></pre></div></div>

<p>This defines a Repository, every 5m, the GitHub repo <code class="language-plaintext highlighter-rouge">https://github.com/myorg/my-repo.git</code> is polled, if the SHA changes, then the pipeline <code class="language-plaintext highlighter-rouge">github-poll-pipeline</code> is executed with the new SHA, and the repoURL.</p>

<h2 id="params">Params</h2>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">params</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">sha</span>
  <span class="na">expression</span><span class="pi">:</span> <span class="s">commit.sha</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">repoURL</span>
  <span class="na">expression</span><span class="pi">:</span> <span class="s">repoURL</span>
</code></pre></div></div>

<p>The expressions here are <a href="https://github.com/google/cel-go">CEL</a> expressions, these are parsed from the commit that’s fetched by the Poller.</p>

<p>The <code class="language-plaintext highlighter-rouge">repoURL</code> is the URL from the spec in the repository, and the <code class="language-plaintext highlighter-rouge">commit</code> is the JSON body of the commit that was fetched.</p>

<h2 id="triggerbindings-and-triggertemplates">TriggerBindings and TriggerTemplates</h2>

<p>Currently, these are not supported, it will take some refactoring of Tekton triggers to make this happen, but this is the goal.</p>]]></content><author><name></name></author><category term="tekton" /><category term="polling" /><summary type="html"><![CDATA[I’ve written a fair bit about Tekton, but one of the perennial problems that I hear from users is that they can’t expose an event-listener to the internet.]]></summary></entry><entry><title type="html">Adding Commit SHAs to Go binaries</title><link href="https://bigkevmcd.github.io/dockerfile/go/sha/2020/07/03/adding-shas-to-go.html" rel="alternate" type="text/html" title="Adding Commit SHAs to Go binaries" /><published>2020-07-03T05:13:49+00:00</published><updated>2020-07-03T05:13:49+00:00</updated><id>https://bigkevmcd.github.io/dockerfile/go/sha/2020/07/03/adding-shas-to-go</id><content type="html" xml:base="https://bigkevmcd.github.io/dockerfile/go/sha/2020/07/03/adding-shas-to-go.html"><![CDATA[<p>Often when building a Go binary, we want to embed the specific commit-revision
that it was built from in the binary, this is useful for doing things like
<code class="language-plaintext highlighter-rouge">command --version</code> or exposing in an HTTP API endpoint (or possibly header).</p>

<p>For example, <code class="language-plaintext highlighter-rouge">kubectl</code> provides really good information in its output:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl version
Client Version: version.Info<span class="o">{</span>Major:<span class="s2">"1"</span>, Minor:<span class="s2">"18"</span>, GitVersion:<span class="s2">"v1.18.4"</span>, GitCommit:<span class="s2">"c96aede7b5205121079932896c4ad89bb93260af"</span>, GitTreeState:<span class="s2">"clean"</span>, BuildDate:<span class="s2">"2020-06-18T07:39:54Z"</span>, GoVersion:<span class="s2">"go1.14.3"</span>, Compiler:<span class="s2">"gc"</span>, Platform:<span class="s2">"linux/amd64"</span><span class="o">}</span>
Server Version: version.Info<span class="o">{</span>Major:<span class="s2">"1"</span>, Minor:<span class="s2">"18"</span>, GitVersion:<span class="s2">"v1.18.3"</span>, GitCommit:<span class="s2">"2e7996e3e2712684bc73f0dec0200d64eec7fe40"</span>, GitTreeState:<span class="s2">"clean"</span>, BuildDate:<span class="s2">"2020-05-20T12:43:34Z"</span>, GoVersion:<span class="s2">"go1.13.9"</span>, Compiler:<span class="s2">"gc"</span>, Platform:<span class="s2">"linux/amd64"</span><span class="o">}</span>
</code></pre></div></div>

<p>So, how do you do this in <em>your</em> compilations?</p>

<p>Let’s start with the simplest possible Go file.</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="s">"fmt"</span>

<span class="k">var</span> <span class="n">gitRevision</span> <span class="o">=</span> <span class="s">"unknown"</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"version %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">gitRevision</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Quickly put this into a git repo so that I can actually get the version</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git init <span class="nb">.</span> <span class="o">&amp;&amp;</span> git add main.go <span class="o">&amp;&amp;</span> git commit <span class="nt">-m</span> <span class="s2">"Initial commit."</span>
</code></pre></div></div>

<h2 id="building-at-the-command-line">Building at the command-line</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ go build main.go
$ ./main
version unknown
</code></pre></div></div>

<p>Ok, nothing totally unexpected there…but…</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ GIT_COMMIT=$(git rev-parse HEAD) &amp;&amp; go build -ldflags "-X main.gitRevision=${GIT_COMMIT}" ./main.go
$ ./main
version 49e5416e820fb376fdb277243dbec8e2e81fbc29
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">"-X main.gitRevision=${GIT_COMMIT}"</code> embeds the environment variable <code class="language-plaintext highlighter-rouge">GIT_COMMIT</code> into the binary, by replacing the path <code class="language-plaintext highlighter-rouge">main.gitRevision</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">-X</code> parameter is passed to the <a href="https://golang.org/cmd/link">Go link tool</a> and from that documentation:</p>

<blockquote>
  <p>-X importpath.name=value
	Set the value of the string variable in importpath named name to value.
	This is only effective if the variable is declared in the source code either uninitialized
	or initialized to a constant string expression. -X will not work if the initializer makes
	a function call or refers to other variables.
	Note that before Go 1.5 this option took two separate arguments.</p>
</blockquote>

<p>That <code class="language-plaintext highlighter-rouge">importpath</code> is fairly crucial to understanding the next part, for the simple case, the Go package is <code class="language-plaintext highlighter-rouge">main</code>, but we can also write into any package in a Go source tree.</p>

<h3 id="adding-a-gomod-path-to-the-path">Adding a go.mod path to the path.</h3>

<p>That was fairly easy, but what if our package is in a go.mod package?</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>go mod init github.com/bigkevmcd/demo
go: creating new go.mod: module github.com/bigkevmcd/demo
</code></pre></div></div>

<p>And I’ll move the version into a specific package.</p>

<p>Editing <code class="language-plaintext highlighter-rouge">pkg/git/git.go</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package git

var Revision = "unknown"
</code></pre></div></div>

<p>And changing the original <code class="language-plaintext highlighter-rouge">main.go</code> to</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">import</span> <span class="p">(</span>
	<span class="s">"fmt"</span>

	<span class="s">"github.com/bigkevmcd/demo/pkg/git"</span>
<span class="p">)</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
	<span class="n">fmt</span><span class="o">.</span><span class="n">Printf</span><span class="p">(</span><span class="s">"version %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">git</span><span class="o">.</span><span class="n">Revision</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>So, I’m importing the version from a package.</p>

<p>I need to slightly adjust the command-line for building this:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ GIT_COMMIT</span><span class="o">=</span><span class="si">$(</span>git rev-parse HEAD<span class="si">)</span> <span class="o">&amp;&amp;</span> go build <span class="nt">-ldflags</span> <span class="s2">"-X github.com/bigkevmcd/demo/pkg/git.Revision=</span><span class="k">${</span><span class="nv">GIT_COMMIT</span><span class="k">}</span><span class="s2">"</span> ./main.go
<span class="nv">$ </span>./main
version abd2bcd0497f7a0f4e04aa157e879b478f1b0f95
</code></pre></div></div>

<p><strong>NOTE</strong> that the full Go “package path” to the value I want to update is
provided.</p>

<h2 id="building-in-a-dockerfile">Building in a Dockerfile</h2>

<p>So, now that I’ve got the basics right, building it in a Dockerfile is fairly
simple:</p>

<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="w"> </span><span class="s">golang:latest</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">build</span>
<span class="k">WORKDIR</span><span class="s"> /go/src</span>
<span class="k">COPY</span><span class="s"> . /go/src</span>
<span class="k">RUN </span><span class="nb">ls</span> <span class="nt">-la</span>
<span class="k">RUN </span><span class="nv">GIT_COMMIT</span><span class="o">=</span><span class="si">$(</span>git rev-parse HEAD<span class="si">)</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nv">CGO_ENABLED</span><span class="o">=</span>0 <span class="nv">GOOS</span><span class="o">=</span>linux go build <span class="se">\
</span>    <span class="nt">-ldflags</span> <span class="s2">"-X github.com/bigkevmcd/demo/pkg/git.Revision=</span><span class="k">${</span><span class="nv">GIT_COMMIT</span><span class="k">}</span><span class="s2">"</span> ./main.go

<span class="k">FROM</span><span class="s"> alpine</span>
<span class="k">WORKDIR</span><span class="s"> /root/</span>
<span class="k">COPY</span><span class="s"> --from=build /go/src/main .</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["./main"]</span>
</code></pre></div></div>

<p>I can easily build and push this to an image repository.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker build <span class="nt">-t</span> bigkevmcd/demo <span class="nb">.</span>
<span class="nv">$ </span>docker push bigkevmcd/demo
</code></pre></div></div>

<p>And I can run this image in Kubernetes with a one-liner:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl run <span class="nt">--image</span><span class="o">=</span>bigkevmcd/demo demo-pod <span class="nt">--restart</span><span class="o">=</span>Never
pod/demo-pod created
<span class="nv">$ </span>kubectl logs pod/demo-pod
version abd2bcd0497f7a0f4e04aa157e879b478f1b0f95
</code></pre></div></div>]]></content><author><name></name></author><category term="dockerfile" /><category term="go" /><category term="sha" /><summary type="html"><![CDATA[Often when building a Go binary, we want to embed the specific commit-revision that it was built from in the binary, this is useful for doing things like command --version or exposing in an HTTP API endpoint (or possibly header).]]></summary></entry><entry><title type="html">Introducing Quay Imager</title><link href="https://bigkevmcd.github.io/quay.io/image/yaml/2020/05/14/quay-imager.html" rel="alternate" type="text/html" title="Introducing Quay Imager" /><published>2020-05-14T17:59:20+00:00</published><updated>2020-05-14T17:59:20+00:00</updated><id>https://bigkevmcd.github.io/quay.io/image/yaml/2020/05/14/quay-imager</id><content type="html" xml:base="https://bigkevmcd.github.io/quay.io/image/yaml/2020/05/14/quay-imager.html"><![CDATA[<p><strong>NOTE:</strong> I renamed Quay Imager to Image Hooks, links have been updated.</p>

<p>Short post to introduce a tiny service called <a href="https://github.com/bigkevmcd/image-hooks">“quay-imager”</a>.</p>

<p>This is an HTTP server that receives Quay.io <a href="https://docs.quay.io/guides/notifications.html">Webhook notifications</a> and automates editing of a YAML file, by replacing an image reference.</p>

<p>How does this work?</p>

<p><img src="https://bigkevmcd.github.io/img/quay-imager.png" alt="Service Interactions" /></p>

<h2 id="deployment">Deployment</h2>

<p>This is packaged and deployed as a Kubernetes deployment.</p>

<p>Before deploying the release, you will need to do some minimal <a href="https://github.com/bigkevmcd/image-hooks#creating-the-configuration">configuration</a>.</p>

<p>Then deploy the release.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>kubectl apply <span class="nt">-f</span> https://github.com/bigkevmcd/image-hooks/releases/download/v0.0.1/release.yaml
</code></pre></div></div>

<p><strong>NOTE</strong>: There’s nothing in this code that requires to be run in a Kubernetes
cluster, so you are free to build and deploy in other ways, for example Heroku.</p>

<h2 id="configuration">Configuration</h2>

<p>Given this configuration:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">repositories</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">&lt;Quay.io username&gt;/&lt;Quay.io repository&gt;</span>
    <span class="na">sourceRepo</span><span class="pi">:</span> <span class="s">&lt;github user&gt;/&lt;github repository&gt;</span>
    <span class="na">sourceBranch</span><span class="pi">:</span> <span class="s">master</span>
    <span class="na">filePath</span><span class="pi">:</span> <span class="s">deploy/deployment.yaml</span>
    <span class="na">updateKey</span><span class="pi">:</span> <span class="s">spec.template.spec.containers.0.image</span>
    <span class="na">branchGenerateName</span><span class="pi">:</span> <span class="s">repo-imager-</span>
</code></pre></div></div>

<p>This is an array of repositories, the <code class="language-plaintext highlighter-rouge">name</code> is used to match on the incoming
Quay.io webhooks</p>

<p>The <code class="language-plaintext highlighter-rouge">sourceRepo</code> and <code class="language-plaintext highlighter-rouge">sourceBranch</code> reference the file to be updated, the
<code class="language-plaintext highlighter-rouge">filePath</code> is the actual file to change.</p>

<p>The <code class="language-plaintext highlighter-rouge">updateKey</code> is a JSON path to update with the incoming key.</p>

<p>Finally, the <code class="language-plaintext highlighter-rouge">branchGenerateName</code> is used to generate a random branch name.</p>

<h2 id="quayio-configuration">Quay.io configuration</h2>

<p>You’ll need to expose your service so it can be accessed by Quay.io, and create
a Webhook Notification for your repository.</p>

<p>This must be a “Push to Repository” notification, with a type of “Webhook POST”.</p>

<p><img src="https://bigkevmcd.github.io/img/quayio/create-repository-notification.png" alt="Configuring a Quay.io Webhook" /></p>

<p>Fill in the <em>Webhook URL</em> with with the exposed URL (don’t forget things
like ngrok if you’re testing this locally).</p>

<p>Finally, push an image to Quay.io:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>docker push quay.io/&lt;Quay.io username&gt;/&lt;Quay.io repository&gt;:v1
</code></pre></div></div>

<p>This should trigger an update to the GitHub repository, creating a new branch (named with the prefix <code class="language-plaintext highlighter-rouge">repo-imager-</code>), and opening a PR with the change.</p>

<p>The <code class="language-plaintext highlighter-rouge">spec.template.spec.containers.0.image</code> field, in the <code class="language-plaintext highlighter-rouge">deploy/deployment.yaml</code> field is updated with the image that was pushed, for example, above, this would be replaced with something like <code class="language-plaintext highlighter-rouge">quay.io/myuser/example:v1</code>.</p>

<p>By approving this PR, ideally, you’d trigger a deployment (if you’re following
GitOps processes).</p>

<h2 id="important-caveat">IMPORTANT CAVEAT</h2>

<p>Quay.io has no support for authenticating hooks, so unfortunately you can’t
trust Webhooks, so I wouldn’t recommend this in production without some
mechanism to prevent randoms on the internet triggering builds.</p>

<h2 id="api">API</h2>

<p>You could see this as an API for automating updating GitOps files, these are the
fields you’d need to send.</p>

<p>It would certainly be possible to have an optional authentication header, and
require API requests to send it, and block non-authenticated requests.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"repository"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mynamespace/repository"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"docker_url"</span><span class="p">:</span><span class="w"> </span><span class="s2">"quay.io/mynamespace/repository"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"updated_tags"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="s2">"latest"</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>]]></content><author><name></name></author><category term="quay.io" /><category term="image" /><category term="yaml" /><summary type="html"><![CDATA[NOTE: I renamed Quay Imager to Image Hooks, links have been updated.]]></summary></entry></feed>