<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Shivan Moodley</title>
    <description>The latest articles on Forem by Shivan Moodley (@cishiv).</description>
    <link>https://forem.com/cishiv</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F299272%2Fd363fbd6-8d13-4e35-b0ed-42d8d3d36f73.jpeg</url>
      <title>Forem: Shivan Moodley</title>
      <link>https://forem.com/cishiv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/cishiv"/>
    <language>en</language>
    <item>
      <title>Using the IFS variable to do things with CSV files</title>
      <dc:creator>Shivan Moodley</dc:creator>
      <pubDate>Wed, 05 Feb 2020 02:44:16 +0000</pubDate>
      <link>https://forem.com/cishiv/using-the-ifs-variable-to-do-things-with-csv-files-1ld5</link>
      <guid>https://forem.com/cishiv/using-the-ifs-variable-to-do-things-with-csv-files-1ld5</guid>
      <description>&lt;h1&gt;
  
  
  The Problem
&lt;/h1&gt;

&lt;p&gt;Earlier this week, I had to do a mass-insert into a database table from a CSV file. &lt;/p&gt;

&lt;p&gt;The CSV file had ~20 columns and ~500k rows. I only really cared about 4 of the columns.&lt;/p&gt;

&lt;p&gt;Visually, the problem can represented accurately as the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CrDi4Xtm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/87atj6kntztbpxwoi2z2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CrDi4Xtm--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/i/87atj6kntztbpxwoi2z2.png" alt="A diagram showing a CSV file and a database with witty labels" width="371" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Previously I had seen this problem solved with massive amounts of over-engineering, and sometimes with clever usage of Microsoft Excel. However, in my never-ending quest to use the tools available to me more effectively. I challenged my self to see if I could generate the inserts I needed without leaving my terminal window.&lt;/p&gt;

&lt;h1&gt;
  
  
  First Steps
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Some Example Data
&lt;/h2&gt;

&lt;p&gt;For this post I am going to use the &lt;a href="https://www.kaggle.com/ronitf/heart-disease-uci"&gt;heart.csv&lt;/a&gt; data set sourced from &lt;a href="https://www.kaggle.com"&gt;Kaggle&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It has the following (14) columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
age age in years
sex(1 = male; 0 = female)
cp chest pain type
trestbps resting blood pressure (in mm Hg on admission to the hospital)
chol serum cholestoral in mg/dl
fbs (fasting blood sugar &amp;gt; 120 mg/dl) (1 = true; 0 = false)
restecg resting electrocardiographic results
thalach maximum heart rate achieved
exang exercise induced angina (1 = yes; 0 = no)
oldpeak ST depression induced by exercise relative to rest
slope the slope of the peak exercise ST segment
ca number of major vessels (0-3) colored by flourosopy
thal 3 = normal; 6 = fixed defect; 7 = reversable defect
target 1 or 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enter bash and IFS
&lt;/h2&gt;

&lt;p&gt;Let's read this using a small script that leverages &lt;code&gt;IFS&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# Read a CSV file and presumably do stuff with what we read

# Configure the Internal Field Seperator (see: Delimeter)
IFS=','

# while stuff in FILE, read COLUMNS using $IFS
while read -r age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal target
do
        # do things
        echo "$age $sex $cp $trestbps $chol $fbs $restecg $thalach $exang $oldpeak $slope $ca $thal $target"
done &amp;lt; "$1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can use this script by running the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# make it executable
chmod +X &amp;lt;SCRIPT_NAME&amp;gt;
# run it 
./&amp;lt;SCRIPT_NAME&amp;gt; &amp;lt;PATH_TO_CSV_FILE&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which should yield:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;﻿age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal target
63 1 3 145 233 1 0 150 0 2.3 0 0 1 1
37 1 2 130 250 0 1 187 0 3.5 0 0 2 1
41 0 1 130 204 0 0 172 0 1.4 2 0 2 1
56 1 1 120 236 0 1 178 0 0.8 2 0 2 1
57 0 0 120 354 0 1 163 1 0.6 2 0 2 1
57 1 0 140 192 0 1 148 0 0.4 1 0 1 1
56 0 1 140 294 0 0 153 0 1.3 1 0 2 1
44 1 1 120 263 0 1 173 0 0 2 0 3 1
...
57 0 0 140 241 0 1 123 1 0.2 1 0 3 0
45 1 3 110 264 0 1 132 0 1.2 1 0 3 0
68 1 0 144 193 1 1 141 0 3.4 1 2 3 0
57 1 0 130 131 0 1 115 1 1.2 1 1 3 0
57 0 1 130 236 0 0 174 0 0 1 1 2 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's further assume that we have a &lt;strong&gt;database schema&lt;/strong&gt; DATA that contains a table called &lt;strong&gt;HEART&lt;/strong&gt; in it.&lt;/p&gt;

&lt;p&gt;The table was created with the following SQL statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE DATA.HEART(
   ID   INT              NOT NULL,
   AGE  INT              NOT NULL,
   THAL INT,
   CHOL   DECIMAL,       
   PRIMARY KEY (ID)
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generating SQL Inserts
&lt;/h2&gt;

&lt;p&gt;Let's modify our pre-existing script to create a bunch of insert statements from our &lt;code&gt;heart.csv&lt;/code&gt; file for this table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# Read a CSV file and presumably doing stuff with what we read

# Configure the Internal Field Seperator (see: Delimeter)
IFS=','

# Keep track of the sequence
declare -i id=0
# while stuff in FILE, read COLUMNS using $IFS
while read -r age sex cp trestbps chol fbs restecg thalach exang oldpeak slope ca thal target
do
        # ignore the first row since it contains column headers
        # craft an insert statement and append to the end of a file
        if [ $id -eq 0 ]
        then
                echo "ignoring first row"
        else
                echo "INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES ($id, $age, $thal, $chol);" &amp;gt;&amp;gt; $2
        fi
        id=$((id+1))
done &amp;lt; "$1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now run this with a second parameter (the output file name):&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./&amp;lt;SCRIPT_NAME&amp;gt; &amp;lt;IN_FILE_NAME&amp;gt; &amp;lt;OUT_FILE_NAME&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Which gives us something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (1, 63, 1, 233);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (2, 37, 2, 250);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (3, 41, 2, 204);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (4, 56, 2, 236);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (5, 57, 2, 354);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (6, 57, 1, 192);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (7, 56, 2, 294);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (8, 44, 3, 263);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (9, 52, 3, 199);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (10, 57, 2, 168);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (11, 54, 2, 239);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (12, 48, 2, 275);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (13, 49, 2, 266);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (14, 64, 2, 211);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (15, 58, 2, 283);
INSERT INTO DATA.HEART (ID, AGE, THAL, CHOL) VALUES (16, 50, 2, 219);
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And you're done! (kind of, you'd still have to actually run the INSERT script)&lt;/p&gt;

&lt;h1&gt;
  
  
  Caveats
&lt;/h1&gt;

&lt;p&gt;This post doesn't cover how to deal with internal commas and special character escaping weirdness. But it is a good starting point for a bare-bones &lt;strong&gt;CSV parser&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I might follow this up with another small post about using this script to easily pull out and aggregate different columns.&lt;/p&gt;

&lt;p&gt;One of the things I've learned from using this approach to deal with CSV files is that the simplest tool is often the best and can yield great results without much overhead.&lt;/p&gt;

&lt;p&gt;If you enjoyed this post or have any thoughts, feel free to comment or find me on &lt;a href="https://twitter.com/cishiv_"&gt;Twitter&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;p&gt;IFS Usage - &lt;a href="https://bash.cyberciti.biz/guide/$IFS"&gt;https://bash.cyberciti.biz/guide/$IFS&lt;/a&gt;&lt;/p&gt;

</description>
      <category>bash</category>
      <category>productivity</category>
      <category>sql</category>
      <category>database</category>
    </item>
    <item>
      <title>Finally published my personal portfolio/blog</title>
      <dc:creator>Shivan Moodley</dc:creator>
      <pubDate>Mon, 13 Jan 2020 19:05:57 +0000</pubDate>
      <link>https://forem.com/cishiv/finally-published-my-personal-portfolio-blog-4bn1</link>
      <guid>https://forem.com/cishiv/finally-published-my-personal-portfolio-blog-4bn1</guid>
      <description>&lt;p&gt;After a few iterations and trying to strive for the 'perfect' blog and personal portfolio, I finally caved and have published my site for the world to see.&lt;/p&gt;

&lt;p&gt;You can find it &lt;a href="https://shivan.dev"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And as a bonus, I quickly added in a dark mode toggle using the &lt;a href="https://github.com/donavon/use-dark-mode"&gt;use-dark-mode&lt;/a&gt; hook.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4Xgxkw72--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/ap37rcvra9sspmo6tf23.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4Xgxkw72--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/ap37rcvra9sspmo6tf23.gif" alt="GIF should dark mode toggle" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Things I want to improve on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a11y : I haven't given any thought to accessibility yet, and it's the first on my list of things to tackle&lt;/li&gt;
&lt;li&gt;responsiveness and device consistency : I have only tested on my mobile device and laptop. So i'm not exactly sure how good this looks on other resolutions and devices&lt;/li&gt;
&lt;li&gt;posts : currently I load posts into a Firebase real-time database via a manual JSON payload upload. I want to automate this process.&lt;/li&gt;
&lt;li&gt;colors: color is subjective, so even though I like then color scheme, everyone else may not feel the same!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Any feedback is appreciated, I plan to post canonically on my personal site then cross-post to DEV!&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>react</category>
    </item>
    <item>
      <title>What is your prototyping stack?</title>
      <dc:creator>Shivan Moodley</dc:creator>
      <pubDate>Mon, 06 Jan 2020 05:15:59 +0000</pubDate>
      <link>https://forem.com/cishiv/what-is-your-prototyping-stack-23b9</link>
      <guid>https://forem.com/cishiv/what-is-your-prototyping-stack-23b9</guid>
      <description>&lt;p&gt;If I ever need to put together a PoC or test out an idea I generally tend toward using React (for anything visual), Go + Gorilla (api/web server stuff) and Heroku for hosting. (Although now I've started leaning toward DigitalOcean even though it takes slightly longer on the config side)&lt;/p&gt;

&lt;p&gt;What's your preferred 'playground' tech stack?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>A 2019 retrospective and looking forward to 2020</title>
      <dc:creator>Shivan Moodley</dc:creator>
      <pubDate>Wed, 01 Jan 2020 14:11:23 +0000</pubDate>
      <link>https://forem.com/cishiv/a-2019-retrospective-and-looking-forward-to-2020-325d</link>
      <guid>https://forem.com/cishiv/a-2019-retrospective-and-looking-forward-to-2020-325d</guid>
      <description>&lt;p&gt;2019 was a year of 'doing stuff' for me. With the momentum I gathered this year, I want to make 2020 the year of 'doing more stuff'.&lt;/p&gt;

&lt;p&gt;So in the spirit of 'doing more stuff', let's start 2020 off on the right foot.&lt;/p&gt;

&lt;p&gt;I've hacked together a quick visualisation of 2019 with React!&lt;/p&gt;

&lt;p&gt;Check it out &lt;a href="https://retrospective-2019.herokuapp.com/"&gt;here&lt;/a&gt; (not optimised for mobile 😉)&lt;/p&gt;

&lt;p&gt;The things I want to do in 2020 are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Continue learning React and mastering all it's intricacies &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Launch a SaaS product and get a few users&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set up a personal blog and post there first and link to DEV&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Be an active part of the DEV community and build a following&lt;/li&gt;
&lt;li&gt;Improve my understanding of Go&lt;/li&gt;
&lt;li&gt;Continue my focus on building scalable systems in Java and Spring Boot&lt;/li&gt;
&lt;li&gt;Keep going with gym!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And of course, I will be writing about most of it here. This post is mostly to build some accountability for myself.&lt;/p&gt;

</description>
      <category>yearinreview</category>
      <category>2019</category>
      <category>career</category>
      <category>motivation</category>
    </item>
    <item>
      <title>How often do you do informal retrospectives?</title>
      <dc:creator>Shivan Moodley</dc:creator>
      <pubDate>Sun, 29 Dec 2019 23:02:57 +0000</pubDate>
      <link>https://forem.com/cishiv/how-often-do-you-do-informal-retrospectives-5dnc</link>
      <guid>https://forem.com/cishiv/how-often-do-you-do-informal-retrospectives-5dnc</guid>
      <description>&lt;p&gt;Recently I've been doing informal retrospectives at the end of each 'development' session. (Just keeping the notes in Google docs for now)&lt;/p&gt;

&lt;p&gt;Usually it's just writing down what I've done, what I'd like to do next and the things I hacked and need to fix. (or reminders to do something that I've been meaning to do for a while)&lt;/p&gt;

&lt;p&gt;Does any one do anything similar? Or maybe with a different format?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Deploying to Heroku: Docker, Go and React</title>
      <dc:creator>Shivan Moodley</dc:creator>
      <pubDate>Sun, 29 Dec 2019 12:54:14 +0000</pubDate>
      <link>https://forem.com/cishiv/deploying-to-heroku-docker-go-and-react-38hh</link>
      <guid>https://forem.com/cishiv/deploying-to-heroku-docker-go-and-react-38hh</guid>
      <description>&lt;p&gt;We're going to deploy a &lt;strong&gt;React Application&lt;/strong&gt; to &lt;strong&gt;Heroku&lt;/strong&gt; that will be served from a &lt;strong&gt;Go backend&lt;/strong&gt; which is then neatly wrapped in a &lt;strong&gt;Docker image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll need the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Golang (I am using v1.13.5)&lt;/li&gt;
&lt;li&gt;npm&lt;/li&gt;
&lt;li&gt;A text editor (VSCode is what I am using)&lt;/li&gt;
&lt;li&gt;A unix based terminal&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;A Heroku account and the Heroku CLI&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What is Heroku?
&lt;/h1&gt;

&lt;p&gt;From their &lt;a href="https://www.heroku.com/home"&gt;page&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Heroku is a platform as a service (PaaS) that enables developers to build, run, and operate applications entirely in the cloud."&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Use CRA to set up a React app
&lt;/h1&gt;

&lt;p&gt;We're going to use &lt;a href="https://reactjs.org/docs/create-a-new-react-app.html"&gt;create-react-app&lt;/a&gt; to bootstrap our React project.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx create-react-app deployment-demo&lt;/code&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Write the Go backend to serve it
&lt;/h1&gt;

&lt;p&gt;Let's change directory into our newly created react project, create a sub-directory called &lt;code&gt;server&lt;/code&gt; and create a file called &lt;strong&gt;server.go&lt;/strong&gt; in it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cd deployment-demo/ &amp;amp;&amp;amp; mkdir "server" &amp;amp;&amp;amp; cd "$_" &amp;amp;&amp;amp; touch server.go&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Open up your favourite text editor and paste the following into &lt;strong&gt;server.go&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/*
        Grab the environment variable that has been hopefully set, but also set up a default
    */&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;defaultPort&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"8080"&lt;/span&gt;
    &lt;span class="c"&gt;/*
        Serve the contents of the build directory that was produced as a part of `npm run build` on the root `/`
    */&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./build"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;

    &lt;span class="c"&gt;/*
        Check if the port environment variable has been set and if so, use that, otherwise let's use a reasonable default
    */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;defaultPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caveat: Heroku gives us a port to bind our web application to, so we grab that from an environment variable, you can read more about it &lt;a href="https://devcenter.heroku.com/articles/runtime-principles#web-servers"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Build a &lt;code&gt;production&lt;/code&gt; version of our application
&lt;/h1&gt;

&lt;p&gt;From our current directory &lt;code&gt;../deployment-demo/server&lt;/code&gt; go up a level back to the root of our React project, and run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will produce a directory called &lt;code&gt;build&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's copy this into our &lt;code&gt;server&lt;/code&gt; directory, which will serve as our production payload.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cp -r build/ server/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We can now navigate back into our &lt;code&gt;server&lt;/code&gt; directory and start deploying.&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker
&lt;/h1&gt;

&lt;p&gt;Create the following Dockerfile in the &lt;strong&gt;server&lt;/strong&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Stage 1
FROM golang:alpine as builder
RUN apk update &amp;amp;&amp;amp; apk add --no-cache git
RUN mkdir /build 
ADD . /build/
WORKDIR /build
RUN go get -d -v
RUN go build -o deployment-demo .
# Stage 2
FROM alpine
RUN adduser -S -D -H -h /app appuser
USER appuser
COPY --from=builder /build/ /app/
WORKDIR /app
CMD ["./deployment-demo"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Heroku
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Create an app
&lt;/h2&gt;

&lt;p&gt;If you don't have a Heroku account, go and &lt;a href="https://signup.heroku.com/"&gt;create&lt;/a&gt; one! (it should take less than 5 minutes)&lt;/p&gt;

&lt;p&gt;Once that's done, you're going to need the Heroku CLI, which can be easily installed on Ubuntu by running:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sudo snap install heroku --classic&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After installing the CLI, run the following command to login:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;heroku login&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This will open a browser window for you to login (it's an extremely cool system)&lt;/p&gt;

&lt;p&gt;Now run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;heroku create [YOUR_APP_NAME]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;in the &lt;code&gt;deployment-demo/server/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;An app will be created for you on your Heroku account and you should be able to see it on your &lt;a href="https://dashboard.heroku.com/"&gt;dashboard&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Container Registry
&lt;/h2&gt;

&lt;p&gt;The Heroku Container Registry allows you to deploy Docker images to Heroku.&lt;/p&gt;

&lt;p&gt;You can read more about it &lt;a href="https://devcenter.heroku.com/articles/container-registry-and-runtime"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Run the following to login in to the registry:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;heroku container:login&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Run this command to build your Docker image and push it to Heroku:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;heroku container:push web --app [YOUR_APP_NAME]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Notice that we specify &lt;strong&gt;web&lt;/strong&gt;, this indicates the process type we want to associate with this application. Further reading on process types can be found &lt;a href="https://devcenter.heroku.com/articles/process-model"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And finally, run this to release the image to your application:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;heroku container:release web --app [YOUR_APP_NAME]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We should now be able to navigate to our application hosted on Heroku by running:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;heroku open --app [YOUR_APP_NAME]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And we should see the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6hDaHCiy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/0rfhrd634snglvyavvgj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6hDaHCiy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/0rfhrd634snglvyavvgj.png" alt="final application running on Heroku" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! That's all you need to do to deploy a Docker image running a Go server that serves a React application to Heroku!&lt;/p&gt;

</description>
      <category>go</category>
      <category>react</category>
      <category>heroku</category>
      <category>docker</category>
    </item>
    <item>
      <title>Building a simple CI/CD pipeline for local testing using Go, Docker, Minikube and a Bash script</title>
      <dc:creator>Shivan Moodley</dc:creator>
      <pubDate>Tue, 24 Dec 2019 18:14:04 +0000</pubDate>
      <link>https://forem.com/cishiv/building-a-simple-ci-cd-pipeline-for-local-testing-using-go-docker-minikube-and-a-bash-script-1647</link>
      <guid>https://forem.com/cishiv/building-a-simple-ci-cd-pipeline-for-local-testing-using-go-docker-minikube-and-a-bash-script-1647</guid>
      <description>&lt;h1&gt;
  
  
  Wat
&lt;/h1&gt;

&lt;p&gt;In this article I will detail the thought process and implementation of an automated build and deployment pipeline that I wrote to reduce the time taken to get feedback for code changes I make to my code bases.&lt;/p&gt;

&lt;p&gt;The bulk of this code is written in Go and Bash. It’s a very hacky implementation but was extremely fun to write and use. &lt;/p&gt;

&lt;p&gt;Disclaimer: This post is &lt;em&gt;quite&lt;/em&gt; code heavy, but I try to highlight the thought-process behind the code.&lt;/p&gt;

&lt;p&gt;You can find the code for this entire post here &lt;a href="https://github.com/cishiv/cicdexample" rel="noopener noreferrer"&gt;cicdexample&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a quick GIF of what we're going to build:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgwuepgbmd24fz2gujfr5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fgwuepgbmd24fz2gujfr5.gif" alt="GIF of the functionality"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;
  
  
  That sounds cool, but why?
&lt;/h1&gt;
&lt;h3&gt;
  
  
  Feedback is important
&lt;/h3&gt;

&lt;p&gt;The most important part for me when writing any sort of code is how fast I can get feedback.&lt;/p&gt;

&lt;p&gt;Earlier this year I started learning Go in my spare time and whilst learning, I wanted to Dockerize my application code (I was writing my first web app in Go) and eventually host it on Heroku.&lt;/p&gt;

&lt;p&gt;So this is what I was doing for a few days:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd5qhkscu4tyix8f7uv7i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fd5qhkscu4tyix8f7uv7i.png" alt="Flowchart depicting the process the author used prior to developing this pipeline"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In total, it was taking me ~ 10 minutes to verify new functionality in a &lt;br&gt;
'production' environment.&lt;br&gt;
No exactly bad turnaround time when you’re working on something for your day-job, but for a pet project it wasn’t really that fun.&lt;/p&gt;

&lt;p&gt;An added downside to the above pipeline is that it is really hard to test functionality when I don't have a consistent internet connection, which happens rather often. So I started to think about alternatives.&lt;/p&gt;

&lt;p&gt;I wanted to have minimal latency between &lt;code&gt;ctrl-s&lt;/code&gt; and testing the change - kind of like this diagram:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8lz15i6d7pot3lq2ntzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8lz15i6d7pot3lq2ntzo.png" alt="Flowchart depicting the process the author wants to use with this pipeline"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Environment consistency trade-off
&lt;/h3&gt;

&lt;p&gt;I realised that testing on Heroku wasn’t a hard requirement for my development process. But I did want some resemblance to my 'production' environment in my test environment. &lt;/p&gt;

&lt;p&gt;I started to sketch out a simple CI/CD pipeline I could run on my laptop that would allow me to test my application running in Docker on &lt;em&gt;similar&lt;/em&gt; cloud infrastructure.&lt;/p&gt;

&lt;p&gt;As an aside, I kind of knew that I was probably going to write something that already exists, but I also knew that it would be a fun exercise and learning experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  A simple start
&lt;/h3&gt;
&lt;h4&gt;
  
  
  What do you need to have?
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Go (any version will do for this article)&lt;/li&gt;
&lt;li&gt;Some Bash knowledge and a Unix terminal&lt;/li&gt;
&lt;li&gt;Minikube&lt;/li&gt;
&lt;li&gt;Kubectl&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An IDE or Text Editor (I interchangeably use Vim and VSCode throughout this article)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Linux (Ubuntu 18.04 LTS is the distro I am using)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Where do we start?
&lt;/h4&gt;

&lt;p&gt;I have some experience working with build tools like Jenkins and I also have some experience with running an automated build pipeline tied to an automatic deployment tool.&lt;/p&gt;

&lt;p&gt;So using the ideas and context I had from those tools. I started simply, I ignored Heroku for the moment and wrote a simple Shell script that would build my go code base, and then based on a command line argument, build and tag the Docker image(s). &lt;/p&gt;

&lt;p&gt;I extended the Shell script to include a &lt;code&gt;kubectl&lt;/code&gt; command to deploy to Minikube after building a docker image. &lt;/p&gt;

&lt;p&gt;I realised that now that I had this Shell script, I had in essence created a deployment and build job for my application. So I started putting together a Go app to automate the running of this script based on a trigger.&lt;/p&gt;
&lt;h2&gt;
  
  
  How exactly did you do that?
&lt;/h2&gt;

&lt;p&gt;To illustrate how I did this without complicating this post with excessive technical detail, I’ve created a little checklist to follow, which this article expands upon:&lt;/p&gt;

&lt;p&gt;a. Create a small Go application that serves a HTML page&lt;br&gt;
b. Dockerize it!&lt;br&gt;
c. Write a simple Bash script that builds the docker image&lt;br&gt;
d. Push the docker image into Minikube and see it run&lt;br&gt;
e. Write a small command line tool that runs the script automatically&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating the test application
&lt;/h3&gt;

&lt;p&gt;Let’s create an example Go application that starts a simple file server and serves a simple HTTP page (that uses &lt;a href="https://bulma.io/" rel="noopener noreferrer"&gt;Bulma CSS&lt;/a&gt; to make it look a bit better)  on ‘/’&lt;/p&gt;

&lt;p&gt;The web server looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// for heroku since we have to use the assigned port for the app&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PORT"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// if we are running on minikube or just running the bin&lt;/span&gt;
        &lt;span class="n"&gt;defaultPort&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"3000"&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no env var set for port, defaulting to "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;defaultPort&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;// serve the contents of the static folder on /&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./static"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;defaultPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./static"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"starting server on port "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;":"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The html file in &lt;code&gt;/static&lt;/code&gt; is quite simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;CI/CD Example&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"text-align: center"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"title is-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Example CI/CD Stuff&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;Changes should make this automatically redeploy to our local test environment&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, now if we build (&lt;code&gt;go build&lt;/code&gt;) this Go App and run it, we should be able to see the following at &lt;code&gt;localhost:3000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnw9pzp3w2mejxbsqr4w4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fnw9pzp3w2mejxbsqr4w4.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Containerisation
&lt;/h3&gt;

&lt;p&gt;We now have an app, which is really cool and does a whole bunch of stuff (nothing) but that is beside the point. We’re focused on how to get this little guy building himself. First things first, we need to Dockerize it.&lt;/p&gt;

&lt;p&gt;If you don’t have Docker installed, check out their docs here (&lt;a href="https://docs.docker.com/install/" rel="noopener noreferrer"&gt;https://docs.docker.com/install/&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Let's create the following simple 2-stage Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; git
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; /build 
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; . /build/&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /build&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go get &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; cicdexample .
&lt;span class="c"&gt;# Stage 2&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; alpine&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;adduser &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /app appuser
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /build/ /app/&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./cicdexample"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Dockerfile first creates a builder image with the entire contents of the local directory copied into a directory called &lt;code&gt;/build&lt;/code&gt; on the image. It then fetches the dependencies for our little app, and builds it - producing a binary called &lt;code&gt;cicdexample&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The second stage actually creates the image we will ultimately run. We use the base Alpine image and create a user called &lt;code&gt;appuser&lt;/code&gt; that uses a directory called &lt;code&gt;/app&lt;/code&gt; as it’s home directory.&lt;br&gt;
We then copy the contents of the &lt;code&gt;/build&lt;/code&gt; directory from the builder image into the &lt;code&gt;/app&lt;/code&gt; directory on the Alpine image, set the working directory to &lt;code&gt;/app&lt;/code&gt; and run the Go binary that we just copied. It’s important to note that we copy the entire &lt;code&gt;/build&lt;/code&gt; directory contents since we need the static resources directory that is not part of the Go binary.&lt;/p&gt;

&lt;p&gt;Once we’ve created the Dockerfile and can successfully run &lt;br&gt;
&lt;code&gt;docker build -t example:test .&lt;/code&gt; which produces (output is slightly trimmed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sending build context to Docker daemon  7.638MB
...
Successfully built 561ee4597a93
Successfully tagged example:test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we're ready to move on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bashing things into shape
&lt;/h3&gt;

&lt;p&gt;Next up we need a script to automate the process of building this Docker image for us. But we don’t just want to build an image with a script, we want to create a feedback loop using &lt;code&gt;go build&lt;/code&gt; so that we don’t waste time setting up a Docker image when our code doesn’t even compile.&lt;/p&gt;

&lt;p&gt;We don’t technically have to flag success from the result of &lt;code&gt;go build&lt;/code&gt;, but rather have the error output redirected to a file, which we can then check for ANY content, and signal failure if there is anything in &lt;code&gt;stderr&lt;/code&gt; as a result of &lt;code&gt;go build&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The logical steps for this script are best represented graphically:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0ojyslk3fn2igz0iiib1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F0ojyslk3fn2igz0iiib1.png" alt="Logic for the build script"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The script is as follows (with a nice timestamps and command line output included):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Timestamp Function&lt;/span&gt;
timestamp&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;date&lt;/span&gt; +&lt;span class="s2"&gt;"%T"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Temporary file for stderr redirects&lt;/span&gt;
&lt;span class="nv"&gt;tmpfile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Go build&lt;/span&gt;
build &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"⏲️    &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: started build script..."&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🏗️   &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: building cicdexample"&lt;/span&gt;
    go build 2&amp;gt;tmpfile
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; tmpfile &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;tmpfile
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"❌   &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: compilation error, exiting"&lt;/span&gt;
        &lt;span class="nb"&gt;rm &lt;/span&gt;tmpfile
        &lt;span class="nb"&gt;exit &lt;/span&gt;0
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Deploy to Minikube using kubectl&lt;/span&gt;
deploy&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🌧️    &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: deploying to Minikube"&lt;/span&gt;
    kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy.yml
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Orchestrate&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🤖  Welcome to the Builder v0.2, written by github.com/cishiv"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"build"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"docker"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;build
        buildDocker
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✔️    &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: complete."&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"👋  &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: exiting..."&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bin"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;build
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✔️    &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: complete."&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"👋  &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: exiting..."&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🤔   &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: missing build argument"&lt;/span&gt;
    &lt;span class="k"&gt;fi
else
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"--help"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"build - start a build to produce artifacts"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  docker - produces docker images"&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  bin - produces executable binaries"&lt;/span&gt;
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🤔  &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: no arguments passed, type --help for a list of arguments"&lt;/span&gt;
    &lt;span class="k"&gt;fi
fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it, with the varying arguments, gives us the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./build build bin&lt;/code&gt; (compilation failure)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🤖    Welcome to the Builder builder v0.2, written by github.com/cishiv
⏲️  16:40:47: started build script...
🏗️ 16:40:47: building cicdexample
# _/home/shiv/Work/dev/go/cicdexample
./main.go:25:1: syntax error: non-declaration statement outside function body
❌ 16:40:47: compilation error, exiting

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;./build build docker&lt;/code&gt; (slightly trimmed output)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🤖    Welcome to the Builder builder v0.2, written by github.com/cishiv
⏲️  16:40:05: started build script...
🏗️ 16:40:05: building cicdexample
🐋    16:40:06: building image example:test
Sending build context to Docker daemon  7.639MB
...
Successfully tagged example:test
✔️  16:40:06: complete.
👋    16:40:06: exiting...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seems reasonable, we can compile our code and build a docker image with one command.&lt;/p&gt;

&lt;p&gt;Before we head to the next step, let's have a look at our checklist so far:&lt;/p&gt;

&lt;p&gt;a. Create a small Go App that serves a HTML page ✔️&lt;br&gt;
b. Dockerize it! ✔️&lt;br&gt;
c. Write a simple Bash script that builds the docker image ✔️&lt;br&gt;
d. Push the docker image into Minikube and see it run&lt;br&gt;
e. Write a small command line tool that runs the script automatically&lt;/p&gt;
&lt;h3&gt;
  
  
  Minikube-ing
&lt;/h3&gt;

&lt;p&gt;We finally have some sort of automation for our build pipeline, but it doesn't really give us a way to test our application yet.&lt;/p&gt;

&lt;p&gt;Enter Minikube to save the day (sort of).&lt;/p&gt;

&lt;p&gt;We want our testing pipeline to be as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1xlagyeb9mk570d429ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F1xlagyeb9mk570d429ju.png" alt="Build pipeline inclusive of Minikube"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t have Minikube installed you can check out the docs here (&lt;a href="https://kubernetes.io/docs/tasks/tools/install-minikube/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/tasks/tools/install-minikube/&lt;/a&gt;) on how to get it up and running.&lt;/p&gt;

&lt;p&gt;You will also need to grab &lt;a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/" rel="noopener noreferrer"&gt;kubectl&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once Minikube is installed, it is as simple as running &lt;code&gt;minikube start&lt;/code&gt; to start up a single node cluster.&lt;/p&gt;

&lt;p&gt;The next step is setting up a Kubernetes deployment for our app, that we can push into the Minikube cluster.&lt;/p&gt;

&lt;p&gt;Since this is not an article about Kubernetes, I will keep this step short. We want to have a &lt;code&gt;deploy.yml&lt;/code&gt; file where we can tell Kubernetes to create a deployment and a service for our application. It would be nicer to have separate files for creating the deployment and service, but for this example, we’ll just recreate both of them every time we want to redeploy.&lt;/p&gt;

&lt;p&gt;So we need the following file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
      &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
      &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
        &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
        &lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stable&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example:test"&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
              &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
    &lt;span class="na"&gt;tier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;nodePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We tell kubernetes to create a service called &lt;code&gt;example&lt;/code&gt; that is exposed on NodePort 30000 (so that we can access it via a URL that doesn't change every time we recreate the service) , with an application running on port 3000 in the container.&lt;/p&gt;

&lt;p&gt;At this point, because we have specified a NodePort in our deployment descriptor we should be able to simply refresh our web page, and see our changes.&lt;/p&gt;

&lt;p&gt;To get the application deployed into the cluster, run the following command in the terminal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./build build docker &amp;amp;&amp;amp; kubectl apply -f deploy.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The application should now be live on the Minikube cluster and be exposed on a NodePort.&lt;/p&gt;

&lt;p&gt;To get the URL for the application, run this command:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;minikube service example --url&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You should get a URL similar to this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://10.0.0.101:30000&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;example&lt;/code&gt; is the service name we specified in our &lt;code&gt;deploy.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;If we navigate to our URL, we should see our app.&lt;/p&gt;

&lt;p&gt;We can now test our pipeline for the first time. &lt;/p&gt;

&lt;p&gt;Make a code change to &lt;code&gt;index.html&lt;/code&gt; and run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./build build docker &amp;amp;&amp;amp; kubectl apply -f deploy.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You might find that this doesn't actually work yet. The kubectl command outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deployment.apps/example unchanged
service/example unchanged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And rightly so, since we don't tag our Docker image differently each time we create it - Kubernetes doesn't actually recognise that our code has changed. A quick hack to remedy this, since we're testing locally, is to amend our command to the following:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./build build docker &amp;amp;&amp;amp; kubectl delete deployment example &amp;amp;&amp;amp; kubectl delete service example &amp;amp;&amp;amp; kubectl apply -f deploy.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This allows us to cleanly recreate the service and deployment every time we re-run the script. It's important to note that this is very much a hack, and a better way to do this would be to tag the Docker images differently each time they are produced, and update the &lt;code&gt;deploy.yml&lt;/code&gt; file with the correct Docker image tag.&lt;/p&gt;

&lt;p&gt;So running:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;./build build docker &amp;amp;&amp;amp; kubectl delete deployment example &amp;amp;&amp;amp; kubectl delete service example &amp;amp;&amp;amp; kubectl apply -f deploy.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Will allow us to see the change we made to our HTML.&lt;/p&gt;

&lt;p&gt;This seems reasonable, however to clean it up a bit, let's add the additional &lt;code&gt;kubectl&lt;/code&gt; commands to our Bash script.&lt;/p&gt;

&lt;p&gt;It is simple enough to do by adding the following function, and making a change to the conditional logic slightly, allowing for a &lt;code&gt;deploy&lt;/code&gt; parameter to be passed to the &lt;code&gt;./build build ...&lt;/code&gt; command:&lt;/p&gt;

&lt;p&gt;Function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy to Minikube using kubectl&lt;/span&gt;
deploy&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"🌧️    &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: deploying to Minikube"&lt;/span&gt;
    kubectl delete deployment example
    kubectl delete service example
    kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy.yml
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conditional logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"build"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"docker"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"deploy"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;build
            buildDocker
            deploy
        &lt;span class="k"&gt;else
            &lt;/span&gt;build
            buildDocker
        &lt;span class="k"&gt;fi
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"✔️    &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: complete."&lt;/span&gt;
        &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"👋  &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;timestamp&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;: exiting..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now run:&lt;br&gt;
&lt;code&gt;./build build docker deploy&lt;/code&gt; &lt;br&gt;
to quite easily drive code changes all the way from compilation to deployment! (with a turn-around time of ~1s)&lt;/p&gt;
&lt;h3&gt;
  
  
  The Final Act: True Automation
&lt;/h3&gt;

&lt;p&gt;Finally, we want to wrap the Bash script we have created into a purpose built Go application that automates this process.&lt;/p&gt;

&lt;p&gt;We have to decide on a trigger for our build pipeline. There are 2 options at this point, which are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Builds are triggered on a combination of time elapsed and file changes.&lt;/li&gt;
&lt;li&gt;Builds are triggered on commits into a git repository&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To illustrate the concept without much technical overhead, we will go with the first option&lt;/p&gt;

&lt;p&gt;This is essentially what we want to write:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fom4k3k03c2cfe105jv8r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fom4k3k03c2cfe105jv8r.png" alt="A more technical diagram of the automated build application"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I won't paste the entire source code here, however I will discuss a few key points regarding it (the full code can be found &lt;a href="https://github.com/cishiv/cicdexample/tree/master/builder" rel="noopener noreferrer"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;We need to address 5 problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What sort of hashes are we going to use for files?&lt;/li&gt;
&lt;li&gt;How often should the hash recalculation be done?&lt;/li&gt;
&lt;li&gt;How do we set up polling on an interval?&lt;/li&gt;
&lt;li&gt;How do we run our script in the context of a Go application?&lt;/li&gt;
&lt;li&gt;What race conditions are there?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first problem is solved quite succinctly in Go, we can calculate a sha256 hash for a file with just the following code snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CalculateHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absoluteFilePath&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;absoluteFilePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;HandleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second problem has a non-trivial answer and it specific to your use case, however it should be reasonable enough to recalculate hashes every 15 seconds or so - this means that we should have automated deployments running every 15 seconds if there was a code change in that window.&lt;/p&gt;

&lt;p&gt;I ran a few benchmarks on the actual time taken to run a build in the Go application so that we could make an educated guess on how often to poll for file changes. &lt;/p&gt;

&lt;p&gt;The snippet I used to run the benchmark is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Since&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s took %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simply add &lt;code&gt;defer stopwatch(time.Now(), "benchmark")&lt;/code&gt; at the top of the function you want to benchmark!&lt;/p&gt;

&lt;p&gt;After the benchmark I estimated that we need to allow for 7 seconds for a past build to complete, and 8 seconds of overhead for the unexpected. Which gives us a total of 15 seconds.&lt;/p&gt;

&lt;p&gt;Problem number 3 is solved by defining the following function and using it like illustrated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;DoEvery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;DoEvery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We simply create a function that will be invoked every X seconds.&lt;/p&gt;

&lt;p&gt;The 4th problem also has a rather simple and effective solution in Go, we can define an action &lt;code&gt;string&lt;/code&gt; and run it using the &lt;code&gt;os/exec&lt;/code&gt; package in Go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bytes"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"os/exec"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;runAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./build build docker deploy"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;runAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Taking action, running: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/bin/sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"-c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;outb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errb&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;outb&lt;/span&gt;
    &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;errb&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last problem is an important one, one glaring race condition is that if we monitor the hashes for ALL the files in a directory, then we will most likely get caught in some sort of recursive build loop, since we are actively changing the files we monitor (by producing a binary). We can borrow a concept from &lt;code&gt;git&lt;/code&gt; here, and implement a &lt;code&gt;whitelist&lt;/code&gt;, that is, a list of files to ignore in our hash calculation. Something to this affect,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;whiteList&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;CreateWhiteList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"./.ignore"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"no .ignore file found, race condition will ensue if jobs edit files -- will not create whitelist"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;scanner&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;whiteList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;whiteList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combining the solutions to these problems, we're able to produce &lt;a href="(https://github.com/cishiv/cicdexample/tree/master/builder)"&gt;this&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Building the builder with&lt;br&gt;
&lt;code&gt;go build -o pipeline&lt;/code&gt; will result in a binary called &lt;code&gt;pipeline&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can then move this binary into our working directory. We also need to create an &lt;code&gt;ignore&lt;/code&gt; file in our working directory to ignore the binary produced by the builder for our application.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ignore&lt;/code&gt; file is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline
cicdexample
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We ignore &lt;code&gt;.git&lt;/code&gt; programatically, since it's never something we want to include.&lt;/p&gt;

&lt;p&gt;We can finally run our builder &lt;/p&gt;

&lt;p&gt;&lt;code&gt;./pipeline&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Making a change to the &lt;code&gt;index.html&lt;/code&gt; of our app, should kick off an automated build and redeployment, exactly as we set out to do.&lt;/p&gt;

&lt;p&gt;As you can see from this output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shiv@shiv-Lenovo-ideapad-310-15IKB:~/Work/dev/go/cicdexample$ sudo ./pipeline
2019/12/24 18:23:42 map[]
2019/12/24 18:23:42 creating whitelist
2019/12/24 18:23:42 pipeline
2019/12/24 18:23:42 cicdexample
2019/12/24 18:23:42 builder
2019/12/24 18:23:42 building registry
2019/12/24 18:23:42 starting directory scan
2019/12/24 18:23:42 pipeline is whitelisted, not adding to registry
2019/12/24 18:23:42 computing hashes &amp;amp; creating map entries
2019/12/24 18:23:57 verifying hashes
2019/12/24 18:24:12 verifying hashes
2019/12/24 18:24:12 ./static/index.html old hashbebc0fe5b73e2217e1e61def2978c4d65b0ffc15ce2d4f36cf6ab6ca1b519c17new hash16af318df74a774939db922bcb4458a695b9a38ecf28f9ea573b91680771eb3achanged detected - updating hash, action required
2019/12/24 18:24:12 Taking action, running: ./build build docker deploy
2019/12/24 18:24:20 🤖    Welcome to the Builder builder v0.2, written by github.com/cishiv
⏲️  18:24:12: started build script...
🏗️ 18:24:12: building cicdexample
🐋    18:24:13: building image example:test
Sending build context to Docker daemon  10.11MB
...
Successfully tagged example:test
🌧️  18:24:19: deploying to Minikube
deployment.apps "example" deleted
service "example" deleted
deployment.apps/example created
service/example created
✔️  18:24:20: complete.
👋    18:24:20: exiting...

2019/12/24 18:24:20 
2019/12/24 18:24:20 --------------------------------------------------------------------------------
2019/12/24 18:24:27 verifying hashes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! We're done. We've successfully written, a useful - albeit simple CI/CD pipeline to improve our development process.&lt;/p&gt;

&lt;p&gt;What are the takeaways from this experience?&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips
&lt;/h2&gt;

&lt;p&gt;It might be tempting to use a pre-existing solution for this kind of pipeline, but my recommendation is that if you have the time and energy to write a small solution to a problem you’ve encountered, you definitely should. It forces you to think about how your application(s) work in a production setting as well as how your development process can be improved. It is also a lot of fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extensions
&lt;/h2&gt;

&lt;p&gt;There are many possible extensions to this project, some of which I want to tackle soon, I've listed some of the more interesting ones here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow for the creation of build scripts via the builder app&lt;/li&gt;
&lt;li&gt;Build from VCS (i.e git clone a repo and build it based on a job description)&lt;/li&gt;
&lt;li&gt;A UI for this build pipeline&lt;/li&gt;
&lt;li&gt;Run the builder app in Docker on Minikube itself &lt;/li&gt;
&lt;li&gt;Host the builder app on a cloud platform and have configurable deployments and builds&lt;/li&gt;
&lt;li&gt;Let users create &lt;code&gt;.json&lt;/code&gt; job files for builds&lt;/li&gt;
&lt;li&gt;Multi-language support&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;This kind of quick and dirty pipeline isn’t better than a pre-existing solution, but it is a lot more fun to use, since I can quickly make changes to it based on a need I may have.&lt;/p&gt;

&lt;p&gt;I am relatively new to Go, Kubernetes and Docker. So the style I used may not be best practise, but it does work for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;p&gt;I am currently using a similar pipeline to automate the deployment of a project that I am working on called Crtx (&lt;a href="http://crtx.xyz/" rel="noopener noreferrer"&gt;http://crtx.xyz/&lt;/a&gt;) - into a test environment as I write code. It is primarily a Go code base with multiple apps that are deployed continuously to a Minikube cluster. This pipeline makes testing data flows between applications much easier. &lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Remarks
&lt;/h2&gt;

&lt;p&gt;This is the first time I’ve written a technical article and would love to hear feedback on the approach I took, as well as on the actual content!&lt;/p&gt;

&lt;p&gt;I plan to write more about the tools I use and build, so if you enjoyed reading this please let me know!&lt;/p&gt;

&lt;p&gt;You can give me feedback on &lt;a href="https://twitter.com/cishiv_" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or right here in the comments ✍️&lt;/p&gt;

</description>
      <category>go</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>bash</category>
    </item>
  </channel>
</rss>
