<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Devops on dwmkerr.com</title><link>https://dwmkerr.com/categories/devops/</link><description>Recent content in Devops on dwmkerr.com</description><generator>Hugo -- gohugo.io</generator><language>en-uk</language><managingEditor>Dave Kerr</managingEditor><copyright>Copright &amp;copy; Dave Kerr</copyright><lastBuildDate>Sat, 29 Apr 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://dwmkerr.com/categories/devops/index.xml" rel="self" type="application/rss+xml"/><item><title>Semantic Versioning, Conventional Commits and Release Pull Requests for .NET with Google's Release Please Project</title><link>https://dwmkerr.com/release-please-dotnet/</link><pubDate>Sat, 29 Apr 2023 00:00:00 +0000</pubDate><guid>https://dwmkerr.com/release-please-dotnet/</guid><description>&lt;p&gt;&lt;a href="https://github.com/googleapis/release-please"&gt;Release Please&lt;/a&gt; is an excellent library from Google that simplifies the management of releases for projects. However, it is not immediately apparent from the Release Please documentation that you can very easily get this working for your .NET projects.&lt;/p&gt;
&lt;p&gt;In this article we&amp;rsquo;ll take a quick tour of how to configure release please for a .NET project and how it can save you time and effort.&lt;/p&gt;
&lt;h2 id="what-is-release-please"&gt;What is Release Please?&lt;/h2&gt;
&lt;p&gt;In a nutshell, Release Please is a tool that can be run as a CLI or a GitHub action, that looks over the commit history for a repository. Based on the commit history, the tool will attempt to identify whether there have been major, minor or patch level changes to the code, based on the &lt;a href="https://semver.org/"&gt;semantic versioning&lt;/a&gt; spec and the usage of &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;Conventional Commits&lt;/a&gt;. It will then open a pull request that updates the changelog for your project and sets a new version number, and allows you to then easily run custom release actions if the pull request is merged.&lt;/p&gt;
&lt;p&gt;This is easier to see with a few screenshots. I recently added Release Please to my &lt;a href="https://github.com/dwmkerr/SharpGL"&gt;SharpGL&lt;/a&gt; project. After making a change, I get a pull request like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/pull-request-summary.png" alt="Screenshot: Pull Request Summary"&gt;&lt;/p&gt;
&lt;p&gt;Going into the details of the pull request, what is particularly useful is that my changelog has been updated, as well as the version data in my &lt;code&gt;SharedAssemblyInfo.cs&lt;/code&gt; file:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/pull-request-details.png" alt="Screenshot: Pull Request Details"&gt;&lt;/p&gt;
&lt;h2 id="how-does-this-help"&gt;How does this help?&lt;/h2&gt;
&lt;p&gt;If you maintain a lot of open source projects, anything you can do to automate chores like this just brings rigour to the process and makes bringing in contributions far easier.&lt;/p&gt;
&lt;p&gt;A bug had been raised on the repo:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/sharpgl/issues/220"&gt;https://github.com/dwmkerr/sharpgl/issues/220&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This bug indicated that the published version on NuGet was mis-aligned with the version in the source code. This is obviously a real problem for consumers of the library as well as contributors.&lt;/p&gt;
&lt;p&gt;I use Release Please for most of my projects now, I particularly like having the release pull request to review, it makes it really easy to see what&amp;rsquo;ll be in the next published vesrion.&lt;/p&gt;
&lt;h2 id="how-do-you-configure-release-please-for-net"&gt;How do you configure Release Please for .NET?&lt;/h2&gt;
&lt;p&gt;It turns out that this is really easy! You can configure you manifest like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;release-type&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;simple&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;package-name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;sharpgl&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;extra-files&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SharpGL/AssemblyInfo.cs&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or directly in your GitHub pipeline configuration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;push&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;branches&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;main&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ubuntu-20.04&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;google-github-actions/release-please-action@v3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;token&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;release-type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;simple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;package-name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;sharpgl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;extra-files&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SharpGL/AssemblyInfo.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It is not immediately apparent from the Release Please documentation that you can manage version numbers in a C# file, but the after looking at the &lt;a href="https://github.com/googleapis/release-please/blob/main/docs/customizing.md#updating-arbitrary-files"&gt;Customising Guide&lt;/a&gt;, it turns out there is a &amp;lsquo;generic&amp;rsquo; file updater that simply looks for version numbers, based on regexes, in any arbitrary files. You just need to use some comments to show where these version numbers are. In my case, the code looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Reflection;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Runtime.CompilerServices;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Runtime.InteropServices;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[assembly: AssemblyProduct(&amp;#34;SharpGL&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[assembly: AssemblyCopyright(&amp;#34;Copyright © Dave Kerr 2023&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// x-release-please-start-version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[assembly: AssemblyVersion(&amp;#34;3.0.0.0&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[assembly: AssemblyFileVersion(&amp;#34;3.0.0.0&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// x-release-please-end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Adding these &lt;code&gt;x-release-please&lt;/code&gt; comments is enough for Release Please to be able to identify the lines that need to be updated.&lt;/p&gt;
&lt;h2 id="other-improvements"&gt;Other Improvements&lt;/h2&gt;
&lt;p&gt;There are two immediate improvements that you can make on top of these changes to make it even easier to handle releases.&lt;/p&gt;
&lt;h3 id="commit-linting"&gt;Commit Linting&lt;/h3&gt;
&lt;p&gt;The first improvement is to look at something like &lt;a href="https://github.com/conventional-changelog/commitlint"&gt;&lt;code&gt;commitlint&lt;/code&gt;&lt;/a&gt; to ensure that incoming pull requests have commits that meet the conventional commit spec. For Release Please to be able to properly infer the types of changes from the commit history, it is essential that &lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;conventional commits&lt;/a&gt; are used.&lt;/p&gt;
&lt;p&gt;Remember - even with a hook to check commit messages, you&amp;rsquo;ll need need to confirm that the author of the changes has structured the commit message appropriately, such as correctly identifying breaking changes.&lt;/p&gt;
&lt;h3 id="automated-publishing"&gt;Automated Publishing&lt;/h3&gt;
&lt;p&gt;It is possible to update your build pipeline to actually publish your packages when the release pull request is merged. Here&amp;rsquo;s an example for a .NET project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# The &amp;#39;main&amp;#39; workflow is used to create the &amp;#39;Release Please&amp;#39; pull&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# request. This PR is opened when we have a new commit to main.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# See:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# https://github.com/googleapis/release-please&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;push&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;branches&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;jobs&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;main&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;runs-on&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;windows-2022&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;steps&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Checkout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;actions/checkout@v3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Note - this is where you put your build / test steps. See:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# https://github.com/dwmkerr/sharpgl/blob/main/.github/workflows/main.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# For an example.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Now that we know the build runs, create a release PR if needed.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;uses&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;google-github-actions/release-please-action@v3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;id&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;with&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;token&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;release-type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;simple&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;package-name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;sharpgl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;extra-files&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; source/SharpGL/SharedAssemblyInfo.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# If we are building a release (i.e. we&amp;#39;ve merged from release-please) then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# we can deploy.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Publish&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;dotnet nuget push ./source/SharpGL/artifacts/packages/*.nupkg --source &amp;#39;https://api.nuget.org/v3/index.json&amp;#39; --api-key ${{secrets.NUGET_API_KEY}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;shell&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bash&lt;/span&gt; &lt;span style="color:#75715e"&gt;# bash for globs in commmand above...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;if&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${{ steps.release.outputs.release_created }}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="summary"&gt;Summary&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;d highly recommend this approach for .NET projects. Adopting semver and conventional commits should make managing contributions and your changelog easier. Release Please really is the icing on the cake - by having structured commits with strong semantics you can reduce overhead and effort and increase automation.&lt;/p&gt;
&lt;p&gt;I have not included much on enforcing conventional commits in the repository or this article at the moment, some of the best tooling for this uses the Node stack (Husky and commitlint specifically). The benefits of enforcing conventional commits may well be worth the extra effort of setting these tools up, it&amp;rsquo;s really up to you to decide.&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Modernising .NET projects for .NET Core and beyond!</title><link>https://dwmkerr.com/modernising-dotnet-projects/</link><pubDate>Wed, 01 Jul 2020 00:00:00 +0000</pubDate><guid>https://dwmkerr.com/modernising-dotnet-projects/</guid><description>&lt;p&gt;The world of .NET is going through a transformation. The .NET Framework is reaching end of life, &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/"&gt;.NET Core&lt;/a&gt; is an increasingly feature rich and robust platform to develop solutions which target Linux, MacOS, embedded devices, containers and more. There&amp;rsquo;s also the .NET Standard.&lt;/p&gt;
&lt;p&gt;But what does this mean for .NET &lt;em&gt;Framework&lt;/em&gt; projects? In this article I&amp;rsquo;ll describe how to modernise your .NET Framework projects for .NET Core, the .NET Standard and .NET 5, which is planned to be released this year. I&amp;rsquo;ll also explain the high level differences between the platforms and what the consequences of upgrading are for consumers, developers and maintainers.&lt;/p&gt;
&lt;!-- vim-markdown-toc GFM --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-net-framework-net-core-and-the-future"&gt;The .NET Framework, .NET Core and the Future&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-challenge-modernisation-and-compatibility"&gt;The Challenge: Modernisation and Compatibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-modernisation-process---introducing-our-two-villains"&gt;The Modernisation Process - Introducing our two Villains&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#step-1---understand-the-domain"&gt;Step 1 - Understand the Domain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-2---understand-the-goal---multi-platform-builds"&gt;Step 2 - Understand the Goal - Multi-Platform Builds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-3---migrate-projects-leaf-wise"&gt;Step 3 - Migrate Projects &amp;ldquo;Leaf-wise&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-4---refactor-rinse-repeat"&gt;Step 4 - Refactor, Rinse, Repeat&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-5---update-your-builds"&gt;Step 5 - Update Your Builds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-6---simplify"&gt;Step 6 - Simplify!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-7---test-test-test"&gt;Step 7 - Test, Test, Test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#step-8---document-compatibility"&gt;Step 8 - Document Compatibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-key-learnings"&gt;The Key Learnings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- vim-markdown-toc --&gt;
&lt;h1 id="the-net-framework-net-core-and-the-future"&gt;The .NET Framework, .NET Core and the Future&lt;/h1&gt;
&lt;p&gt;There&amp;rsquo;s a lot which has been written on this topic, but it can still be a little overwhelming to understand just how all of these things fit together.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a simple visual I&amp;rsquo;ve created to try and put things into context:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/dotnet-timeline.png" alt="Diagram: .NET Timeline"&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m only going to cover the bare essentials - but there are links to further reading on each topic if you want to go deeper. This article is mainly going to be focused on the practicality and consequence of migration and re-targeting.&lt;/p&gt;
&lt;p&gt;First, the &lt;strong&gt;.NET Framework&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The .NET Framework was created in 2002 as set of unified tools and standards to allow developers on the Microsoft Platform to more quickly build solutions, provide interoperability between languages and more. &lt;a href="https://dotnet.microsoft.com/learn/dotnet/what-is-dotnet-framework"&gt;Read more about the .NET Framework&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The .NET Framework rapidly gained popularity, partly due to the convenience of developing in C# rather than Basic or C/C++. C# provided a more developer friendly language than C or C++ for many use cases, and was heavily inspired by Java. &lt;a href="https://docs.microsoft.com/en-us/dotnet/csharp/"&gt;Read more about C#&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;With the increase in popularity, the .NET Framework started to have more frequent releases and became a standard part of the Windows operating system, installed out of the box rather than on-demand if needed.&lt;/li&gt;
&lt;li&gt;However - the .NET Framework only functioned on Microsoft Windows, which greatly limited its potential uses cases, even as more and more engineers used it for Web, Client Applications and mobile.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enter &lt;strong&gt;.NET Core&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microsoft signaled a &lt;em&gt;radical&lt;/em&gt; switch in their strategy with the appointment of &lt;a href="https://en.wikipedia.org/wiki/Satya_Nadella"&gt;Satya Nadella&lt;/a&gt;, becoming increasingly focused on open source, and more importantly, deciding that the Microsoft development toolchain should not &lt;em&gt;force&lt;/em&gt; users to use Windows as their execution environment&lt;/li&gt;
&lt;li&gt;.NET Core was developed as a lightweight version of the .NET Framework, which could run on multiple platforms - including Linux and MacOS. &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/"&gt;Read more about .NET Core&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;In a short period of time .NET Core became more and more feature rich, providing a lot of capabilities for web developers and front-end application developers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The challenges of &lt;strong&gt;divergence&lt;/strong&gt; and the &lt;strong&gt;.NET Standard&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As .NET Core became more feature rich, the API became closer to the .NET Framework - but they are still fundamentally different runtimes. A binary compiled for the .NET Core does not run on the .NET Framework and vice-versa.&lt;/li&gt;
&lt;li&gt;To deal with this issue, Microsoft developed the &lt;strong&gt;.NET Standard&lt;/strong&gt; - a specification of a set of APIs. If a runtime offered these APIs, then solutions built on &lt;em&gt;any runtime which meets the standard&lt;/em&gt; could run on any compliant platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What does this mean? Basically, the table below shows the consequences of this. If you build on .NET Core 2.0 (for example), you can also run on the .NET Framework 4.6.1. Mono 5.4, Unity 2018.1 and more, because all of these runtimes implement the &lt;em&gt;.NET Standard 2.0&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Of course, some features are always going to be very platform specific, so the standard started out small but has grown over time.&lt;/p&gt;
&lt;p&gt;Moving to &lt;strong&gt;convergence&lt;/strong&gt; and &lt;strong&gt;.NET&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Given that the later versions of the .NET Framework and .NET Core actually follow the same standard, the platforms are actually starting to become more and more similar.&lt;/li&gt;
&lt;li&gt;They are becoming &lt;em&gt;so&lt;/em&gt; similar that it no longer makes sense to maintain them separately. The next major version of &lt;em&gt;both&lt;/em&gt; platforms is &lt;strong&gt;.NET 5&lt;/strong&gt;. This is a new runtime which is the next version of .NET Core &lt;em&gt;and&lt;/em&gt; the .NET Framework.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This means that the .NET Framework and .NET Core are going to converge into a single platform, which will be wonderful for developers and simplify a complex landscape.&lt;/p&gt;
&lt;p&gt;But what does this mean if you have .NET Framework projects? How do we modernise, and do we have to make trade-offs around compatibility?&lt;/p&gt;
&lt;h1 id="the-challenge-modernisation-and-compatibility"&gt;The Challenge: Modernisation and Compatibility&lt;/h1&gt;
&lt;p&gt;I have a number of projects which target the .NET Framework. On &lt;em&gt;all&lt;/em&gt; of these projects I have had multiple requests to migrate to the .NET Core, but I have had to hold off on this work until I could really understand in detail a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What would this mean for &lt;em&gt;consumers&lt;/em&gt; of the libraries? Would they have to change the platform they use? Could this break things for them?&lt;/li&gt;
&lt;li&gt;What would this mean for &lt;em&gt;developers&lt;/em&gt; on the platform? Would they need to change their development environment? Would this cause problems?&lt;/li&gt;
&lt;li&gt;What would this mean for &lt;em&gt;maintainers&lt;/em&gt; of the libraries? Would this greatly increase build and deployment complexity?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Finally I have found the time to be able to start to address these issues in detail - hopefully the learnings will be useful to anyone who is maintaining a .NET codebase and thinking about the future.&lt;/p&gt;
&lt;h1 id="the-modernisation-process---introducing-our-two-villains"&gt;The Modernisation Process - Introducing our two Villains&lt;/h1&gt;
&lt;p&gt;There are two key projects I wanted to modernise. They are both reasonably well used, complex, and have some potentially serious complexities for multi-platform builds.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/sharpgl"&gt;&lt;strong&gt;SharpGL&lt;/strong&gt;&lt;/a&gt; is a library that allows developers to use &lt;a href="https://www.opengl.org/"&gt;OpenGL&lt;/a&gt; in .NET applications. The big challenge? OpenGL is cross platform, but SharpGL &lt;em&gt;specifically&lt;/em&gt; provides an interface to the &lt;em&gt;Windows&lt;/em&gt; version of OpenGL. Can this possibly be made more future-proof? Could it ever target other platforms?&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/sharpshell"&gt;&lt;strong&gt;SharpShell&lt;/strong&gt;&lt;/a&gt; is a library that allows developers to build &amp;lsquo;Shell Extensions&amp;rsquo; for Windows. Shell extensions are customisations to the Windows user interface, so would not be portable across platforms, but I still want to ensure that the project is future proof.&lt;/p&gt;
&lt;p&gt;What would be the experience with these two projects? I have other .NET projects, but they are far less popular and much more simple, my instinct is that if I can work through the process with &lt;em&gt;these&lt;/em&gt; projects, the others should be more straightforward.&lt;/p&gt;
&lt;p&gt;These are the steps I&amp;rsquo;ve followed to modernise. I&amp;rsquo;ll finish the article with a summary of the key learnings.&lt;/p&gt;
&lt;h2 id="step-1---understand-the-domain"&gt;Step 1 - Understand the Domain&lt;/h2&gt;
&lt;p&gt;I cannot stress this enough. In all meaningful technology work, &lt;em&gt;understand the domain&lt;/em&gt; you are dealing with. A quick Google on how to migrate, or following the formal migration guide was not enough for me. I knew I had to actually understand, at a reasonably detailed level, the differences in the runtime, the trade-offs, the process, the complexity.&lt;/p&gt;
&lt;p&gt;This article is the result of that work - sometimes writing about a topic is the best way to force yourself to learn it.&lt;/p&gt;
&lt;p&gt;Making changes rapidly and waiting to see what the consequences are can often work for small projects, internal tools and so on, but for a library which is relied upon by others is not good for the community. The last thing I wanted to do was make changes which had unintended consequences for users. So making sure that I learnt about this space, how things work under the hood, and what the expected changes in the future are was critical.&lt;/p&gt;
&lt;p&gt;Hopefully for others the process of understanding the domain will be a little easier with this article to cover the high level topics. During my actual process of writing and migrating, I went a lot deeper than this article goes.&lt;/p&gt;
&lt;p&gt;The key document to follow to actually &lt;em&gt;execute&lt;/em&gt; the migration is the excellent official &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;.NET Framework to .NET Core Porting Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="step-2---understand-the-goal---multi-platform-builds"&gt;Step 2 - Understand the Goal - Multi-Platform Builds&lt;/h2&gt;
&lt;p&gt;Given the understanding of the domain, it made it much easier to understand what the required steps would be. Essentially, all that would be needed would be to target a version of the .NET Framework which adheres to a recent version of the .NET Standard. Once this was done, in theory the project could be built for the .NET Framework &lt;em&gt;and&lt;/em&gt; for .NET Core, and also be ready for the upcoming .NET 5 release.&lt;/p&gt;
&lt;p&gt;Multi-platform builds are supported in Visual Studio 2019. These builds allow us to have a single codebase, but build libraries for multiple platforms (i.e. the .NET Framework and .NET Core). The resulting binaries can be packed as a single package, and when consumers install the package, the appropriate library is installed.&lt;/p&gt;
&lt;p&gt;This introduces the first of the significant consequences - modernising your project means you must migrate it to Visual Studio 2019.&lt;/p&gt;
&lt;p&gt;In the past, this might have been more of an issue, licenses for Visual Studio were expensive, and many organisations were locked onto specific versions for compatibility issues (or because they were slow to upgrade). This seems to be the case less often nowadays, but is still an important consideration.&lt;/p&gt;
&lt;p&gt;My projects were using Visual Studio 2017. This is how the project properties looked:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/sharpgl-target-framework-2017.png" alt="Screenshot: SharpGL Target Framework Properties for Visual Studio 2017"&gt;&lt;/p&gt;
&lt;p&gt;Unsurprisingly the .NET Standard isn&amp;rsquo;t mentioned. Time to upgrade to 2019. While I installed it I could reminisce about the excitement of buying Visual C++ .NET Learning Edition:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/visual-cpp-dotnet-learning-edition.jpg" alt="Photo: Visual C++ .NET 2003 Learning Edition"&gt;&lt;/p&gt;
&lt;p&gt;And try and remember what is was like to be a 15 years old. I wonder if that box set is still kicking around somewhere, I want to see it again. So much has changed. But long install processes for Visual Studio haven&amp;rsquo;t, at least they kept that:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/install-visual-studio-2019.png" alt="Screenshot: Visual Studio 2019 Installer"&gt;&lt;/p&gt;
&lt;p&gt;When installing, remember to enable the .NET Core features.&lt;/p&gt;
&lt;h2 id="step-3---migrate-projects-leaf-wise"&gt;Step 3 - Migrate Projects &amp;ldquo;Leaf-wise&amp;rdquo;&lt;/h2&gt;
&lt;p&gt;As per the &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;Porting Guide&lt;/a&gt;, we need to migrate each of the projects which make up the solution, starting with the &amp;rsquo;leaves&amp;rsquo; (projects which don&amp;rsquo;t depend on other projects) and then moving up the tree to the &amp;lsquo;root&amp;rsquo; projects (projects which are depended on by others).&lt;/p&gt;
&lt;p&gt;Visually, for a solution like SharpGL, that would mean the projects will need to be converted in the following order:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/sharpgl-project-structure.png" alt="Diagram: SharpGL Project Dependency Graph"&gt;&lt;/p&gt;
&lt;p&gt;I was expecting each project to have quite different experiences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SharpGL.Serialization&lt;/code&gt; is just a set of classes which load data from files. In theory, this library should become completely portable.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpGL.WPF&lt;/code&gt; and &lt;code&gt;SharpGL.WinForms&lt;/code&gt; are &lt;em&gt;specifically&lt;/em&gt; for Windows front-end technologies. I expected these to be able to be ported, but don&amp;rsquo;t expect them to work on other platforms (in the future there might be &lt;code&gt;SharpGL.OSx&lt;/code&gt;, or &lt;code&gt;SharpGL.Gnome&lt;/code&gt;, who knows)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpGL.SceneGraph&lt;/code&gt; is a set of classes which represent 3D scenes - things like lights, cameras, materials and so on. I expect &lt;em&gt;some&lt;/em&gt; of this to &amp;lsquo;just work&amp;rsquo;, but things like image loading to perhaps need some tweaking.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SharpGL&lt;/code&gt; is just a wrapper around the Windows &lt;code&gt;opengl32.dll&lt;/code&gt; library. I can&amp;rsquo;t imagine this &lt;em&gt;working&lt;/em&gt; anywhere but Windows, but how would the project structure porting go and would it build?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The details on &lt;em&gt;how&lt;/em&gt; to migrate a project are in the &lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;Porting Guide&lt;/a&gt;, but the general approach will be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Attempt to convert to the latest project format with the &lt;code&gt;try-convert&lt;/code&gt; tool&lt;/li&gt;
&lt;li&gt;Re-target the project to the .NET Framework 4.7.2 (the first version which supports the .NET standard)&lt;/li&gt;
&lt;li&gt;Repeat for projects which this project depends on, walking the tree of projects to the root&lt;/li&gt;
&lt;li&gt;Run the Portability Analysis tool to see if there are APIs which are not available on certain platforms&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is how you project might look after migration, having run the &lt;code&gt;try-convert&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/migrate-project.png" alt="Screenshot: Ported Visual Studio Project"&gt;&lt;/p&gt;
&lt;p&gt;Now we just need to edit the project files and change the line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;4.7.2&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;TargetFrameworks&amp;gt;&lt;/span&gt;netcoreapp2.0;netcoreapp3.0;netcoreapp3.1;net40;net45;net472&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFrameworks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The targets you will choose will depend on the APIs you want to use. There is an Portability Analysis extension available which can build a portability report, here&amp;rsquo;s what one looks like:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/portability-report-summary.png" alt="Screenshot: Portability Report Summary"&gt;&lt;/p&gt;
&lt;p&gt;This will also show the &lt;em&gt;specific&lt;/em&gt; APIs which are not compatible with specific targets:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/portability-report.png" alt="Screenshot: Portability Report Details"&gt;&lt;/p&gt;
&lt;p&gt;Now it&amp;rsquo;s time to move to the next step.&lt;/p&gt;
&lt;h2 id="step-4---refactor-rinse-repeat"&gt;Step 4 - Refactor, Rinse, Repeat&lt;/h2&gt;
&lt;p&gt;This is the tricky part. You&amp;rsquo;ll now need to work out whether you want to &lt;em&gt;remove&lt;/em&gt; API calls which are not portable, try and use alternatives, or conditionally compile the code for different platforms.&lt;/p&gt;
&lt;p&gt;If you are using non-portable APIs you may need to use conditional blocks to execute different code depending on the framework used. The &lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-target-frameworks"&gt;Target frameworks in SDK-style projects&lt;/a&gt; guide shows how to do this.&lt;/p&gt;
&lt;p&gt;You may also have to manually edit the project file to ensure that certain dependencies are &lt;em&gt;only&lt;/em&gt; used for certain targets. You solution file and dependencies may end up looking something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/conditional-dependencies.png" alt="Screenshot: Conditional Dependencies"&gt;&lt;/p&gt;
&lt;p&gt;Once you have reloaded the project you&amp;rsquo;ll see your dependencies can now be specified on a per-framework basis, and a build generates assemblies for each of the targets:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/generated-assemblies.png" alt="Screenshot: Generated Assemblies"&gt;&lt;/p&gt;
&lt;p&gt;This process might be simple, or complex, depending on the nuances of your project. For me it was fairly iterative - starting by targeting only &lt;code&gt;net40&lt;/code&gt; (the original target framework which I&amp;rsquo;d used), then adding more and more targets.&lt;/p&gt;
&lt;p&gt;Some targets will simply not be possible - for example .NET Core only supports WinForms and WPF from .NET Core 3.0 onwards; you won&amp;rsquo;t be able to build a WinForms or WPF assembly which targets a lower version, the framework doesn&amp;rsquo;t support it.&lt;/p&gt;
&lt;h2 id="step-5---update-your-builds"&gt;Step 5 - Update Your Builds&lt;/h2&gt;
&lt;p&gt;At this stage, having fixed compatibility issues, you should have code which builds in Visual Studio.&lt;/p&gt;
&lt;p&gt;Now I would recommend porting all of your build code to use the &lt;code&gt;dotnet&lt;/code&gt; build system. This is going to maximise the portability and future-proof your project, you&amp;rsquo;ll be able to run the builds on multiple platforms and are using the preferred standard tool (&lt;code&gt;msbuild&lt;/code&gt; will essentially become legacy).&lt;/p&gt;
&lt;p&gt;The way I like to structure things personally is have a set of scripts which you can run to build, test and package the code locally. You can then call these scripts from you CI tool of choice to automate things, but still keep the logic in your own code, rather than hidden away in a build system.&lt;/p&gt;
&lt;p&gt;For example, in my SharpGL project I have the following scripts:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Script&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ensure your machine can run builds by installing necessary components such as &lt;code&gt;nunit&lt;/code&gt;. Should only need to be run once.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;build.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Build all solutions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run all tests, including those in samples.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;coverage.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create a coverage report. Reports are written to &lt;code&gt;./artifacts/coverage&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pack.ps1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create all of the SharpGL NuGet packages, which are copied to &lt;code&gt;./artifacts/packages&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I updated my scripts to use the &lt;code&gt;dotnet&lt;/code&gt; tool. For example, the &amp;lsquo;build&amp;rsquo; script looks something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ps" data-lang="ps"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Run&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;build,&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hiding&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;documentation&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;warnings&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pinvoke&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;code.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;$buildCommand&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;=&amp;#34;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;msbuild&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$PSScriptRoot\SharpGL.sln&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-noWarn:CS1591&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-noWarn:CS1573&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-t:Rebuild&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;-p:Configuration=Release&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Write-Host&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;Running:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;&amp;#34;$buildCommand&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;Invoke-Expression&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;$buildCommand&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the &amp;lsquo;pack&amp;rsquo; script looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ps" data-lang="ps"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL/SharpGL.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.SceneGraph/SharpGL.SceneGraph.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.Serialization/SharpGL.Serialization.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.WinForms/SharpGL.WinForms.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dotnet&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pack&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-restore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;--no-build&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;&amp;#34;$PSScriptRoot&lt;/span&gt;/Core/SharpGL.WPF/SharpGL.WPF.csproj&amp;#34; &lt;span style="color:#a6e22e"&gt;-c:Release&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The actual scripts are a little more complex. But the key thing here is that I can run &lt;em&gt;any&lt;/em&gt; part of the CI/CD process locally (to test, debug and so on) or on a CI/CD platform.&lt;/p&gt;
&lt;p&gt;You will most likely have to &lt;em&gt;conditionally&lt;/em&gt; reference certain components. The dependency for &lt;code&gt;net40&lt;/code&gt; might be different to that for &lt;code&gt;netcoreapp3.0&lt;/code&gt;. You&amp;rsquo;ll see that in many of my project files there is now code like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Design&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net40&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Design&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Design&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Windows.Forms&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net40&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Windows.Forms&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Reference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Windows.Forms&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.0&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.1&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.CSharp&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.7.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.0&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;netcoreapp3.1&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net45&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;System.Data.DataSetExtensions&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(TargetFramework)&amp;#39; == &amp;#39;net472&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In my case quite a bit of trial and error was needed to find the appropriate references for each platform.&lt;/p&gt;
&lt;h2 id="step-6---simplify"&gt;Step 6 - Simplify!&lt;/h2&gt;
&lt;p&gt;One benefit I have found during this process is that you can &lt;em&gt;simplify&lt;/em&gt; your projects. You no longer need any kind of &amp;lsquo;automated NuGet restore&amp;rsquo; functionality. This means you can remove code like this from your project files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Project=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(SolutionDir)\.nuget\NuGet.targets&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Exists(&amp;#39;$(SolutionDir)\.nuget\NuGet.targets&amp;#39;)&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Target&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Name=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;EnsureNuGetPackageBuildImports&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BeforeTargets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;PrepareForBuild&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ErrorText&amp;gt;&lt;/span&gt;This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.&lt;span style="color:#f92672"&gt;&amp;lt;/ErrorText&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Error&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;!Exists(&amp;#39;..\packages\NUnit.3.11.0\build\NUnit.props&amp;#39;)&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Text=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$([System.String]::Format(&amp;#39;$(ErrorText)&amp;#39;, &amp;#39;..\packages\NUnit.3.11.0\build\NUnit.props&amp;#39;))&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Error&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;!Exists(&amp;#39;..\packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props&amp;#39;)&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Text=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$([System.String]::Format(&amp;#39;$(ErrorText)&amp;#39;, &amp;#39;..\packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props&amp;#39;))&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can also remove your &lt;code&gt;project.json&lt;/code&gt; as all of the data is now in the &lt;code&gt;csproj&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Another nice update is that you no longer need to maintain an &lt;code&gt;AssemblyInfo.cs&lt;/code&gt; file; you can keep all of your assembly metadata in the &lt;code&gt;csproj&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Finally, you can almost certainly remove any &lt;code&gt;nuspec&lt;/code&gt; files - all NuGet packaging data can also be embedded in the &lt;code&gt;csproj&lt;/code&gt; file. For example, here&amp;rsquo;s what my SharpShell project metadata looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Project&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Sdk=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.NET.Sdk.WindowsDesktop&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;TargetFrameworks&amp;gt;&lt;/span&gt;netcoreapp2.0;netcoreapp3.0;netcoreapp3.1;net40;net45;net472&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFrameworks&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;OutputType&amp;gt;&lt;/span&gt;Library&lt;span style="color:#f92672"&gt;&amp;lt;/OutputType&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- The following properies are used to manage how the project is packaged. --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageId&amp;gt;&lt;/span&gt;SharpShell&lt;span style="color:#f92672"&gt;&amp;lt;/PackageId&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Copyright&amp;gt;&lt;/span&gt;Copyright (c) Dave Kerr 2020&lt;span style="color:#f92672"&gt;&amp;lt;/Copyright&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageProjectUrl&amp;gt;&lt;/span&gt;https://github.com/dwmkerr/sharpshell&lt;span style="color:#f92672"&gt;&amp;lt;/PackageProjectUrl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;RepositoryUrl&amp;gt;&lt;/span&gt;https://github.com/dwmkerr/sharpshell&lt;span style="color:#f92672"&gt;&amp;lt;/RepositoryUrl&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Version&amp;gt;&lt;/span&gt;3.1.1.0&lt;span style="color:#f92672"&gt;&amp;lt;/Version&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Authors&amp;gt;&lt;/span&gt;Dave Kerr&lt;span style="color:#f92672"&gt;&amp;lt;/Authors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Company&amp;gt;&lt;/span&gt;Dave Kerr&lt;span style="color:#f92672"&gt;&amp;lt;/Company&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageTags&amp;gt;&lt;/span&gt;Shell;SharpShell;COM;Context Menu;Icon Handler&lt;span style="color:#f92672"&gt;&amp;lt;/PackageTags&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Description&amp;gt;&lt;/span&gt;SharpShell is a framework that lets you build Windows Shell Extensions using .NET Core or the .NET Framework.&lt;span style="color:#f92672"&gt;&amp;lt;/Description&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- ...snip... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This helps to keep a lot of the project dependency and property data in one place and is probably more convenient for many users.&lt;/p&gt;
&lt;p&gt;You can see the &lt;a href="https://github.com/dwmkerr/sharpgl/pull/177"&gt;Pull Request&lt;/a&gt; for SharpGL to see how the project files were updated in this case. You can also see the &lt;a href="https://github.com/dwmkerr/sharpshell/pull/331"&gt;SharpShell Pull Request&lt;/a&gt;. The SharpShell version is still work in progress at the time of writing.&lt;/p&gt;
&lt;h2 id="step-7---test-test-test"&gt;Step 7 - Test, Test, Test&lt;/h2&gt;
&lt;p&gt;Now for the fun part. You are going to &lt;em&gt;really&lt;/em&gt; have to test the new packages on each platform. Sadly, this kind of migration is not something which will have issues exposed via unit tests, you&amp;rsquo;ll need to create test projects which import your packages, ideally for each platform, and make sure they work. There could be runtime errors, particularly if you have made mistakes with the references.&lt;/p&gt;
&lt;p&gt;Many issues will be caught at compile time - some will not.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a screenshot of me having fun trying out the .NET Framework 4 package for WinForms, and the .NET Core 3.1 package for WPF:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/test-packages.png" alt="Screenshot: Testing SharpGL"&gt;&lt;/p&gt;
&lt;p&gt;How you test your packages will be very dependent on what you are building. If it is highly platform specific then you will likely have to do lots of testing. If it is fairly self-contained code then you might be able to get away with some basic smoke testing.&lt;/p&gt;
&lt;h2 id="step-8---document-compatibility"&gt;Step 8 - Document Compatibility&lt;/h2&gt;
&lt;p&gt;If you are supporting multiple platforms and frameworks, it&amp;rsquo;s going to be a lot of help to consumers of your code if you can be very clear about &lt;em&gt;what is supported&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This may be more complex than you think. Your library may run fine as part of a .NET Core Console Application on Windows - but does it work on MacOS? What about Linux?&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a screenshot I would never have imagined when I started the SharpGL project - a terminal application running on MacOS which is using the &lt;code&gt;SharpGL.Serialization&lt;/code&gt; library to load geometry from a file:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/sharpgl-on-mac.png" alt="Screenshot: Loading Geometry in SharpGL on MacOS"&gt;&lt;/p&gt;
&lt;p&gt;Now of course for something like SharpGL to run on a Mac or Linux, a lot more work would be needed. SharpGL is at its core nothing more than a wrapper around &lt;code&gt;opengl32.dll&lt;/code&gt; on Windows, on other platforms there are no DLLs, but OpenGL &lt;em&gt;is&lt;/em&gt; still available. So support is possible, but not ready yet. So at this stage, docmenting what you know works &lt;em&gt;as well as what doesn&amp;rsquo;t&lt;/em&gt; will be really helpful.&lt;/p&gt;
&lt;p&gt;You might also want to preserve your &amp;lsquo;pre-migration&amp;rsquo; code in a separate branch, in case you have users who for some reason have issues migrating and need to use an older version. For SharpGL, I updated the project page to indicate compatibility, what has been tested and so on:&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/readme-compatability.png" alt="Screenshot: SharpGL README showing compatibility information"&gt;&lt;/p&gt;
&lt;h1 id="the-key-learnings"&gt;The Key Learnings&lt;/h1&gt;
&lt;p&gt;Here are the key learnings which stood out for me as I worked on migration of these projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consumer Experience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you are careful, you don&amp;rsquo;t have to break anything for consumers - with multi-targeting you can &lt;em&gt;still&lt;/em&gt; target older frameworks.&lt;/li&gt;
&lt;li&gt;You can potentially greatly increase the compatibility of your projects by offering support for .NET Core.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Developer Experience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need to upgrade to Visual Studio 2019&amp;hellip;&lt;/li&gt;
&lt;li&gt;&amp;hellip;however, you can use Visual Studio for Mac or even the command-line to build across many platforms.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Maintainer Experience&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You will have a much larger set of potential consumers, but you will likely find bugs which are framework or platform specific.&lt;/li&gt;
&lt;li&gt;You will likely need to work on migrating your project files and use the latest &lt;code&gt;dotnet&lt;/code&gt; tooling.&lt;/li&gt;
&lt;li&gt;You should be careful to document known compatibility issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in all, the process was less painful than I expected. Now that this work is done I can focus on more exciting things, such as potentially getting projects like SharpGL working on Linux or MacOS, which is much more exciting.&lt;/p&gt;
&lt;p&gt;As always, questions, comments, suggestions, rants, anything are welcome!&lt;/p&gt;
&lt;p&gt;The pull request which migrates the SharpGL project and SharpShell projects are below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/dwmkerr/sharpgl/pull/177/"&gt;github.com/dwmkerr/sharpgl/pull/177/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dwmkerr/sharpshell/pull/331"&gt;github.com/dwmkerr/sharpshell/pull/331&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Useful References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/"&gt;Microsoft Docs: .NET Core Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-gb/dotnet/core/porting/"&gt;Microsoft Docs: Overview of porting from .NET Framework to .NET Core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/dotnet/standard/frameworks#how-to-specify-target-frameworks"&gt;Microsoft Docs: Target frameworks in SDK-style projects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>CodeProject</category></item><item><title>Observations, tips and tricks for the CKA certification</title><link>https://dwmkerr.com/tips-for-cka/</link><pubDate>Thu, 04 Jun 2020 00:00:00 +0000</pubDate><guid>https://dwmkerr.com/tips-for-cka/</guid><description>&lt;p&gt;In this article I&amp;rsquo;ll share some observations, tips and tricks for the &lt;a href="https://www.linuxfoundation.org/"&gt;Linux Foundation&amp;rsquo;s&lt;/a&gt; &amp;ldquo;&lt;a href="https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/"&gt;Certified Kubernetes Administrator&lt;/a&gt; certification and exam.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been operating Kubernetes in multiple environments for a few years now. I thought this would be an easy certification to get, but I was surprised by how hard it was!&lt;/p&gt;
&lt;p&gt;I took this exam without doing any formal training, I mostly focused on the areas of the curriculum which I knew I was a little weak at. The task-based structure for the exam I thought was really excellent. It took me two attempts to pass, and I learnt a few things along the way.&lt;/p&gt;
&lt;p&gt;Here I&amp;rsquo;ll share some thoughts on the certification which hopefully will be useful if you are considering taking it!&lt;/p&gt;
&lt;!-- vim-markdown-toc GFM --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#tip-do-the-right-certification"&gt;Tip: Do the right Certification!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tip-understand-the-format"&gt;Tip: Understand the Format!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tip-know-your-vim"&gt;Tip: Know your Vim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#you-need-to-know-the-architecture-of-kubernetes"&gt;You need to know the architecture of Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tip-you-need-to-know-linux-sysadmin"&gt;Tip: You Need to know Linux Sysadmin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tip-dry-run-is-your-friend"&gt;Tip: &amp;ldquo;Dry Run&amp;rdquo; is your friend&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tip-know-how-to-troubleshoot-networking"&gt;Tip: Know how to troubleshoot networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tip-nail-the-easy-questions-quickly"&gt;Tip: Nail the easy questions quickly&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#thats-it"&gt;That&amp;rsquo;s it!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- vim-markdown-toc --&gt;
&lt;h2 id="tip-do-the-right-certification"&gt;Tip: Do the right Certification!&lt;/h2&gt;
&lt;p&gt;The CKA exam tests &lt;em&gt;administration&lt;/em&gt; and &lt;em&gt;operation&lt;/em&gt; skills and techniques for Kubernetes. If you have set up and administered clusters before, this will likely not be too challenging. But if you&amp;rsquo;ve never set up a cluster by hand, troubleshot weird issues, fixed clusters and so on, then this is likely going to be very hard.&lt;/p&gt;
&lt;p&gt;There is a certification which is much more geared towards developers who use Kubernetes, but don&amp;rsquo;t necessarily administer it - that&amp;rsquo;s the &lt;a href="https://www.cncf.io/certification/ckad/"&gt;CKAD&lt;/a&gt; exam and might be the one to take if you are not too familiar with system administration.&lt;/p&gt;
&lt;h2 id="tip-understand-the-format"&gt;Tip: Understand the Format!&lt;/h2&gt;
&lt;p&gt;This is not a multiple choice question exam. It&amp;rsquo;s a task based exam, meaning you have about 22 or so specific tasks to complete, in a web browser which has a terminal connected to a cluster.&lt;/p&gt;
&lt;p&gt;It is open-book - meaning that you can use the &lt;a href="https://kubernetes.io/docs/home/"&gt;Kubernetes Documentation&lt;/a&gt; during the exam. It&amp;rsquo;s not a memory test of specific flags for commands or whatever, it will really require you to work with a running cluster. This means you&amp;rsquo;ll have to be pretty familiar with &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;kubeadm&lt;/code&gt; and also Linux in general!&lt;/p&gt;
&lt;h2 id="tip-know-your-vim"&gt;Tip: Know your Vim&lt;/h2&gt;
&lt;p&gt;In the two exams I took, &lt;code&gt;nano&lt;/code&gt; was available. But if you are using &lt;code&gt;nano&lt;/code&gt; to work with files you may struggle for time.&lt;/p&gt;
&lt;p&gt;I spent a &lt;em&gt;lot&lt;/em&gt; of time in &lt;code&gt;vim&lt;/code&gt; in the exam. &lt;code&gt;vim&lt;/code&gt; is my main text editor for day to day work, so I&amp;rsquo;m fairly familiar with it. Knowing how to quickly copy a file (lets say for example a file which represents a deployment) and quickly manipulate the text in it will be crucial. Make sure you are going to be using a text editor which you can be efficient in!&lt;/p&gt;
&lt;p&gt;You won&amp;rsquo;t be using a graphical text editor to work with files, so being competent in a terminal editor like &lt;code&gt;vim&lt;/code&gt; or &lt;code&gt;emacs&lt;/code&gt; could make a big difference. Of course you could install your favourite text editor, but you won&amp;rsquo;t be able to use a graphical editor like VS Code.&lt;/p&gt;
&lt;p&gt;Also, as in most Linux distributions, &lt;code&gt;screen&lt;/code&gt; is available out of the box, and &lt;code&gt;tmux&lt;/code&gt; can also be installed. If you are familiar with either of these terminal mutliplexers it could save you a tonne of time, for example being able to run &lt;code&gt;watch -n 5 -d kubectl get pods&lt;/code&gt; in one pane while applying resources in another.&lt;/p&gt;
&lt;h2 id="you-need-to-know-the-architecture-of-kubernetes"&gt;You need to know the architecture of Kubernetes&lt;/h2&gt;
&lt;p&gt;This exam will require you to deal with trivial tasks such as running a deployment or creating a volume. But the questions which focus on that tend to only count for one or two percent of the overall grade each. Questions which deal with troubleshooting actual Kubernetes issues could count for six or seven percent each.&lt;/p&gt;
&lt;p&gt;This means you &lt;em&gt;need&lt;/em&gt; to know how Kubernetes is architecture. The &lt;code&gt;kubelet&lt;/code&gt; which runs on nodes, the API server, the &lt;code&gt;etcd&lt;/code&gt; store, all of these things you &lt;em&gt;have&lt;/em&gt; to understand how they work and how they fit together.&lt;/p&gt;
&lt;p&gt;The online documentation covers the architecture in detail, here&amp;rsquo;s the best place to start:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/overview/components/"&gt;https://kubernetes.io/docs/concepts/overview/components/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/overview/components/"&gt;&lt;img src="./images/k8s-architecture.png" alt="Kubernetes Architecture"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You will need to know how the control plane works, how nodes communicate, how transport of messages works and is secured if you are going to have a chance at dealing with the harder questions.&lt;/p&gt;
&lt;h2 id="tip-you-need-to-know-linux-sysadmin"&gt;Tip: You Need to know Linux Sysadmin&lt;/h2&gt;
&lt;p&gt;If you are not familiar with &lt;code&gt;systemctl&lt;/code&gt;, &lt;code&gt;journalctl&lt;/code&gt;, &lt;code&gt;apt&lt;/code&gt;, &lt;code&gt;systemd&lt;/code&gt; units and how the core Kubernetes components are configured, you&amp;rsquo;ll really struggle.&lt;/p&gt;
&lt;p&gt;Look over the &lt;a href="https://github.com/cncf/curriculum"&gt;CNCF curriculum&lt;/a&gt; - expect to not just have to know how to deal with &amp;lsquo;happy path&amp;rsquo; situations, but also broken clusters, incorrect configuration and so on.&lt;/p&gt;
&lt;h2 id="tip-dry-run-is-your-friend"&gt;Tip: &amp;ldquo;Dry Run&amp;rdquo; is your friend&lt;/h2&gt;
&lt;p&gt;One thing which helped me a lot in my second attempt at the exam was the &lt;code&gt;--dry-run&lt;/code&gt; flag. Before you create resources or change anything, run the operation with the &lt;code&gt;--dry-run&lt;/code&gt; flag and see whether the output is what you would expect.&lt;/p&gt;
&lt;p&gt;This is a quick and easy way to see the changes to the cluster which you are going to apply - and troubleshoot them - before making any actual changes.&lt;/p&gt;
&lt;h2 id="tip-know-how-to-troubleshoot-networking"&gt;Tip: Know how to troubleshoot networking&lt;/h2&gt;
&lt;p&gt;Networking in Kubernetes is complex. You must be able to troubleshoot networking issues in the cluster to be able to deal with the more complex tasks.&lt;/p&gt;
&lt;p&gt;This means that you should know how to be able to run typical networking tools like &lt;code&gt;dig&lt;/code&gt;, &lt;code&gt;nslookup&lt;/code&gt;, &lt;code&gt;telnet&lt;/code&gt; etc, in the cluster itself.&lt;/p&gt;
&lt;p&gt;If you are not familiar with these tools you might need to take an online course in Kubernetes or Linux Networking Administration before considering this certification. The &lt;a href="https://training.linuxfoundation.org/certification/linux-foundation-certified-sysadmin-lfcs/"&gt;Linux Certified Systems Administrator&lt;/a&gt; training would be a good place to start.&lt;/p&gt;
&lt;p&gt;If you have taken the &lt;a href="https://success.docker.com/certification"&gt;Docker Certified Associate&lt;/a&gt; exam then some of this should be familiar. If you are not very familiar with how Docker itself works, you&amp;rsquo;ll likely struggle with Kubernetes.&lt;/p&gt;
&lt;h2 id="tip-nail-the-easy-questions-quickly"&gt;Tip: Nail the easy questions quickly&lt;/h2&gt;
&lt;p&gt;There are a lot of tasks which only count for one or two percent each; these ones you should be able to complete in a few minutes. You&amp;rsquo;ll need all the time in the exam to work on the really hard questions which deal with diagnosing and fixing cluster issues.&lt;/p&gt;
&lt;p&gt;Know your core Kubernetes concepts; if you have done the CKAD exam you should be good, if not, check the curriculum and make sure you can quickly complete all of the trivial tasks without wasting too much time.&lt;/p&gt;
&lt;h2 id="thats-it"&gt;That&amp;rsquo;s it!&lt;/h2&gt;
&lt;p&gt;Hopefully this was helpful! Good luck if you are taking the exam and hopefully you&amp;rsquo;ll find it a challenging but rewarding experience. I&amp;rsquo;ve taken many exams over the years but this was one of the most challenging, but also one of the most enjoyable, I really felt like it was testing practical techniques rather than your ability to just remember random commands and flags.&lt;/p&gt;
&lt;p&gt;As always, if you have any comments or questions, please just add them in the section below!&lt;/p&gt;
&lt;p&gt;&lt;img src="./images/cka-cert.png" alt="CKA Certification"&gt;&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Supercharge your Java Projects with Conventional Commits, Semantic Versioning and Semantic Releases</title><link>https://dwmkerr.com/conventional-commits-and-semantic-versioning-for-java/</link><pubDate>Sun, 17 May 2020 00:00:00 +0000</pubDate><guid>https://dwmkerr.com/conventional-commits-and-semantic-versioning-for-java/</guid><description>&lt;p&gt;In this article we&amp;rsquo;ll look at a few simple techniques which can really supercharge your Java project and make them much easier to work with!&lt;/p&gt;
&lt;!-- vim-markdown-toc GFM --&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#semantic-versioning"&gt;Semantic Versioning&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#why-does-this-matter"&gt;Why Does This Matter?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-semantic-versioning-specification"&gt;The Semantic Versioning Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-semantic-versions"&gt;Using Semantic Versions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-challenge-of-semantic-versions"&gt;The Challenge of Semantic Versions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conventional-commits"&gt;Conventional Commits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#time-for-magic"&gt;Time for Magic&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#enforcing-conventional-commits-with-git-hooks"&gt;Enforcing Conventional Commits with Git Hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-the-hook-works"&gt;How the Hook Works&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#creating-the-initial-release"&gt;Creating the Initial Release&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#go-forth-and-devops"&gt;Go Forth And DevOps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-gradle-version"&gt;The Gradle Version&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#thats-it"&gt;That&amp;rsquo;s It&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- vim-markdown-toc --&gt;
&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; If you know the concepts, then just jump straight to my fork of &lt;code&gt;standard-version&lt;/code&gt; at &lt;a href="https://github.com/dwmkerr/standard-version"&gt;&lt;code&gt;github.com/dwmkerr/standard-version&lt;/code&gt;&lt;/a&gt;. It adds support for Java projects. I am currently trying to get it into the mainline, so if you like this, please comment on the &lt;a href="https://github.com/conventional-changelog/standard-version/pull/591"&gt;Pull Request&lt;/a&gt; here. &lt;strong&gt;tl;dr end!&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="semantic-versioning"&gt;Semantic Versioning&lt;/h2&gt;
&lt;p&gt;First, let&amp;rsquo;s talk about the idea of a &lt;em&gt;Semantic Version&lt;/em&gt;. A semantic version is nothing more than a versioning scheme you will probably be familiar with, where versions look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The only thing special about a &lt;em&gt;Semantic Version&lt;/em&gt; is that we give a very specific meaning to each part of the version. In short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1&lt;/code&gt; is the &lt;em&gt;major&lt;/em&gt; part of the version&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2&lt;/code&gt; is the &lt;em&gt;minor&lt;/em&gt; part of the version&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3&lt;/code&gt; is the &lt;em&gt;patch&lt;/em&gt; part of the version&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we give &lt;em&gt;semantics&lt;/em&gt; (meaning and context) to these parts:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Major&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;major&lt;/em&gt; version number change means something big has changed, and the API of the software is different to the earlier version. Essentially, this is a potentially &lt;em&gt;breaking&lt;/em&gt; change, so you should only use this new version after carefully reading about the changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Minor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;minor&lt;/em&gt; version number change means that something has been added or changed, which affects the functionality of the code, but in a &lt;em&gt;non breaking&lt;/em&gt; way. An example would be the addition of a new API. That won&amp;rsquo;t affect existing users, so they can generally safely upgrade minor versions without too much risk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Patch&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;em&gt;patch&lt;/em&gt; version number change means something really inconsequential to the user of the code has changed. It might be new documentation, better logging, but it is generally not a &lt;em&gt;functional&lt;/em&gt; change.&lt;/p&gt;
&lt;h3 id="why-does-this-matter"&gt;Why Does This Matter?&lt;/h3&gt;
&lt;p&gt;If we have Semantic Versions, we can be a lot more sure about &lt;em&gt;when it is safe to upgrade&lt;/em&gt;. If we see a &lt;em&gt;major&lt;/em&gt; version change, we know we need to be careful. &lt;em&gt;Minor&lt;/em&gt; changes might need attention, and &lt;em&gt;patches&lt;/em&gt; are almost always going to be safe.&lt;/p&gt;
&lt;p&gt;Managing dependencies and keeping them up to date is hard in software development, and one of the reasons people are wary of updating dependencies is that &lt;em&gt;they don&amp;rsquo;t know if they upgrade will break their code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Semantic Versioning tries to bring a little order to this chaotic world.&lt;/p&gt;
&lt;h3 id="the-semantic-versioning-specification"&gt;The Semantic Versioning Specification&lt;/h3&gt;
&lt;p&gt;There is a detailed specification for semantic versioning, which also covers more sophisticated cases, you can find it here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://semver.org/"&gt;https://semver.org/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d suggest this is recommend reading for &lt;em&gt;any&lt;/em&gt; software engineer!&lt;/p&gt;
&lt;h3 id="using-semantic-versions"&gt;Using Semantic Versions&lt;/h3&gt;
&lt;p&gt;Now the easiest way to start with semantic versioning is to simply adhere to the spec! For example, if you make a change which could break something for users, bump the &lt;em&gt;major&lt;/em&gt; part of the version.&lt;/p&gt;
&lt;p&gt;But, things aren&amp;rsquo;t all that easy&amp;hellip;&lt;/p&gt;
&lt;h3 id="the-challenge-of-semantic-versions"&gt;The Challenge of Semantic Versions&lt;/h3&gt;
&lt;p&gt;The challenge is this. Imagine you are cutting a new release of your code and many people have contributed. Some bug fixes, some patches, some documentation. How do you look through all of those changes and decide how to appropriately change the version number?&lt;/p&gt;
&lt;p&gt;To solve this problem, say hello to &lt;em&gt;Conventional Commits&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="conventional-commits"&gt;Conventional Commits&lt;/h2&gt;
&lt;p&gt;If you have a commit history like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Updated the users API
Bugfix
trying the build again, got it working
Bugfix: [JIRA-21] fixed that issue
you can now get user&amp;#39;s friends with this change
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then it is &lt;em&gt;very&lt;/em&gt; hard to reason about what is going on. What about if the commit history looked like this?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;feat(users): [#12] fetching users returns their avatar url
fix(users): [#45] display names with emojis return correctly
build(cicd): update the expired deploy key
fix(docs): [#22] fix broken links to the javadocs
feat(users): [#49] users api optionally returns friends, non-breaking
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It&amp;rsquo;s much easier to see what each change means, at least at a high level.&lt;/p&gt;
&lt;p&gt;By having some kind of standard for commit messages, we can do a lot. We can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Classify changes by type (such as a feature or fix)&lt;/li&gt;
&lt;li&gt;Include a clear description of the change&lt;/li&gt;
&lt;li&gt;Use a convention to indicate a breaking change&lt;/li&gt;
&lt;li&gt;Link to a ticketing system&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just like semantic versioning, conventional commits have a specification too:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.conventionalcommits.org/en/v1.0.0/"&gt;https://www.conventionalcommits.org/en/v1.0.0/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="time-for-magic"&gt;Time for Magic&lt;/h2&gt;
&lt;p&gt;Now if we have conventional commits, and want to use semantic versions, we can actually skip the whole process of looking over a commit history to create a new semantic version - we can automate it.&lt;/p&gt;
&lt;p&gt;We can even automate the process of creating a &amp;lsquo;changelog&amp;rsquo;, a list of each change which comes in each version. There&amp;rsquo;s an &lt;em&gt;excellent&lt;/em&gt; library which does this, called &lt;code&gt;standard-version&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/conventional-changelog/standard-version"&gt;https://github.com/conventional-changelog/standard-version&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s maintained by the same group behind conventional commits. The only problem? It only works for JavaScript projects (unless you are willing to write custom code which can be complex).&lt;/p&gt;
&lt;p&gt;But I&amp;rsquo;ve updated the library to support Maven projects and Gradle projects, so you can use it for Java now as well!&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see it in action. Here&amp;rsquo;s a very simple Java library built with Maven:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/java-maven-standard-version-sample"&gt;https://github.com/dwmkerr/java-maven-standard-version-sample&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This library has no changelog, no tags, no version data at all except for &lt;code&gt;0.1.0&lt;/code&gt; in the &lt;code&gt;pom.xml&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Now if I was to clone the library, make a change and make a commit, which &lt;em&gt;didn&amp;rsquo;t&lt;/em&gt; follow the conventional commit spec, we&amp;rsquo;ll just see the usual success message:&lt;/p&gt;
&lt;img src="./images/bad-commit-message.png" alt="Bad commit message" width="800px" /&gt;
&lt;p&gt;This is a problem; we want to &lt;em&gt;enforce&lt;/em&gt; conventional commits.&lt;/p&gt;
&lt;h3 id="enforcing-conventional-commits-with-git-hooks"&gt;Enforcing Conventional Commits with Git Hooks&lt;/h3&gt;
&lt;p&gt;Git has a powerful &amp;lsquo;hooks&amp;rsquo; facility, which let you run logic at key points in operations. This is a &lt;em&gt;massive&lt;/em&gt; topic on its own, so we&amp;rsquo;re not going to go into lots of details, but if you are interested you can read about them here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now the issue with Git Hooks is that they are &lt;em&gt;per user&lt;/em&gt; - if I add a hook to my &lt;code&gt;.git&lt;/code&gt; folder, no one else will get it. We want the same hooks for &lt;em&gt;all&lt;/em&gt; users.&lt;/p&gt;
&lt;p&gt;There are a few ways to get around this. You can set up server side hooks (which could reject a push if it has an invalid commit message), but this isn&amp;rsquo;t easy to do (and with some providers, like GitHub for public projects, not even available as an option). Also, we want fast feedback, so if I make a bad commit message, it fails straight away and I can fix it.&lt;/p&gt;
&lt;p&gt;The way I suggest getting around this is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;code&gt;.githooks&lt;/code&gt; folder in your repo&lt;/li&gt;
&lt;li&gt;Instruct people to configure the git repo to look for hooks there&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That way there are no global changes, only project specific ones. We still need to make sure the developer sets up the hooks though! You&amp;rsquo;ll notice in my sample project&amp;rsquo;s &lt;a href="https://github.com/dwmkerr/java-maven-standard-version-sample#developer-guide"&gt;&lt;code&gt;README.md&lt;/code&gt;&lt;/a&gt; file the first thing I do is instruct people to setup the hooks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git config core.hooksPath .githooks
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s see how the same operation would look if we&amp;rsquo;d setup the hook first:&lt;/p&gt;
&lt;img src="./images/git-hook-bad-message.png" alt="Bad commit message with hook" width="800px" /&gt;
&lt;p&gt;Our hook has fired off and told us we&amp;rsquo;ve not used a conventional commit message - it even let&amp;rsquo;s us know where to go to find out more.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s try a message which should work, as it meets the standard:&lt;/p&gt;
&lt;img src="./images/git-hook-good-message.png" alt="Good commit message with hook" width="800px" /&gt;
&lt;p&gt;Awesome! We&amp;rsquo;ve been informed that our message meets the standard (useful to actually remind us that this is being checked!) and the commit has succeeded!&lt;/p&gt;
&lt;p&gt;Remember; we only need to setup the hooks once - it&amp;rsquo;s a one time activity.&lt;/p&gt;
&lt;h3 id="how-the-hook-works"&gt;How the Hook Works&lt;/h3&gt;
&lt;p&gt;Hooks are just shell scripts. You can write them in Ruby, Python, whatever. I have written this one in pure Bash because it&amp;rsquo;s really just checking a regex, which Bash is more than capable of. Also, I can&amp;rsquo;t be sure the developer will have Ruby or another tool on their machine.&lt;/p&gt;
&lt;p&gt;The hook is as simple as this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Create a regex for a conventional commit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;convetional_commit_regex&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z \-]+\))?!?: .+&lt;/span&gt;$&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Get the commit message (the parameter we&amp;#39;re given is just the path to the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# temporary file which holds the message).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;commit_message&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;cat &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$1&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Check the message, if we match, all good baby.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[[&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$commit_message&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt;~ $convetional_commit_regex &lt;span style="color:#f92672"&gt;]]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo -e &lt;span style="color:#e6db74"&gt;&amp;#34;\e[32mCommit message meets Conventional Commit standards...\e[0m&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Uh-oh, this is not a conventional commit, show an example and link to the spec.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo -e &lt;span style="color:#e6db74"&gt;&amp;#34;\e[31mThe commit message does not meet the Conventional Commit standard\e[0m&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;An example of a valid message is: &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34; feat(login): add the &amp;#39;remember me&amp;#39; button&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The only really tricky bit is the regex, and the weird &lt;code&gt;\e[32&lt;/code&gt; type characters which are used to set the colours. You might find it easier to write your hooks in a proper programming language - and for anything more complex I&amp;rsquo;d suggest that makes far more sense! But if a bit of Bash will do the trick, there&amp;rsquo;s nothing wrong with that.&lt;/p&gt;
&lt;p&gt;As a side-note, if you are into Bash and the shell, check out my online &lt;a href="https://effective-shell.com"&gt;Effective Shell&lt;/a&gt; book.
git config core.hooksPath .githooks&lt;/p&gt;
&lt;h2 id="creating-the-initial-release"&gt;Creating the Initial Release&lt;/h2&gt;
&lt;p&gt;Now the chances are, if you are interested in this technique, you&amp;rsquo;ve probably got an existing project you want to use it on. It probably doesn&amp;rsquo;t have a changelog or conventional commits. That&amp;rsquo;s OK, just start from now.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how we&amp;rsquo;d start using the &lt;code&gt;standard-version&lt;/code&gt; library to manage our versions. I&amp;rsquo;ve added a new API to the &lt;a href="https://github.com/dwmkerr/java-maven-standard-version-sample/tree/release"&gt;&lt;code&gt;release&lt;/code&gt;&lt;/a&gt; branch (to keep &lt;code&gt;master&lt;/code&gt; clean for people reading the sample) and committed it.&lt;/p&gt;
&lt;p&gt;Now lets actually create our changelog:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx @dwmkerr/standard-version --first-release --packageFiles pom.xml --bumpFiles pom.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src="./images/first-release.png" alt="First release" width="800px" /&gt;
&lt;p&gt;Now it&amp;rsquo;s a pain I know, but you need &lt;a href="https://nodejs.org/en/download/"&gt;Node.js&lt;/a&gt; installed for this to work. The &lt;code&gt;standard-version&lt;/code&gt; library is built on node, that&amp;rsquo;s what is used to do all of the logic around writing a changelog and working out what the version bump should be. You also have to use my fork &lt;code&gt;@dwmkerr/standard-version&lt;/code&gt; rather than the main version, because at the time of writing my pull request which adds support for &lt;code&gt;pom.xml&lt;/code&gt; files is not yet merged.&lt;/p&gt;
&lt;p&gt;What has happened here is that the &lt;code&gt;standard-version&lt;/code&gt; tool has &lt;em&gt;not&lt;/em&gt; changed the version number. We told it this is the &lt;code&gt;first-release&lt;/code&gt;, meaning we haven&amp;rsquo;t published yet, so there&amp;rsquo;s no need to create a new number. What is &lt;em&gt;has&lt;/em&gt; done is given us a changelog and told use how to push the tags and code. If we push, we can now see the changelog:&lt;/p&gt;
&lt;img src="./images/changelog-v1.png" alt="Changelog v1" width="800px" /&gt;
&lt;p&gt;See how we get a changelog showing the changes, the version and the date? We even have links to the commits for each key change!&lt;/p&gt;
&lt;p&gt;If we&amp;rsquo;d linked the message to GitHub Issue numbers it&amp;rsquo;d automatically have links to the issues too!&lt;/p&gt;
&lt;p&gt;Now in this code I deliberately made a mistake - the test for the &lt;code&gt;Goodbye&lt;/code&gt; api is a copy and paste of the &lt;code&gt;Hello&lt;/code&gt; test! And the &lt;code&gt;Goodbye&lt;/code&gt; api has a spelling mistake. Let&amp;rsquo;s fix this and cut a new release.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve made the change on the &lt;code&gt;release&lt;/code&gt; branch, now I&amp;rsquo;ll run &lt;code&gt;standard-version&lt;/code&gt; again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx @dwmkerr/standard-version --packageFiles pom.xml --bumpFiles pom.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src="./images/second-release.png" alt="Second release" width="800px" /&gt;
&lt;p&gt;Note that there was no need for the &lt;code&gt;--first-release&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;Now this time, a new version has been generated. This was a &lt;code&gt;fix&lt;/code&gt; commit, so it has made it a &lt;em&gt;minor&lt;/em&gt; version bump. If we needed to make it a breaking change, we can use a message with an exclamation after the type, such as &lt;code&gt;fix(goodbye)!: fix the typo&lt;/code&gt;. Check the &lt;code&gt;standard-version&lt;/code&gt; docs for more about this.&lt;/p&gt;
&lt;p&gt;Finally, let&amp;rsquo;s look at our new changelog:&lt;/p&gt;
&lt;img src="./images/second-changelog.png" alt="Second changelog" width="800px" /&gt;
&lt;p&gt;We have even more info now - we have a link to the tag. This is &lt;em&gt;incredibly&lt;/em&gt; useful for managing releases.&lt;/p&gt;
&lt;p&gt;The icing on the cake? Let&amp;rsquo;s look at the &lt;code&gt;pom.xml&lt;/code&gt;:&lt;/p&gt;
&lt;img src="./images/updated-pom.png" alt="Updated pom.xml" width="800px" /&gt;
&lt;p&gt;Note that &lt;em&gt;the version has been updated&lt;/em&gt;. &lt;code&gt;standard-release&lt;/code&gt; is keeping our Git Tags and our Java Library Version numbers &lt;em&gt;automatically in sync&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve started doing this and seen it in action for a while, you&amp;rsquo;ll wonder how you lived without it!&lt;/p&gt;
&lt;h2 id="go-forth-and-devops"&gt;Go Forth And DevOps&lt;/h2&gt;
&lt;p&gt;This is just the beginning! Think of all the cool things we can do with this in place, here&amp;rsquo;s just a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update our build pipeline so that when we merge into &lt;code&gt;master&lt;/code&gt; we automatically run &lt;code&gt;standard-version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Update our build pipeline so that when a new version tag is added, we automatically publish the library&lt;/li&gt;
&lt;li&gt;Send out a slack notification with the changelog when a new version is committed&lt;/li&gt;
&lt;li&gt;Share the changelog with our consumers as our libraries are updated&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With these basic building blocks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Conventional Commits&lt;/li&gt;
&lt;li&gt;Semantic Versioning&lt;/li&gt;
&lt;li&gt;Enforcing of Commit Standards&lt;/li&gt;
&lt;li&gt;Usage of the &lt;code&gt;standard-release&lt;/code&gt; tool&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We have created a very powerful way to manage what is actually a highly complex process. We&amp;rsquo;ve introduced almost no additional complexity, just a few guidelines for developers.&lt;/p&gt;
&lt;h2 id="the-gradle-version"&gt;The Gradle Version&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s basically the same technique for Gradle, you just tell &lt;code&gt;standard-version&lt;/code&gt; to hit your &lt;code&gt;build.gradle&lt;/code&gt; file;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npx @dwmkerr/standard-version --packageFiles build.gradle --bumpFiles build.gradle
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s an accompanying sample project at:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/java-gradle-standard-version-sample"&gt;github.com/dwmkerr/java-gradle-standard-version-sample&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is the same as the Maven version in that the &lt;code&gt;master&lt;/code&gt; branch has no &lt;code&gt;standard-version&lt;/code&gt; code or changelogs, just open the &lt;a href="https://github.com/dwmkerr/java-gradle-standard-version-sample/tree/release"&gt;&lt;code&gt;release&lt;/code&gt;&lt;/a&gt; branch to see what it looks like after we&amp;rsquo;ve applied the same techniques as we did to the Maven version.&lt;/p&gt;
&lt;h2 id="thats-it"&gt;That&amp;rsquo;s It&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a whole world of libraries for this. &lt;a href="https://github.com/commitizen/cz-cli"&gt;&lt;code&gt;commitizen&lt;/code&gt;&lt;/a&gt; which helps you write conventional commit messages for example. But I found very little for Java. If you find this useful, please do chip in on the pull request here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/conventional-changelog/standard-version/pull/591"&gt;https://github.com/conventional-changelog/standard-version/pull/591&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As it would be great to add it to the mainline. I&amp;rsquo;m also just finishing off the update which adds support for Gradle.&lt;/p&gt;
&lt;p&gt;As always, questions, comments, suggestions, rants, anything are welcome!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Effective Shell for Beginners</title><link>https://dwmkerr.com/effective-shell-for-beginners/</link><pubDate>Tue, 21 Jan 2020 00:00:00 +0000</pubDate><guid>https://dwmkerr.com/effective-shell-for-beginners/</guid><description>&lt;p&gt;I have rebuilt my &amp;ldquo;Effective Shell&amp;rdquo; series as an online book - it&amp;rsquo;s available now on:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://effective-shell.com"&gt;https://effective-shell.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The whole site is built from a GitHub repo at &lt;a href="https://github.com/dwmkerr/effective-shell"&gt;github.com/dwmkerr/effective-shell&lt;/a&gt;. It is open for contributions, changes, issues and suggestions. I&amp;rsquo;ve also added a comment section to each page to get input.&lt;/p&gt;
&lt;p&gt;To keep the material as accessible as possible, I have added a new section for beginners, to help anyone who has not used a shell before. It goes over who the book is useful for, what the shell is, and how to set up your computer to work through the material:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://effective-shell.com"&gt;&lt;img src="images/effective-shell-screenshot.png" alt="Effective Shell: Screenshot" width="1024px" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All comments and suggestions are welcome!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Effective Shell Part 7: The Subtleties of Shell Commands</title><link>https://dwmkerr.com/effective-shell-7-shell-commands/</link><pubDate>Tue, 25 Jun 2019 07:25:23 +0000</pubDate><guid>https://dwmkerr.com/effective-shell-7-shell-commands/</guid><description>&lt;p&gt;In this chapter, we&amp;rsquo;ll take a look at the various different types of shell commands that exist and how this can affect your work.&lt;/p&gt;
&lt;p&gt;By the end of this chapter, you might even be able to make sense of the horrifying and perfectly syntactically valid code below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;which &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;where &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;what &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;whence &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;whereis who&lt;span style="color:#66d9ef"&gt;))))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-1-navigating-the-command-line/"&gt;Part 1: Navigating the Command Line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-2-become-a-clipboard-gymnast/"&gt;Part 2: Become a Clipboard Gymnast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-3-getting-hepl/"&gt;Part 3: Getting Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-4-moving-around/"&gt;Part 4: Moving Around&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-part-5-understanding-the-shell/"&gt;Part 5: Interlude - Understanding the Shell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-6-job-control/"&gt;Part 6: Everything You Don&amp;rsquo;t Need to Know About Job Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dwmkerr.com/effective-shell-7-shell-commands/"&gt;Part 7: The Subtleties of Shell Commands&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-are-commands"&gt;What Are Commands?&lt;/h2&gt;
&lt;p&gt;This is &lt;em&gt;really&lt;/em&gt; important to understand! A &lt;em&gt;command&lt;/em&gt; in a shell is something you execute. It might take parameters. Generally it&amp;rsquo;ll have a form like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;command param1 param2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;ve already seen many commands during this series:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls &lt;span style="color:#75715e"&gt;# Show the contents of the current directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ~ &lt;span style="color:#75715e"&gt;# Move to the user&amp;#39;s home&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat file.txt &lt;span style="color:#75715e"&gt;# Output the contents of &amp;#39;file.txt&amp;#39; to stdout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But to be an effective shell user, you must understand that not all commands are created equal. The differences between the types of commands will affect how you use them.&lt;/p&gt;
&lt;p&gt;There are four types of commands in most shells:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Executables&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Built-Ins&amp;rdquo; (which we&amp;rsquo;ll just call &lt;em&gt;builtins&lt;/em&gt; from now on)&lt;/li&gt;
&lt;li&gt;Functions&lt;/li&gt;
&lt;li&gt;Aliases&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s quickly dig in and see a bit more.&lt;/p&gt;
&lt;h2 id="executables---programs"&gt;Executables - Programs&lt;/h2&gt;
&lt;p&gt;Executables are just files with the &amp;rsquo;executable&amp;rsquo; bit set&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. If I execute the &lt;code&gt;cat&lt;/code&gt; command, the shell will search for an executable named &lt;code&gt;cat&lt;/code&gt; in my &lt;code&gt;$PATH&lt;/code&gt;. If it finds it, it will run the program.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ cat file.txt
This is a simple text file
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What is &lt;code&gt;$PATH&lt;/code&gt;? &lt;code&gt;$PATH&lt;/code&gt; is the standard environment variable used to define &lt;em&gt;where&lt;/em&gt; the shell should search for programs. If we temporarily &lt;em&gt;empty&lt;/em&gt; this variable, the shell won&amp;rsquo;t find the command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ PATH&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt; cat file.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bash: cat: No such file or directory
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Normally your &lt;code&gt;$PATH&lt;/code&gt; variable will include the standard locations for Linux programs - folders such as &lt;code&gt;/bin&lt;/code&gt;, &lt;code&gt;/sbin&lt;/code&gt;, &lt;code&gt;/usr/bin&lt;/code&gt; and so on&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;If you were to print the variable, you&amp;rsquo;d see a bunch of paths (they are separated by colons; I&amp;rsquo;ve put them on separate lines for readability):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The shell will start with the &lt;em&gt;earlier&lt;/em&gt; locations and move to the later ones. This allows &lt;em&gt;local&lt;/em&gt; flavours of tools to be installed for users, which will take precedence over &lt;em&gt;general&lt;/em&gt; versions of tools.&lt;/p&gt;
&lt;p&gt;There will likely be other locations too - you might see Java folders, package manager folders and so on.&lt;/p&gt;
&lt;h2 id="executables---scripts"&gt;Executables - Scripts&lt;/h2&gt;
&lt;p&gt;Imagine we create a text file called &lt;code&gt;dog&lt;/code&gt; in the local folder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;🐶 woof 🐶&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we make the file &lt;em&gt;executable&lt;/em&gt;, by running &lt;code&gt;chmod +x dog&lt;/code&gt;&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, then we can run this just like any other program - as long as we tell the shell to look for programs in the current directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ PATH&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;.&amp;#34;&lt;/span&gt; dog
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;🐶 woof 🐶
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;More common would be to run the program by giving a path:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ./dog
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;🐶 woof 🐶
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or just move it to a standard location that the shell already checks for programs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ mv dog /usr/local/bin
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ dog
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;🐶 woof 🐶
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The point is that executables don&amp;rsquo;t &lt;em&gt;have&lt;/em&gt; to be compiled program code. If a file starts with &lt;code&gt;#!&lt;/code&gt; (the &amp;lsquo;shebang&amp;rsquo;), then the system will try to run the contents of the file with the program specified in the shebang.&lt;/p&gt;
&lt;p&gt;We will look at shebangs in greater detail in a later chapter.&lt;/p&gt;
&lt;h2 id="builtins"&gt;Builtins&lt;/h2&gt;
&lt;p&gt;OK, so we&amp;rsquo;ve seen executables. What about a command like this?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;local V&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt; echo $V
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You will not find the &lt;code&gt;local&lt;/code&gt; executable anywhere on your system. It is a &lt;em&gt;builtin&lt;/em&gt; - a special command built directly into the shell program.&lt;/p&gt;
&lt;p&gt;Builtins are often highly specific to your shell. They might be used for programming (&lt;code&gt;local&lt;/code&gt; for example is used to declare a locally scoped variable), or they might be for very shell-specific features.&lt;/p&gt;
&lt;p&gt;This is where we need to take note. As soon as you are running a builtin, you are potentially using a feature that is specific to &lt;em&gt;your&lt;/em&gt; shell, rather than a program that is shared across the system and can be run by &lt;em&gt;any&lt;/em&gt; shell.&lt;/p&gt;
&lt;p&gt;Trying to programmatically execute &lt;code&gt;local&lt;/code&gt; as a process will fail - there is no executable with that name; it is purely a shell construct.&lt;/p&gt;
&lt;p&gt;So how do we know if a command is a builtin? The preferred method is to use the &lt;code&gt;type&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ type local
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;local is a shell builtin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;type&lt;/code&gt; command (which is &lt;em&gt;itself&lt;/em&gt; a builtin!) can tell you the exact type of shell command.&lt;/p&gt;
&lt;p&gt;Interestingly, you might be using more builtins than you think. &lt;code&gt;echo&lt;/code&gt; is a program, but most of the time you are not executing it when you are in a shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ type -a echo
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo is a shell builtin
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo is /bin/echo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By using the &lt;code&gt;-a&lt;/code&gt; flag on &lt;code&gt;type&lt;/code&gt; to show &lt;em&gt;all&lt;/em&gt; commands that match the name, we see that &lt;code&gt;echo&lt;/code&gt; is actually both a builtin &lt;em&gt;and&lt;/em&gt; a program.&lt;/p&gt;
&lt;p&gt;Many simple programs have builtin versions. The shell can execute them much faster.&lt;/p&gt;
&lt;p&gt;Some commands are a builtin so that they can function in a sensible manner. The &lt;code&gt;cd&lt;/code&gt; command changes the current directory - if we executed it as a process, it would change only the directory for the &lt;code&gt;cd&lt;/code&gt; process itself, not the shell, making it much less useful.&lt;/p&gt;
&lt;p&gt;Builtins will vary from shell to shell, but many shells are &amp;lsquo;Bash-like&amp;rsquo; - meaning they will have a set very similar to the Bash builtins, which you can see here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html"&gt;https://www.gnu.org/software/bash/manual/html_node/Bash-Builtins.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As should be familiar from &lt;a href="https://www.dwmkerr.com/effective-shell-part-3-getting-hepl/"&gt;Part 3: Getting Help&lt;/a&gt;, you can get help for builtins:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ man source &lt;span style="color:#75715e"&gt;# source is a builtin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;BUILTIN&lt;span style="color:#f92672"&gt;(&lt;/span&gt;1&lt;span style="color:#f92672"&gt;)&lt;/span&gt; BSD General Commands Manual BUILTIN&lt;span style="color:#f92672"&gt;(&lt;/span&gt;1&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builtin, !, %, &lt;span style="color:#75715e"&gt;# ...snip...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SYNOPSIS
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builtin &lt;span style="color:#f92672"&gt;[&lt;/span&gt;-options&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;args ...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, the manual will &lt;em&gt;not&lt;/em&gt; show information on specific builtins, which is a pain. Your shell &lt;em&gt;might&lt;/em&gt; have an option to show more details - for example, in Bash you can use &lt;code&gt;help&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ help source
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source: source filename &lt;span style="color:#f92672"&gt;[&lt;/span&gt;arguments&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Read and execute commands from FILENAME and &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;. The pathnames
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; in $PATH are used to find the directory containing FILENAME. If any
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ARGUMENTS are supplied, they become the positional parameters when
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; FILENAME is executed.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But remember: &lt;code&gt;help&lt;/code&gt; is a builtin; you might not find it in all shells (you won&amp;rsquo;t find it in &lt;code&gt;zsh&lt;/code&gt;, for example). This highlights again the challenges of builtins.&lt;/p&gt;
&lt;h2 id="functions"&gt;Functions&lt;/h2&gt;
&lt;p&gt;You can define your own shell functions. We will see a lot more of this later, but let&amp;rsquo;s show a quick example for now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ restart-shell &lt;span style="color:#f92672"&gt;()&lt;/span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt; exec -l &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;$SHELL&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This snippet creates a function that restarts the shell (quite useful if you are messing with shell configuration files or think you might have irreversibly goofed up your current session).&lt;/p&gt;
&lt;p&gt;We can execute this function just like any command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ restart-shell
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And running &lt;code&gt;type&lt;/code&gt; will show us that this is a function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ type restart-shell
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;restart-shell is a &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;restart-shell &lt;span style="color:#f92672"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exec -l $SHELL
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Functions are one of the most powerful shell constructs we will see; they are extremely useful for building sophisticated logic. We&amp;rsquo;re going to see them in a lot more detail later, but for now it is enough to know that they exist, and can run logic, and are run as commands.&lt;/p&gt;
&lt;h2 id="aliases"&gt;Aliases&lt;/h2&gt;
&lt;p&gt;An alias is just a shortcut. Type in a certain set of characters, and the shell will replace them with the value defined in the alias.&lt;/p&gt;
&lt;p&gt;Some common commands are actually already aliases - for example, in my &lt;code&gt;zsh&lt;/code&gt; shell, the &lt;code&gt;ls&lt;/code&gt; command is an alias:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% type -a ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls is an alias &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ls -G
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls is /bin/ls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I make sure that when I use the &lt;code&gt;ls&lt;/code&gt; command, the shell always expands it to &lt;code&gt;ls -G&lt;/code&gt;, which colours the output.&lt;/p&gt;
&lt;p&gt;We can quickly define aliases to save on keystrokes. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ alias k&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;kubectl&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From this point on, I can use the &lt;code&gt;k&lt;/code&gt; alias as shorthand for the &lt;code&gt;kubectl&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;Aliases are far less sophisticated than functions. Think of them as keystroke savers and nothing more, and you won&amp;rsquo;t go far wrong. Aliases are not portable across shells and have certain behaviours which can make them problematic to work with, there will be an entire chapter dedicated to alisases coming up in the series.&lt;/p&gt;
&lt;h2 id="so-what"&gt;So What?&lt;/h2&gt;
&lt;p&gt;So we now hopefully have a greater understanding of the variety of shell commands. Not all commands are executables, not all of the commands we &lt;em&gt;think&lt;/em&gt; are executables necessarily are, and some commands might be more sophisticated.&lt;/p&gt;
&lt;p&gt;As a shell user, the key things to remember are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Executables are &amp;lsquo;safe&amp;rsquo; - they are programs your system can use; your shell just calls out to them.&lt;/li&gt;
&lt;li&gt;Builtins are &lt;em&gt;very&lt;/em&gt; shell-specific and usually control the shell itself&lt;/li&gt;
&lt;li&gt;Functions are powerful ways to write logic but will normally be shell-specific.&lt;/li&gt;
&lt;li&gt;Aliases are conveniences for human operators, but only in the context of an interactive shell.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To find out how a command is implemented, just use the &lt;code&gt;type -a&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ type -a cat
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat is /bin/cat
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="more-than-you-need-to-know"&gt;More than You Need to Know&lt;/h2&gt;
&lt;p&gt;OK, for the masochistic few, you might be wondering about all of the other commands and utilities you may have seen that can tell you about programs and commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;what&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;whatis&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;which&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;whence&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;where&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;whereis&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;command&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A &lt;em&gt;lot&lt;/em&gt; of these are legacy and should be avoided, but for completeness sake, we&amp;rsquo;ll go through them.&lt;/p&gt;
&lt;h3 id="what"&gt;&lt;code&gt;what&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;what&lt;/code&gt; reads out special metadata embedded in a program, generally used to identify the version of source code it was built from:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ what /bin/ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/bin/ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Copyright &lt;span style="color:#f92672"&gt;(&lt;/span&gt;c&lt;span style="color:#f92672"&gt;)&lt;/span&gt; 1989, 1993, &lt;span style="color:#ae81ff"&gt;1994&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PROGRAM:ls PROJECT:file_cmds-272.220.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There should be almost no circumstance in which you need to use it in your day-to-day work, but you might come across it if you &lt;em&gt;meant&lt;/em&gt; to type &lt;code&gt;whatis&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="whatis"&gt;&lt;code&gt;whatis&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;whatis&lt;/code&gt; searches a local help database for text. This can be useful in tracking down manual pages:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ whatis bash
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bash&lt;span style="color:#f92672"&gt;(&lt;/span&gt;1&lt;span style="color:#f92672"&gt;)&lt;/span&gt; - GNU Bourne-Again SHell
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bashbug&lt;span style="color:#f92672"&gt;(&lt;/span&gt;1&lt;span style="color:#f92672"&gt;)&lt;/span&gt; - report a bug in bash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But I can&amp;rsquo;t imagine it will be a regularly used tool by most users.&lt;/p&gt;
&lt;h3 id="which"&gt;&lt;code&gt;which&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;which&lt;/code&gt; will search your &lt;code&gt;$PATH&lt;/code&gt; to see whether an executable can be found. With the &lt;code&gt;-a&lt;/code&gt; flag, it will show all results.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ which -a vi
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/local/bin/vi
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/vi
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;which&lt;/code&gt; originated in &lt;code&gt;csh&lt;/code&gt;. It remains on many systems for compatibility but in general should be avoided due to potentially odd behaviour&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id="whence"&gt;&lt;code&gt;whence&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;whence&lt;/code&gt; was added to the Korn shell. You are unlikely to use it unless you are on systems using &lt;code&gt;ksh&lt;/code&gt;. &lt;code&gt;zsh&lt;/code&gt; also has this command, but it should be avoided and considered non-standard.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% whence brew
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/local/bin/brew
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="where"&gt;&lt;code&gt;where&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;This is a shell builtin that can provide information on commands, similar to &lt;code&gt;type&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% where ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls: aliased to ls -G
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/bin/ls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, &lt;code&gt;type&lt;/code&gt; should be preferred, as it is more standard.&lt;/p&gt;
&lt;h3 id="whereis"&gt;&lt;code&gt;whereis&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;whereis&lt;/code&gt; is available on some systems and generally operates the same as &lt;code&gt;which&lt;/code&gt;, searching paths for an executable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% whereis ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/bin/ls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Again, &lt;code&gt;type&lt;/code&gt; should be preferred for compatability.&lt;/p&gt;
&lt;h3 id="command"&gt;&lt;code&gt;command&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;command&lt;/code&gt; is defined in the POSIX standard, so should be expected to be present on most modern systems. Without arguments, it simply executes a command. With the &lt;code&gt;-v&lt;/code&gt; argument, you get a fairly machine-readable or processable response; with the &lt;code&gt;-V&lt;/code&gt; argument, you get a more human readable response:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% command -v ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;alias ls&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;ls -G&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% command -V ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls is an alias &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ls -G
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;command&lt;/code&gt; can be useful in scripts, as we will see in later chapters.&lt;/p&gt;
&lt;h3 id="type"&gt;&lt;code&gt;type&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;type&lt;/code&gt; is part of the Unix standard and will be present in most modern systems. As we&amp;rsquo;ve already seen, it will identify the type of command as well as the location for an executable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% type -a ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls is an alias &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ls -G
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls is /bin/ls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This command can also be used to only search for paths:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% type -p ls
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ls is /bin/ls
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In summary, avoid anything that starts with &amp;lsquo;&lt;code&gt;w&lt;/code&gt;&amp;rsquo;! These are legacy commands, generally needed only when working on older Unix machines. &lt;code&gt;type&lt;/code&gt; or &lt;code&gt;command&lt;/code&gt; should be used instead.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;We will cover permissions and modes in later chapters.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Why these names and locations? It&amp;rsquo;s a long story. The best place to start if you are intersted is the &lt;a href="https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard"&gt;Filesystem Hierarchy Standard&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;&lt;code&gt;chmod&lt;/code&gt; changes the mode of a file; &lt;code&gt;+x&lt;/code&gt; means &amp;lsquo;add the executable bit&amp;rsquo;. This tells the operating system the file can be executed.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;&lt;a href="https://unix.stackexchange.com/questions/85249/why-not-use-which-what-to-use-then"&gt;Stack Exchange: Why not use “which”? What to use then?&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item><item><title>Effective Shell Part 6: Everything You Don't Need To Know About Job Control</title><link>https://dwmkerr.com/effective-shell-6-job-control/</link><pubDate>Mon, 10 Jun 2019 08:26:33 +0000</pubDate><guid>https://dwmkerr.com/effective-shell-6-job-control/</guid><description>&lt;p&gt;&lt;em&gt;Job control&lt;/em&gt; is a feature of most shells, which is generally not particularly intuitive to work with. However, knowing the basics can help prevent you from getting yourself into a tangle, and can from time to time make certain tasks a little easier.&lt;/p&gt;
&lt;p&gt;In this chapter, we&amp;rsquo;ll look at the main features of job control, why it can be a problematic, and some alternatives.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-1-navigating-the-command-line/"&gt;Part 1: Navigating the Command Line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-2-become-a-clipboard-gymnast/"&gt;Part 2: Become a Clipboard Gymnast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-3-getting-hepl/"&gt;Part 3: Getting Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-4-moving-around/"&gt;Part 4: Moving Around&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-part-5-understanding-the-shell/"&gt;Part 5: Interlude - Understanding the Shell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dwmkerr.com/effective-shell-6-job-control/"&gt;Part 6: Everything You Don&amp;rsquo;t Need to Know About Job Control&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-7-shell-commands/"&gt;Part 7: The Subtleties of Shell Commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-is-job-control"&gt;What Is Job Control?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with an example. I am building a simple web page. It has one &lt;code&gt;index.html&lt;/code&gt; file, one &lt;code&gt;styles.css&lt;/code&gt; file, and one &lt;code&gt;code.js&lt;/code&gt; file. The &lt;code&gt;index.html&lt;/code&gt; file looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-html" data-lang="html"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;html&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;My New Project&amp;lt;/&lt;span style="color:#f92672"&gt;title&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;link&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rel&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;text/css&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;href&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;styles.css&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;src&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;code.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- Snip... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;html&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Opening the file in a browser doesn&amp;rsquo;t quite work, as it won&amp;rsquo;t load the code or the styles. We need a web server to serve styles and code.&lt;/p&gt;
&lt;p&gt;A super-useful one-liner to run a web server on any machine with Python installed is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In fact, this is so useful that I normally &lt;em&gt;alias&lt;/em&gt; this command, so that I can just type &lt;code&gt;serve&lt;/code&gt;. We&amp;rsquo;ll see aliases in a later chapter.&lt;/p&gt;
&lt;p&gt;For now, if we run this command (you can get &lt;a href="https://github.com/dwmkerr/effective-shell/tree/master/6-job-control/sample"&gt;the three sample files here&lt;/a&gt; if you want to try this yourself), then we can open the webpage in a browser, with the styles and code loaded:&lt;/p&gt;
&lt;img src="images/website-screenshot.png" alt="Screenshot: Website" width="600" /&gt;
&lt;p&gt;We can also see that the server has served the HTML, JavaScript, and CSS files:&lt;/p&gt;
&lt;img src="images/server-screenshot.png" alt="Screenshot: Server" width="600" /&gt;
&lt;p&gt;All well and good so far.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say we want to now continue using our shell, maybe to edit the website with a terminal editor like Vim or Emacs, or we want to zip up the site, or just run any shell command&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;We have a problem. The &lt;code&gt;python&lt;/code&gt; process is still running - it&amp;rsquo;s serving the website. Our shell is essentially useless, until we stop the server. See what happens when I try to edit a file:&lt;/p&gt;
&lt;img src="images/blocked-shell.gif" alt="Demo: Blocked Shell" width="600" /&gt;
&lt;p&gt;In the example above, I try to run &lt;code&gt;vi&lt;/code&gt;, but nothing is happening. Standard input is not being read by the server and not being interpreted by the shell.&lt;/p&gt;
&lt;p&gt;I have to kill the server by hitting &lt;code&gt;Ctrl+C&lt;/code&gt; (which sends a &lt;code&gt;SIGINT&lt;/code&gt;&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; - we&amp;rsquo;ll see more about signals later), clear my screen to get rid of all of the error messages, then start again.&lt;/p&gt;
&lt;p&gt;This is obviously not optimal. Let&amp;rsquo;s look at some solutions.&lt;/p&gt;
&lt;h2 id="solution-1-start-the-server-in-the-background"&gt;Solution 1: Start the Server in the Background&lt;/h2&gt;
&lt;p&gt;In most shells, you can run a command and instruct the shell to run it in the &lt;em&gt;background&lt;/em&gt;. To do this, you end the line with an ampersand. Here&amp;rsquo;s how the example would look in this case:&lt;/p&gt;
&lt;img src="images/start-in-background.gif" alt="Demo: Starting a Background Job" width="600" /&gt;
&lt;p&gt;By ending the command with an &lt;code&gt;&amp;amp;&lt;/code&gt; ampersand symbol, we instruct the shell to run the command as a &lt;em&gt;background job&lt;/em&gt;. This means that our shell is still functional. The shell has also notified us that this command is running as a background job with a specific &lt;em&gt;job number&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt; &amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;19372&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In slightly obtuse language, the shell has informed us that it has started a job in the background, with job number &lt;code&gt;1&lt;/code&gt; and that this job is currently handling the process with ID &lt;code&gt;19372&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The ampersand solution is a fairly common pattern used in day-to-day work.&lt;/p&gt;
&lt;h2 id="solution-2-move-the-server-to-the-background"&gt;Solution 2: Move the Server to the Background&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you forgot to start the command in the background. Most likely in this case you&amp;rsquo;d kill the server with &lt;code&gt;Ctrl+C&lt;/code&gt; and then start it again with the &lt;code&gt;&amp;amp;&lt;/code&gt; option. However, what if this was a large file download or a task you didn&amp;rsquo;t want to abort?&lt;/p&gt;
&lt;p&gt;In the example below, we&amp;rsquo;ll move the job to the background:&lt;/p&gt;
&lt;img src="images/move-to-background.gif" alt="Demo: Moving a Job to the Background" width="600" /&gt;
&lt;p&gt;The process is currently in the foreground, so my shell is inactive. Hitting &lt;code&gt;Ctrl+Z&lt;/code&gt; sends a &amp;lsquo;suspend&amp;rsquo; signal to the process&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, pausing it and moving it to the background.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s dissect this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Serving HTTP on 0.0.0.0 port &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;127.0.0.1 - - &lt;span style="color:#f92672"&gt;[&lt;/span&gt;03/Jun/2019 13:38:45&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GET / HTTP/1.1&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; -
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;^Z
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; + &lt;span style="color:#ae81ff"&gt;21268&lt;/span&gt; suspended python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The shell echos as I type, so we see &lt;code&gt;^Z&lt;/code&gt; (i.e., the &lt;code&gt;Ctrl+Z&lt;/code&gt; chord I entered). The shell responds by moving the process into a background job and suspending it.&lt;/p&gt;
&lt;p&gt;The key here is that it is &lt;em&gt;suspended&lt;/em&gt;. The process is paused. So the web server is no longer serving. If you are following with the sample, reload your browser. The webpage fails to load, as the server process is not able to respond to requests.&lt;/p&gt;
&lt;p&gt;To &lt;em&gt;continue&lt;/em&gt; the job, in the background, we use the &lt;code&gt;bg&lt;/code&gt; (&amp;lsquo;background&amp;rsquo;) command, with a &lt;em&gt;job identifier&lt;/em&gt; (which always starts with a &lt;code&gt;%&lt;/code&gt; symbol - we&amp;rsquo;ll see why soon) to tell the shell to continue the job:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% bg %1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; + &lt;span style="color:#ae81ff"&gt;21268&lt;/span&gt; continued python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The shell lets us know the job is being continued, and if we load the webpage again, the content is shown as expected.&lt;/p&gt;
&lt;p&gt;As a final check, we run the &lt;code&gt;jobs&lt;/code&gt; command to see what jobs the shell is running:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% jobs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; + running python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And there you have it - our server is running as a background job. This is exactly what we would see if we run &lt;code&gt;jobs&lt;/code&gt; after starting the server with an &lt;code&gt;&amp;amp;&lt;/code&gt; at the end. In fact, using an &lt;code&gt;&amp;amp;&lt;/code&gt; is perhaps an easier way to remember how to continue a suspended job:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% %1 &amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; + &lt;span style="color:#ae81ff"&gt;21268&lt;/span&gt; continued python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the same way ending a command with &lt;code&gt;&amp;amp;&lt;/code&gt; runs it in the background, ending a job identifier with &lt;code&gt;&amp;amp;&lt;/code&gt; &lt;em&gt;continues&lt;/em&gt; it in the background.&lt;/p&gt;
&lt;p&gt;There is at least one more way to move a job to the background&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;, but I have not yet found it useful in any scenarios, and it is overly complex to explain. See the footnote for details if you are interested.&lt;/p&gt;
&lt;h2 id="moving-background-jobs-to-the-foreground"&gt;Moving Background Jobs to the Foreground&lt;/h2&gt;
&lt;p&gt;If you have a job in the background, you can bring it back to the foreground with the &lt;code&gt;fg&lt;/code&gt; (&amp;lsquo;foreground&amp;rsquo;) command. Let&amp;rsquo;s show the jobs, with the &lt;code&gt;jobs&lt;/code&gt; command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% jobs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; + running python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here I have a background job running a server. Any one of the following commands will bring it back to the foreground:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fg %1 &lt;span style="color:#75715e"&gt;# Explicitly bring Job 1 into the foreground&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;%1 &lt;span style="color:#75715e"&gt;# ...or in shorthand, just enter the job id...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fg &lt;span style="color:#75715e"&gt;# ...if not given an id, fg and bg assume the most recent job.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now the job is in the foreground, and you can interact with the process again however you like.&lt;/p&gt;
&lt;h2 id="cleaning-up-jobs"&gt;Cleaning Up Jobs&lt;/h2&gt;
&lt;p&gt;You might realise you cannot continue what you are doing because an old job is &lt;em&gt;still running&lt;/em&gt;. Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;img src="images/kill-job.gif" alt="Demo: Cleaning Up Jobs" width="600" /&gt;
&lt;p&gt;I tried to run my web server, but there was still one running as a background job. The server failed to start because the port is in use.&lt;/p&gt;
&lt;p&gt;To clean it up, I run the &lt;code&gt;jobs&lt;/code&gt; command to list the jobs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% jobs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; + suspended python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s my old web server. Note that even though it is suspended, it&amp;rsquo;ll still be blocking the port it is serving on&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. The process is paused, but it is still holding onto all of the resources it is using.&lt;/p&gt;
&lt;p&gt;Now that I know the job identifier (&lt;code&gt;%1&lt;/code&gt; in this case), I can kill the job:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;% kill %1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;1&lt;span style="color:#f92672"&gt;]&lt;/span&gt; + &lt;span style="color:#ae81ff"&gt;22843&lt;/span&gt; terminated python -m SimpleHTTPServer &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;This is why job identifiers start with a percentage sign!&lt;/em&gt; The &lt;code&gt;kill&lt;/code&gt; command I have used is not a special job control command (like &lt;code&gt;bg&lt;/code&gt; or &lt;code&gt;fg&lt;/code&gt;). It is the normal &lt;code&gt;kill&lt;/code&gt; command, which terminates a process. But shells that support job control can normally use a job identifier in place of a &lt;em&gt;process identifier&lt;/em&gt;. So rather than working out what the process identifier is that I need to kill, I can just use the job identifier&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="why-you-shouldnt-use-jobs"&gt;Why You Shouldn&amp;rsquo;t Use Jobs&lt;/h2&gt;
&lt;p&gt;Avoid jobs. They are not intuitive to interface with, and they suffer from some serious problems.&lt;/p&gt;
&lt;p&gt;The most obvious one is that all jobs write to the same output, meaning you can quickly get garbled output like this:&lt;/p&gt;
&lt;img src="images/output.png" alt="Screenshot: Garbled Output" width="600" /&gt;
&lt;p&gt;This is what happens when I run a job, which just outputs text every second. It&amp;rsquo;s in the background, but it&amp;rsquo;s printing all over my commands. Even running the &lt;code&gt;jobs&lt;/code&gt; command to try and find the job to stop it is difficult.&lt;/p&gt;
&lt;p&gt;Input is even more complex. If a job is &lt;em&gt;running&lt;/em&gt; in the background, but requires input, it will be &lt;em&gt;silently suspended&lt;/em&gt;. This can cause confusion.&lt;/p&gt;
&lt;p&gt;Jobs &lt;em&gt;can&lt;/em&gt; be used in scripts but must be done so with caution and could easily confuse a consumer of the script if they leave background jobs hanging around, which cannot be easily cleaned up&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Handling errors and exit codes for jobs can be problematic, causing confusion, poor error handling, or overly complex code.&lt;/p&gt;
&lt;h2 id="how-to-escape-jobs"&gt;How to Escape Jobs&lt;/h2&gt;
&lt;p&gt;If there are two things to take away, it would be this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you have started running a command in the foreground, and you don&amp;rsquo;t want to stop it and would rather move it to the background, hit &lt;code&gt;Ctrl+Z&lt;/code&gt;. Then Google &amp;ldquo;job control&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you think there is a job running in the background, and it is messing with your screen, type &lt;code&gt;fg&lt;/code&gt; to bring it to the front and kill it with &lt;code&gt;Ctrl+C&lt;/code&gt;. Repeat as needed!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In either case, if you need to do something more subtle, you can return to this reference. But the first command should allow you to get your shell back while you work out how to continue the job, and the second should kill a background job that is messing with your screen.&lt;/p&gt;
&lt;h2 id="alternatives-to-jobs"&gt;Alternatives to Jobs&lt;/h2&gt;
&lt;p&gt;If you are using any kind of modern terminal such as iTerm, Terminal or the GNOME Terminal, just open a new tab or split! Much easier.&lt;/p&gt;
&lt;p&gt;The benefit to this is that each tab gets its own standard input and output, so there&amp;rsquo;s no risk of overwriting. And of course you can hide/reveal/rearrange the tabs however you like.&lt;/p&gt;
&lt;p&gt;The traditional alternative to a job for an operator who simply wants more than one thing going on at once would be a &lt;em&gt;terminal multiplexer&lt;/em&gt;, such as &lt;code&gt;screen&lt;/code&gt; or &lt;code&gt;tmux&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/terminal-multiplexer.gif" alt="terminal-multiplexer"&gt;&lt;/p&gt;
&lt;p&gt;Multiplexers work in a very similar way to a modern graphical terminal - they manage many shell instances. The benefits to a modern terminal, such as iTerm, is that you have a very intuitive GUI and lots of features.&lt;/p&gt;
&lt;p&gt;The benefits to a multiplexer are that you can run them over SSH sessions to manage complex operations on remote machines and that they run a client-server model, meaning many people can work with many multiplexed processes (and they can persist beyond sessions).&lt;/p&gt;
&lt;p&gt;My personal preference is both - I use a modern terminal &lt;em&gt;and&lt;/em&gt; run everything inside it in &lt;code&gt;tmux&lt;/code&gt;. We&amp;rsquo;ll look at both of these options in later chapters.&lt;/p&gt;
&lt;h2 id="quick-reference"&gt;Quick Reference&lt;/h2&gt;
&lt;p&gt;You might find that jobs are useful, or you might find that they are not. Either way, here&amp;rsquo;s a quick reference of some common commands:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;command &amp;amp;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run the command as a background job.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;Ctrl+Z&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Move the current process into a background job, suspended.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;jobs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all jobs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fg %1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Move background job number 1 into the foreground.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bg %1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Continue background job number 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kill %1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terminate job number 1.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;wait %1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Block until job number 1 exits.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you want to find out more about the gory details of jobs, the best place to start is the &lt;a href="https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Job-Control"&gt;Bash Manual - Job Control Section&lt;/a&gt;, or the &amp;lsquo;Job Control&amp;rsquo; section of your preferred shell&amp;rsquo;s manual.&lt;/p&gt;
&lt;p&gt;I hope you found this useful, and, as always, please leave comments, questions or suggestions below!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="footnotes"&gt;Footnotes&lt;/h2&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;If you are not a heavy shell user, this might seem unlikely. But if you do a lot of work in shells, such as sysadmin, devops, or do your coding from a terminal, this happens all the time!&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Signals like &lt;code&gt;SIGINT&lt;/code&gt;, &lt;code&gt;SIGKILL&lt;/code&gt;, &lt;code&gt;SIGTERM&lt;/code&gt; and so on will be covered in a later chapter.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Technically, &lt;code&gt;SIGTSTP&lt;/code&gt; - which is &amp;lsquo;TTY stop&amp;rsquo;. If you have always wondered about the &amp;lsquo;TTY&amp;rsquo; acroynm, check the previous chatper, &lt;a href="https://dwmkerr.com/effective-shell-part-5-understanding-the-shell/"&gt;Interlude: Understanding the Shell&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;The alternative method is to use &lt;code&gt;Ctrl+Y&lt;/code&gt;, which will send a &lt;em&gt;delayed interrupt&lt;/em&gt;, which will continue to run the process until it tries to read from &lt;code&gt;stdin&lt;/code&gt;. At this point, the job is suspended and the control given to the shell. The operator can then use &lt;code&gt;bg&lt;/code&gt; or &lt;code&gt;kill&lt;/code&gt; or &lt;code&gt;fg&lt;/code&gt; to either move to the background, stop the process, or keep in the foreground as preferred. See: &lt;a href="https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Job-Control"&gt;https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Job-Control&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Another super-useful snippet: &lt;code&gt;lsof -i -P -n | grep 8000&lt;/code&gt; to find any process that has a given port open. Another one for the aliases chapter!&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;There are times this is needed. If a job runs &lt;em&gt;many processes&lt;/em&gt; - for example, by running a pipeline - the process identifier will change as the command moves from one stage of the pipeline to the next. The job identifier will remain constant. Remember, a job is a shell &lt;em&gt;command&lt;/em&gt;, so could run many processes.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;To see how bad this can be, create a script that starts jobs, then run it. Then run the &lt;code&gt;jobs&lt;/code&gt; command to see what is running. The output might surprise you!&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item><item><title>Effective Shell Interlude: Understanding the Shell</title><link>https://dwmkerr.com/effective-shell-part-5-understanding-the-shell/</link><pubDate>Tue, 21 May 2019 09:22:05 +0000</pubDate><guid>https://dwmkerr.com/effective-shell-part-5-understanding-the-shell/</guid><description>&lt;p&gt;This is the first &amp;lsquo;interlude&amp;rsquo; in my &lt;a href="https://github.com/dwmkerr/effective-shell"&gt;Effective Shell&lt;/a&gt; series. These interludes give some background, history or more flavour to some of the topics.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-1-navigating-the-command-line/"&gt;Part 1: Navigating the Command Line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-2-become-a-clipboard-gymnast/"&gt;Part 2: Become a Clipboard Gymnast&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dwmkerr.com/effective-shell-part-3-getting-hepl/"&gt;Part 3: Getting Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-4-moving-around/"&gt;Part 4: Moving Around&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dwmkerr.com/effective-shell-part-5-understanding-the-shell/"&gt;Part 5: Interlude - Understanding the Shell&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-6-job-control/"&gt;Part 6: Everything You Don&amp;rsquo;t Need to Know About Job Control&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dwmkerr.com/effective-shell-7-shell-commands/"&gt;Part 7: The Subtleties of Shell Commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This one &lt;em&gt;should&lt;/em&gt; be high-level enough for even non-technical readers to enjoy (or at least understand!). I&amp;rsquo;ve tried to make sure any term that might be unfamiliar is described in a footnote&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. For the more technical reader, it provides an important grounding on some of the key concepts relating to shells and how they work.&lt;/p&gt;
&lt;h2 id="introduction-for-the-non-technical-reader"&gt;Introduction for the Non-Technical Reader&lt;/h2&gt;
&lt;p&gt;It might come as a surprise that &lt;em&gt;many&lt;/em&gt; technical computer users (programmers, data scientists, systems administrators etc) spend a lot of time using an interface which looks like it&amp;rsquo;s from the sixties:&lt;/p&gt;
&lt;img src="images/screenshot-shell.png" alt="Diagram: The Shell" width="600px" /&gt;
&lt;p&gt;If you work with technologists, you might have seen them using an interface like this. This kind of simple, text-based interface is called a &lt;em&gt;shell&lt;/em&gt;, and it has been a common way to interface with computers ever since the first screens and keyboards were created.&lt;/p&gt;
&lt;p&gt;Given how much computing has advanced, why would people use such an interface? Just look at how much the Windows operating-system has changed over the last three decades:&lt;/p&gt;
&lt;img src="images/screenshot-windows-evolution.png" alt="Image: The Evolution of Windows" width="600px" /&gt;
&lt;p&gt;&lt;em&gt;(By Source (WP:NFCC#4), Fair use, &lt;a href="https://en.wikipedia.org/w/index.php?curid=58853841"&gt;https://en.wikipedia.org/w/index.php?curid=58853841&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Why would people choose to use such an archaic interface as a shell?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Typing is &lt;em&gt;fast&lt;/em&gt;: A skilled shell user can manipulate a system at dazzling speeds just using a keyboard. Typing commands is generally &lt;em&gt;much&lt;/em&gt; faster than exploring through user interfaces with a mouse&lt;/li&gt;
&lt;li&gt;Shells are &lt;em&gt;programmable&lt;/em&gt;: Users will often being programming as they work in a shell, creating scripts to automate time-consuming or repetetive processes&lt;/li&gt;
&lt;li&gt;Shells are &lt;em&gt;portable&lt;/em&gt;: A shell can be used to interface to almost any type of computer, from a mainframe to a Raspberry Pi, in a very similar way.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Not all technical users will use a shell regularly, but there are many who will spend the bulk of their time in such an interface. It is such a crucial skill to be able to operate one effectively that I have been writing this series primarily to show ways to be more efficient with this kind of interface.&lt;/p&gt;
&lt;h2 id="introduction-for-the-technical-reader"&gt;Introduction for the Technical Reader&lt;/h2&gt;
&lt;p&gt;You may be familar with the shell, but it can be useful to understand some of the surrounding concepts in detail. How does a shell differ from a terminal? What is a &lt;em&gt;tty&lt;/em&gt;? How do shells really work? Hopefully as you read this article you&amp;rsquo;ll discovery something that you didn&amp;rsquo;t know about shells.&lt;/p&gt;
&lt;h2 id="lets-get-started"&gt;Let&amp;rsquo;s Get Started!&lt;/h2&gt;
&lt;p&gt;To understand what shells, terminals, command-prompts and so on are and how they relate, we need to start with the basics: how a modern computer works!&lt;/p&gt;
&lt;h2 id="a-computer-in-a-nutshell"&gt;A Computer in a Nutshell&lt;/h2&gt;
&lt;p&gt;The diagram below shows a simplified view of a typical computer:&lt;/p&gt;
&lt;img src="images/diagram1-operating-system.png" alt="Diagram: Operating System" width="600px" /&gt;
&lt;p&gt;Already there&amp;rsquo;s a lot going on.&lt;/p&gt;
&lt;p&gt;Your computer is going to have a CPU&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; and memory&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, and almost certainly a network adapter&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; and display adapter&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;. Most computers will have at least one hard disk. For home PCs, there&amp;rsquo;ll also likely be a bunch of peripherals, such as a mouse, keyboard, printers, flash drives, webcams and so on.&lt;/p&gt;
&lt;h3 id="the-operating-system"&gt;The Operating System&lt;/h3&gt;
&lt;p&gt;The operating system is the piece of software installed on a computer that can interface with the &lt;em&gt;hardware&lt;/em&gt;. Without hardware, such as a CPU, memory, a network adapter, a graphics card, disk drives and so on, there&amp;rsquo;s not much that you can do with the computer. The operating system is the primary interface to this hardware. No normal programs will talk to hardware directly - the operating system abstracts this hardware away and provides a &lt;em&gt;software&lt;/em&gt; interface to it.&lt;/p&gt;
&lt;p&gt;The abstraction the operating system provides is essential. Developers don&amp;rsquo;t need to know the specifics of how to work with individual devices from different vendors; the operating system provides a standardised interface to all of this. It also handles various tasks such as making sure the system starts up properly.&lt;/p&gt;
&lt;p&gt;The operating system is generally broken down into two parts - the &lt;em&gt;kernel&lt;/em&gt; and &lt;em&gt;user space&lt;/em&gt;:&lt;/p&gt;
&lt;img src="images/diagram2-the-kernel-and-user-space.png" alt="Diagram: The Kernel and User Space" width="600px" /&gt;
&lt;p&gt;Let&amp;rsquo;s look at these in more detail.&lt;/p&gt;
&lt;h3 id="the-kernel"&gt;The Kernel&lt;/h3&gt;
&lt;p&gt;This is the part of the operating system that is responsible for the most sensitive tasks: interfacing with physical devices, managing the resources that are available for users and programs, starting up the various systems that are needed, and so on.&lt;/p&gt;
&lt;p&gt;Software running in the kernel has direct access to resources, so is &lt;em&gt;extremely&lt;/em&gt; sensitive. The kernel will balance resources between the programs in user space, which we&amp;rsquo;ll look at shortly. If you&amp;rsquo;ve ever had to install &amp;lsquo;drivers&amp;rsquo;, these are examples of pieces of software that will run in the kernel - they&amp;rsquo;ll have direct access to a physical device you&amp;rsquo;ve installed, and expose it to the rest of the software on the computer.&lt;/p&gt;
&lt;p&gt;Why &amp;lsquo;kernel&amp;rsquo;? The kernel is the soft, edible part of a nut or seed, which is surrounded by a shell. Below you can see a walnut - the kernel is the soft bit in the middle, and the shell surrounds and protects it. This is a useful metaphor that is used for parts of a computer.&lt;/p&gt;
&lt;img src="images/image-walnut.jpg" alt="Image: Photo of a walnut, showing the kernel and the shell" width="200px" /&gt;
&lt;p&gt;&lt;em&gt;(By Kkchaudhary11 - Own work, CC BY-SA 4.0, &lt;a href="https://commons.wikimedia.org/w/index.php?curid=49069244"&gt;https://commons.wikimedia.org/w/index.php?curid=49069244&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The operating system kernel really is the &lt;em&gt;core&lt;/em&gt; of the operating system. It&amp;rsquo;s such a sensitive area of the operating system that we actually want to avoid running software in it if possible&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt;. And that is where &lt;em&gt;user space&lt;/em&gt; comes in.&lt;/p&gt;
&lt;h3 id="user-space"&gt;User Space&lt;/h3&gt;
&lt;p&gt;The vast majority of programs run in &amp;lsquo;user space&amp;rsquo; (also commonly called &amp;lsquo;user land&amp;rsquo;).&lt;/p&gt;
&lt;p&gt;When a program starts, the kernel will allocate it a private segment of memory and provide &lt;em&gt;limited&lt;/em&gt; access to resources. The program is given access to a library of functions by the operating system, which it can use to access resources such as files, devices and so on. Programs in user space are essentially in sandboxes, where there is a limit to how much damage they can do.&lt;/p&gt;
&lt;p&gt;For example, a program running in user space can use the standard &lt;a href="http://man7.org/linux/man-pages/man3/fopen.3.html"&gt;&lt;code&gt;fopen&lt;/code&gt;&lt;/a&gt; function, which is provided on almost every operating system as part of the &lt;a href="https://www.gnu.org/software/libc/"&gt;C Standard Library&lt;/a&gt;. This allows a program to attempt to open a file. The operating system will make a decision on whether the program is &lt;em&gt;allowed&lt;/em&gt; to open the file (based on things such as permissions, where the file is and so on) and then, if it is OK with the call, will give the program access to the file. Under the hood, this &amp;lsquo;user space&amp;rsquo; call translates to a system call in the kernel.&lt;/p&gt;
&lt;p&gt;Now that the key components have been introduced, we can look at the &lt;em&gt;shell&lt;/em&gt;. The name should come as no surprise, as it is a &lt;em&gt;wrapper&lt;/em&gt; or outer layer to the operating system (which itself contains the sensitive nugget of the kernel).&lt;/p&gt;
&lt;h3 id="the-shell"&gt;The Shell&lt;/h3&gt;
&lt;p&gt;So what is the shell? The shell is just a general name for any &lt;em&gt;user space&lt;/em&gt; program that allows access to resources in the system, via some kind of interface.&lt;/p&gt;
&lt;p&gt;Shells come in many different flavours but are generally provided to aid a human operator in accessing the system. This could be interactively, by typing at a terminal, or via scripts, which are files that contain a sequence of commands.&lt;/p&gt;
&lt;p&gt;For example, to see all of the files in a folder, the human operator &lt;em&gt;could&lt;/em&gt; write a program in a language such as C, making system calls to do what they want. But for day-to-day tasks, this would be repetitive. A shell will normally offer us a quick way to do that exact task, without having to manually write a program to do it.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example, where a shell is being used to show the &amp;lsquo;png&amp;rsquo; images in the folder I am working in&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;img src="images/screenshot1-example-shell.png" alt="Screenshot: Browsing Contents of the File System the the Bourne Again Shell" width="600px" /&gt;
&lt;p&gt;So a shell is a user-space program to interface with the computer. But there a few more moving parts than just a shell we are seeing in the image above. There are different types of shells, there are terminal programs, and there are the programs or commands that the shell calls (in the example above, &lt;code&gt;tree&lt;/code&gt; is a program). Let&amp;rsquo;s pick these apart.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a diagram that more accurately shows what is going on:&lt;/p&gt;
&lt;img src="images/diagram3-terminal-and-shell.png" alt="Diagram: The Terminal &amp; The Shell" width="600px" /&gt;
&lt;p&gt;We&amp;rsquo;ve introduced a few new things here. There&amp;rsquo;s a &lt;em&gt;user&lt;/em&gt;, who is interfacing with a &lt;em&gt;terminal&lt;/em&gt;, which is running a &lt;em&gt;shell&lt;/em&gt;, which is showing a &lt;em&gt;command prompt&lt;/em&gt;. The user has written a command that is calling a program (in this case, the &lt;code&gt;tree&lt;/code&gt; program).&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s dissect this bit by bit.&lt;/p&gt;
&lt;h3 id="the-terminal"&gt;The Terminal&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re not &lt;em&gt;directly&lt;/em&gt; interacting with the &amp;lsquo;shell&amp;rsquo; in this diagram. We&amp;rsquo;re actually using a &lt;em&gt;terminal&lt;/em&gt;. When a user wants to work with a shell interactively, using a keyboard to provide input and a display to see the output on the screen, the user uses a &lt;em&gt;terminal&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;A terminal is just a program that reads input from the keyboard, passes that input to another program (normally a shell), and displays the results on the screen. A shell program on its own does not do this - it requires a terminal as an interface.&lt;/p&gt;
&lt;p&gt;Why the word &lt;em&gt;terminal&lt;/em&gt;? This makes sense when you look at how people interfaced with computers historically. Input to a computer might be through punch cards, and output would often be via a printer. The &lt;em&gt;Teletype Termimal&lt;/em&gt;&lt;sup id="fnref:8"&gt;&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref"&gt;8&lt;/a&gt;&lt;/sup&gt; became a common way for users to interface with computers.&lt;/p&gt;
&lt;img src="images/image-asr-33.jpg" alt="Photo: ASR-33 TTY" width="600px" /&gt;
&lt;p&gt;&lt;em&gt;(Photograph by Rama, Wikimedia Commons, Cc-by-sa-2.0-fr, CC BY-SA 2.0 fr, &lt;a href="https://commons.wikimedia.org/w/index.php?curid=17821795"&gt;https://commons.wikimedia.org/w/index.php?curid=17821795&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;At this time, computers were very large, complex, and expensive machines. It was common to have &lt;em&gt;many&lt;/em&gt; terminals connected to a single large machine (or &amp;lsquo;mainframe&amp;rsquo;), or a few terminals that people would share. But the terminal itself was just a human interface to the operating system. A more modern terminal would be something like an IBM 3486:&lt;/p&gt;
&lt;img src="images/image-ibm3486.jpg" alt="Photo: IBM 3486" width="600px" /&gt;
&lt;p&gt;&lt;em&gt;(By ClickRick - Own work, CC BY-SA 3.0, &lt;a href="https://commons.wikimedia.org/w/index.php?curid=6693700"&gt;https://commons.wikimedia.org/w/index.php?curid=6693700&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is a very small computer in its own right but still basically just a dumb screen and keyboard connected by a cable to a larger mainframe computer in another location.&lt;/p&gt;
&lt;p&gt;This mechanism is still very much the case today. When I want to work with a computer in a data centre, I don&amp;rsquo;t go and find the machine, plug in a keyboard and a display and directly interface to it. I run a &lt;em&gt;terminal program&lt;/em&gt; on my computer to provide access to the remote machine. My terminal program allows me to use my keyboard and display to work with a remote machine - all via a &lt;em&gt;secure shell&lt;/em&gt; - which is a secured-shell connection over a network.&lt;/p&gt;
&lt;p&gt;So terminals in many ways are quite simple - they are interfaces. But because they are quite simple programs, we can&amp;rsquo;t do much with them. So normally, the first thing that a terminal program will do is run a &lt;em&gt;shell&lt;/em&gt; program - a program that we can use to operate the computer.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s nothing special about terminals - anyone can write a program to operate as a terminal, which is why you will see many different terminals around. Examples are the standard &amp;rsquo;terminal&amp;rsquo; app for MacOS X, the &lt;a href="https://wiki.gnome.org/Apps/Terminal/VTE"&gt;gnome-terminal&lt;/a&gt; for Linux, and &lt;a href="https://www.iterm2.com/"&gt;iTerm2&lt;/a&gt; and &lt;a href="https://hyper.is/"&gt;Hyper&lt;/a&gt;. There&amp;rsquo;s a bunch of screenshots of different setups at the end of the article.&lt;/p&gt;
&lt;h2 id="back-to-the-shell"&gt;Back to the Shell&lt;/h2&gt;
&lt;p&gt;Now that we&amp;rsquo;ve described the terminal, we can go back and look at the shell in detail.&lt;/p&gt;
&lt;p&gt;The shell is the program that is going to take input from somewhere and run a series of commands. When the shell is running in a terminal, it is normally taking input interactively from the user. As the user types in commands, the terminal feeds the input to the shell and presents the output of the shell on the screen.&lt;/p&gt;
&lt;p&gt;A shell program can also take input from files; these files will then generally be &amp;lsquo;shell scripts&amp;rsquo;. This might be used to run automated operations, such as cleaning up certain folders when a computer starts.&lt;/p&gt;
&lt;p&gt;Shells can write output to files or other locations, and so on. You can run a shell program outside of a terminal - you just won&amp;rsquo;t be able to interface with it using a keyboard or display. And in fact, lots of operations happen in this way: automated scripts, startup tasks, installers and so on.&lt;/p&gt;
&lt;p&gt;So what else does a shell do? Most of the features are related to helping human operators work with the system more efficiently.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Quickly enter commands, see the history of commands and quickly restructure commands (see &lt;a href="http://www.dwmkerr.com/effective-shell-part-1-navigating-the-command-line/"&gt;Effective Shell - Navigating the Command Line&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Navigate through the file system, moving from folder to folder (see &lt;a href="https://dwmkerr.com/effective-shell-4-moving-around/"&gt;Effective Shell - Move Around!&lt;/a&gt;), which makes it easier for an operator to navigate the file system.&lt;/li&gt;
&lt;li&gt;Chain the output of commands together - for example, taking the output of one basic program, such as the &lt;code&gt;tree&lt;/code&gt; program we saw, and writing it to a file (see &lt;a href="https://github.com/dwmkerr/effective-shell#coming-soon"&gt;Effective Shell - Understanding Pipelines&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Offer a programming language, allowing the operator to perform more complicated tasks (see &lt;a href="https://github.com/dwmkerr/effective-shell#coming-soon"&gt;Effective Shell - Basic Shell Scripting&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a lot more! In fact, that&amp;rsquo;s what the whole &lt;a href="https://github.com/dwmkerr/effective-shell"&gt;Effective Shell&lt;/a&gt; series is about - how to get the most from these powerful programs, particularly for those who use them regularly.&lt;/p&gt;
&lt;h3 id="the-command-prompt-or-command-line"&gt;The Command Prompt or Command Line&lt;/h3&gt;
&lt;p&gt;The last part of the diagram, which we haven&amp;rsquo;t covered yet, is the &lt;em&gt;command prompt&lt;/em&gt;.&lt;/p&gt;
&lt;img src="images/diagram4-command-prompt-1.png" alt="Diagram: Command Prompt" width="300px" /&gt;
&lt;p&gt;When a &lt;em&gt;shell&lt;/em&gt; is running in &lt;em&gt;terminal&lt;/em&gt;, it knows that a human operator will be interfacing with it. So to make sure that the operator has some kind of visual hint that &lt;em&gt;they have to enter commands&lt;/em&gt;, the shell will output some kind of prompt.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve included a set of screenshots at the end of the article, just after this section, and you can see how some different command prompts look.&lt;/p&gt;
&lt;p&gt;Note that shells don&amp;rsquo;t have to use command prompts - if you use a shell program to execute a script, there will be no command prompt. Shells only show a prompt when they know they are being used interactively. Many programs which allow a user to operate interactively will show a command prompt.&lt;/p&gt;
&lt;p&gt;Shell command prompts can be customised, so they will often look different from machine to machine (for more details, see &lt;a href="https://github.com/dwmkerr/effective-shell#coming-soon"&gt;Effective Shell - Customising the Command Line&lt;/a&gt;). Below is an example that shows a &lt;em&gt;lot&lt;/em&gt; of technical information. This is from the highly popular &lt;a href="https://ohmyz.sh/"&gt;oh-my-zsh&lt;/a&gt; framework for the &amp;lsquo;Z Shell&amp;rsquo; shell, which is very popular among developers:&lt;/p&gt;
&lt;img src="images/image-ohmyzsh.jpg" alt="Image: Customised oh-my-zsh" width="600px" /&gt;
&lt;p&gt;*(Source: &lt;a href="https://ohmyz.sh/"&gt;https://ohmyz.sh/&lt;/a&gt;)&lt;/p&gt;
&lt;h3 id="shell-commands-and-different-shells"&gt;Shell Commands and Different Shells&lt;/h3&gt;
&lt;p&gt;A lot of the &amp;lsquo;commands&amp;rsquo; in a shell, such as &lt;code&gt;cat&lt;/code&gt; (which shows the contents of a file), are actually just simple programs, which will interface with the kernel. No matter what shell you use, these commands will behave the same way, because really all you are doing is calling another progam.&lt;/p&gt;
&lt;p&gt;Some commands, such as &lt;code&gt;cd&lt;/code&gt; (change directory), are built into the shell. Some commands are functions that have been defined, or aliases to other commands (for more details on commands, see &lt;a href="https://github.com/dwmkerr/effective-shell#coming-soon"&gt;Effective Shell - Commands&lt;/a&gt;). Commands will often differ between shells.&lt;/p&gt;
&lt;p&gt;Not all shells are created equal - anyone can write a shell program, maybe creating a simple interface to the computer or a highly complex one with many features. In fact, a later article in this series will look at the geneology of the most common shells.&lt;/p&gt;
&lt;p&gt;On most Unix-like systems, the default shell is a program called &lt;code&gt;bash&lt;/code&gt;, which stands for &amp;quot; Bourne Again Shell&amp;quot; (the name and history around it will be discussed at length in the later article). But there are many other shells: the C Shell, the Korn Shell, Z Shell and Fish, just to name just a few.&lt;/p&gt;
&lt;p&gt;Users and administators can configure what shell they like to use. When a terminal opens, it will immediately start the user&amp;rsquo;s preferred shell program. It is possible to change this. Different users will have different preferences, given that shells offer varying features. This can cause complexity when working with systems, as we cannot always expect every user to have the same shell, or even for the same shell to be set up consistently, as they can be extensively customised.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s review the earlier diagram again:&lt;/p&gt;
&lt;img src="images/diagram3-terminal-and-shell-1.png" alt="Diagram: The Terminal &amp; The Shell" width="600px" /&gt;
&lt;p&gt;We can see the real internals of what is going on in this &amp;ldquo;Terminal -&amp;gt; Shell -&amp;gt; Program&amp;rdquo; chain in the diagram above quite easily.&lt;/p&gt;
&lt;p&gt;Try the command &lt;code&gt;pstree -psa $$&lt;/code&gt; in a shell&lt;sup id="fnref:9"&gt;&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref"&gt;9&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;img src="images/image-psforest.png" alt="Image: Process Tree" width="600px" /&gt;
&lt;p&gt;The first &lt;code&gt;systemd&lt;/code&gt; process is the primary process for the OS - it is process number &lt;code&gt;1&lt;/code&gt;, which initialises everything else. The second &lt;code&gt;systemd&lt;/code&gt; process is the process that is running the interface for my user. We can ignore these for now; they are internals to how the operating system boots and starts processes.&lt;/p&gt;
&lt;p&gt;What is interesting is that we can see a &lt;em&gt;terminal&lt;/em&gt; (the gnome terminal), which has started my preferred &lt;em&gt;shell&lt;/em&gt; (which is &lt;code&gt;zsh&lt;/code&gt;), which is running a &lt;em&gt;command&lt;/em&gt; (the program &lt;code&gt;pstree&lt;/code&gt;). Here we can see the exact chain as shown in the diagram earlier.&lt;/p&gt;
&lt;h3 id="thats-a-wrap"&gt;That&amp;rsquo;s a Wrap!&lt;/h3&gt;
&lt;p&gt;These are the key technologies and concepts that surround a shell.&lt;/p&gt;
&lt;p&gt;If you are interested in more technical details of working with shells, then my &lt;a href="https://github.com/effective-shell"&gt;Effective Shell&lt;/a&gt; series goes into these topics in depth. The goal of this series is to help teach techniques that making working with shells more efficient.&lt;/p&gt;
&lt;p&gt;To close the article, below are some examples of different terminals, shells, command prompts and so on.&lt;/p&gt;
&lt;h4 id="example-iterm-2--tmux--zsh"&gt;Example: iTerm 2 / tmux / zsh&lt;/h4&gt;
&lt;img src="images/example-iterm-zsh.png" alt="Example: iTerm 2, tmux, zsh" width="600px" /&gt;
&lt;p&gt;In this example, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A MacOS operating system&lt;/li&gt;
&lt;li&gt;iTerm2 as the terminal program&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tmux&lt;/code&gt; running as a &amp;rsquo;terminal multiplexer&amp;rsquo; (see &lt;a href="https://github.com/dwmkerr/effective-shell#coming-soon"&gt;Effective Shell: Terminal Multiplexers&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zsh&lt;/code&gt; (Z Shell) as the shell program, using &amp;lsquo;oh my zsh&amp;rsquo;, which is easily recognised by the &lt;code&gt;%&lt;/code&gt; sign in the command prompt.&lt;/li&gt;
&lt;li&gt;A customised command line, which shows the user and folder on one line, with only the &lt;code&gt;%&lt;/code&gt; symbol below, to leave lots of space for the input commands&lt;sup id="fnref:10"&gt;&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref"&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="example-bash"&gt;Example: Bash&lt;/h4&gt;
&lt;img src="images/example-bash.png" alt="Example: Bash" width="600px" /&gt;
&lt;img src="images/example-bash-root.png" alt="Example: Bash Elevated" width="600px" /&gt;
&lt;p&gt;In this example, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A Linux operating system (Ubuntu 14)&lt;/li&gt;
&lt;li&gt;The gnome terminal&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bash&lt;/code&gt; as the shell&lt;/li&gt;
&lt;li&gt;In the second screenshot, the user has &amp;lsquo;root privileges&amp;rsquo;, and to indicate this, &lt;code&gt;bash&lt;/code&gt; helpfully changes the default command prompt from a dollar sign to a hash sign&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="example-windows-explorer"&gt;Example: Windows Explorer&lt;/h4&gt;
&lt;img src="images/example-explorer.png" alt="Example: Windows Explorer" width="600px" /&gt;
&lt;p&gt;In this example, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Windows 10 operating system&lt;/li&gt;
&lt;li&gt;No terminal&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;explorer.exe&lt;/code&gt; program showing us a &lt;em&gt;graphical&lt;/em&gt; shell&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This looks different from previous examples. The program, which shows the familiar Windows interface, &lt;code&gt;explorer.exe&lt;/code&gt;, is in fact a shell as well, offering interactive access to the operating system and computer resources. The bulk of the Windows APIs to interact with this interface are in the &lt;a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb773177(v=vs.85).aspx"&gt;Shell Library&lt;/a&gt;. I also maintain a popular library for building extensions to the graphical Windows shell - &lt;a href="https://github.com/dwmkerr/sharpshell"&gt;sharpshell&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="example-windows-command-prompt"&gt;Example: Windows Command Prompt&lt;/h4&gt;
&lt;img src="images/example-cmd.png" alt="Example: Command Prompt" width="600px" /&gt;
&lt;p&gt;In this example, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Windows 10 operating system&lt;/li&gt;
&lt;li&gt;The command prompt terminal and shell&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In Windows, the terminal and shell are combined into a single &lt;code&gt;cmd.exe&lt;/code&gt; program. There&amp;rsquo;s an excellent article on the internals - &lt;a href="https://devblogs.microsoft.com/commandline/windows-command-line-inside-the-windows-console/"&gt;Microsoft DevBlogs: Windows Command-Line: Inside the Windows Console&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="example-windows-powershell"&gt;Example: Windows PowerShell&lt;/h4&gt;
&lt;img src="images/example-powershell.png" alt="Example: Windows Powershell" width="600px" /&gt;
&lt;p&gt;In this example, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Windows 10 operating system&lt;/li&gt;
&lt;li&gt;The PowerShell terminal&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PowerShell is an improvement on the &amp;lsquo;command prompt&amp;rsquo; program that was originally used in Windows, offering much more functionality for scripting and other modern shell features.&lt;/p&gt;
&lt;h4 id="example-windows-subsystem-for-linux-wsl"&gt;Example: Windows Subsystem for Linux (WSL)&lt;/h4&gt;
&lt;img src="images/example-wsl.png" alt="Example: WSL" width="600px" /&gt;
&lt;p&gt;In this example, we have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Windows 10 operating system&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Bash.exe&lt;/code&gt; program&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This screenshot, from &lt;a href="https://docs.microsoft.com/en-us/windows/wsl/faq"&gt;MSDN: Frequently Asked Questions about Windows Subsystem for Linux&lt;/a&gt; shows Bash running in Windows. This is a relatively new feature at the time of writing, allowing Windows users to use a Linux interface to the PC. This is a feature that may become increasingly valuable, as in general it is challenging to write shell code that can run on Windows and Unix-like systems.&lt;/p&gt;
&lt;h2 id="share-and-discuss"&gt;Share and Discuss&lt;/h2&gt;
&lt;p&gt;If you enjoyed this article, please do share it! Feel free to include suggestions, improvements or corrections in the comments below.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Useful References&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A simple Linux kernel module, showing how basic kernel programming works in Linux: &lt;a href="https://github.com/dwmkerr/linux-kernel-module"&gt;github.com/dwmkerr/linux-kernel-module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/How-Linux-Works-2nd-Superuser/dp/1593275676"&gt;How Linux Works - Brian Ward&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://unix.stackexchange.com/questions/4126/what-is-the-exact-difference-between-a-terminal-a-shell-a-tty-and-a-con/4132"&gt;StackExchange: What is the exact difference between a &amp;rsquo;terminal&amp;rsquo;, a &amp;lsquo;shell&amp;rsquo;, a &amp;rsquo;tty&amp;rsquo;, and a console?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/commandline/windows-command-line-inside-the-windows-console/"&gt;Microsoft: Inside the Windows Console&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Footnotes&lt;/strong&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I&amp;rsquo;d be fascinated to know if this is at all interesting to less technically inclined people, so please do go ahead and let me know in the comments!&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;CPU: central processing unit. This is the chip in the computer that does most of the work (which after many layers of abstraction eventually becomes arithmetic and sending simple instructions to other places).&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Memory is the &amp;lsquo;working space&amp;rsquo; where the state of your system is stored. If you are writing a document, the text lives in memory, until you save it, when it then gets written to a hard drive. Memory is &lt;em&gt;ephemeral&lt;/em&gt; - everything is gone when you turn off the power to it.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;This is the part of your computer that knows how to do things like connect to a WiFi network, or has a network socket you might plug a network cable into.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;This is the part of your computer you plug the screen into.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;This is because a mistake in &lt;em&gt;Kernel Mode&lt;/em&gt; programs can have disasterous effects. It could access any files, no matter who they belong do, control the hardware, install more software - almost anything. Errors in this code can cause terrible issues (like the infamous Windows &amp;lsquo;blue screen of death&amp;rsquo;), and malicious code in the kernel essentially has full access to not only all your data but also your webcam, network adapter and so on.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;As an aside, if you are curious about the visual style of my setup or customisations that have been made, everything in my setup is available online on my &amp;lsquo;dotfiles&amp;rsquo; repo - &lt;a href="https://github.com/dwmkerr/dotfiles"&gt;github.com/dwmkerr/dotfiles&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:8"&gt;
&lt;p&gt;And that&amp;rsquo;s where the &amp;lsquo;TTY&amp;rsquo; acronym you will see sometimes comes from. Enter the &lt;code&gt;ps&lt;/code&gt; command, and you&amp;rsquo;ll actually see the TTY interface each process is attached to. This is a topic that will come up later in the series.&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:9"&gt;
&lt;p&gt;&lt;code&gt;$$&lt;/code&gt; is a Bash &lt;a href="https://www.tldp.org/LDP/abs/html/internalvariables.html#PROCCID"&gt;internal variable&lt;/a&gt;. These will also be covered in a later article in the series.&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:10"&gt;
&lt;p&gt;Feel free to see my &lt;a href="https://github.com/dwmkerr/dotfiles"&gt;dotfiles&lt;/a&gt; to configure a similar setup for yourself.&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item><item><title>Manipulating Istio and other Custom Kubernetes Resources in Golang</title><link>https://dwmkerr.com/manipulating-istio-and-other-custom-kubernetes-resources-in-golang/</link><pubDate>Mon, 08 Oct 2018 21:34:02 +0000</pubDate><guid>https://dwmkerr.com/manipulating-istio-and-other-custom-kubernetes-resources-in-golang/</guid><description>&lt;p&gt;In this article I&amp;rsquo;ll demonstrate how to use Golang to manipulate Kubernetes Custom Resources, with Istio as an example. No knowledge of Istio is needed, I&amp;rsquo;ll just use it to demonstrate the concepts!&lt;/p&gt;
&lt;p&gt;&lt;img src="images/code-2.jpg" alt="code"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://istio.io"&gt;Istio&lt;/a&gt; is a highly popular Service Mesh platform which allows engineers to quickly add telemetry, advanced traffic management and more to their service-based applications.&lt;/p&gt;
&lt;p&gt;One interesting element of how Istio works is that when deployed into a Kubernetes cluster, many key configuration objects are handled as &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/"&gt;Custom Resources&lt;/a&gt;. Custom Resources are a very powerful Kubernetes feature, which allow you to create your own &amp;lsquo;first class&amp;rsquo; resources (just like Pods, ReplicaSets, Deployments or whatever) and then interface with them using &lt;code&gt;kubectl&lt;/code&gt; or the Kubernetes APIs.&lt;/p&gt;
&lt;p&gt;In this article I&amp;rsquo;ll show you how to interface with these Custom Resources using the Golang Kubernetes client.&lt;/p&gt;
&lt;h2 id="crds-a-quick-overview"&gt;CRDs: A Quick Overview&lt;/h2&gt;
&lt;p&gt;When you set up Istio for your cluster, one common thing you will likely do is specify how you will route traffic. This can be quite sophisticated, as shown below:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/TrafficManagementOverview.svg" alt="TrafficManagementOverview"&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://istio.io/docs/concepts/traffic-management/"&gt;Figure 1: Istio Traffic Management Examples, from istio.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One way for a system like this to be configured would be to have a ConfigMap which contains the definition of how services are routed.&lt;/p&gt;
&lt;p&gt;However, Istio actually registers new types of resources (Custom Resource Definitions) which represent things like Gateways or Services. We can create/update/delete/manipulate them just like any other Kubernetes object.&lt;/p&gt;
&lt;p&gt;For example, I could create a virtual service for the example above with something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat &lt;span style="color:#e6db74"&gt;&amp;lt;&amp;lt; EOF | kubectl create -f -
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;apiVersion: networking.istio.io/v1alpha3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;kind: VirtualService
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; name: service2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; hosts:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - &amp;#34;*&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; gateways:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - demo1-gateway
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; http:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - route:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - destination:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; host: service2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; subset: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; weight: 95
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; - destination:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; host: service2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; subset: v2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; weight: 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Again, the important thing is not the specific content of this resource, more the fact that I can treat my Istio resources just like I would any other Kubernetes object:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ kubectl get virtualservices.networking.istio.io
NAME AGE
service2 93s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Or:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ kubectl delete virtualservices.networking.istio.io/service2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I can use &lt;code&gt;edit&lt;/code&gt;, &lt;code&gt;describe&lt;/code&gt;, register lifecycle events, watch for changes, and so on.&lt;/p&gt;
&lt;h2 id="working-with-crds-in-golang"&gt;Working with CRDs in Golang&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://github.com/kubernetes/client-go"&gt;Golang Kubernetes Client&lt;/a&gt; allows you to create strongly defined types which you can then use to interface with CRDs. An example is in the Red Hat blog post &lt;a href="https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/"&gt;Kubernetes Deep Dive: Code Generation for Custom Resources&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This is an excellent approach, but can feel pretty heavy if you want to quickly access some data, and don&amp;rsquo;t want to have to generate a lot of code.&lt;/p&gt;
&lt;p&gt;There is an alternative, which is to use the &lt;a href="https://github.com/kubernetes/client-go/blob/master/dynamic/interface.go"&gt;&lt;code&gt;DynamicClient&lt;/code&gt;&lt;/a&gt;. The &lt;em&gt;preferred&lt;/em&gt; approach seems to be the first, which involves code generation, so little documentation exists for the second approach. However, it is actually very simple.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example of how you can list all Istio &lt;code&gt;VirtualService&lt;/code&gt; resources, without having to generate any code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;metav1&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/apimachinery/pkg/apis/meta/v1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/client-go/dynamic&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Create a Dynamic Client to interface with CRDs.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;dynamicClient&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;dynamic&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewForConfig&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Create a GVR which represents an Istio Virtual Service.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;virtualServiceGVR&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;schema&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GroupVersionResource&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Group&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;networking.istio.io&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;v1alpha3&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Resource&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;virtualservices&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// List all of the Virtual Services.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;virtualServices&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;dynamicClient&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Resource&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;virtualServiceGVR&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Namespace&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;List&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;metav1&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ListOptions&lt;/span&gt;{})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;virtualService&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;range&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;virtualServices&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Items&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;VirtualService: %s\n&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;virtualService&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GetName&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This snippet omits setup and error-handling for clarity, the full example is in the &lt;a href="https://gist.github.com/dwmkerr/09ac0fd98595460456e17d5ef0c77667"&gt;k8s-list-virtualservices.go&lt;/a&gt; gist.&lt;/p&gt;
&lt;h2 id="patching-crds-in-golang"&gt;Patching CRDs in Golang&lt;/h2&gt;
&lt;p&gt;You may have noticed that the &lt;code&gt;.Resource().Namespace().List()&lt;/code&gt; code looks very similar to the structure for making API calls when using the Kubernetes &lt;code&gt;Clientset&lt;/code&gt;. In fact, it is essentially the same. Looking at &lt;a href="https://github.com/kubernetes/client-go/blob/master/dynamic/interface.go"&gt;the interface&lt;/a&gt;, you can see you have all of the operations you&amp;rsquo;d expect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Create&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Delete&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Get&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so on. This is nice because you can use the same trick in my article &amp;lsquo;&lt;a href="https://www.dwmkerr.com/patching-kubernetes-resources-in-golang/"&gt;Patching Kubernetes Resources in Golang&lt;/a&gt;&amp;rsquo; to manipulate these entities, without ever having to create a structure to represent it.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s another abbreviated example, this time showing how we can adjust the weight of the routing from the services to 50%/50%:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;metav1&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/apimachinery/pkg/apis/meta/v1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/client-go/dynamic&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Create a GVR which represents an Istio Virtual Service.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;virtualServiceGVR&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;schema&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;GroupVersionResource&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Group&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;networking.istio.io&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;v1alpha3&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Resource&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;virtualservices&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Weight the two routes - 50/50.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; make([]&lt;span style="color:#a6e22e"&gt;PatchUInt32Value&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;Op&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;replace&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;/spec/http/0/route/0/weight&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;Op&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;replace&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;/spec/http/0/route/1/weight&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt; = &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;patchBytes&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Marshal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;patchPayload&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Apply the patch to the &amp;#39;service2&amp;#39; service.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;dynamicClient&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Resource&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;virtualServiceGVR&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Namespace&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;Patch&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;service2&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;types&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;JSONPatchType&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;patchBytes&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;See the full example in the gist &lt;a href="https://gist.github.com/dwmkerr/7332888e092156ce8ce4ea551b0c321f"&gt;k8s-patch-virtualservice.go&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After running the sample, you can use the Kubernetes CLI to verify the changes:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ kubectl get virtualservices.networking.istio.io/service2 -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
clusterName: &amp;#34;&amp;#34;
creationTimestamp: 2018-10-08T09:53:16Z
generation: 0
name: service2
namespace: default
resourceVersion: &amp;#34;487435&amp;#34;
selfLink: /apis/networking.istio.io/v1alpha3/namespaces/default/virtualservices/service2
uid: fac5930c-cadf-11e8-90a2-42010a94005b
spec:
gateways:
- demo1-gateway
hosts:
- &amp;#39;*&amp;#39;
http:
- route:
- destination:
host: service2
subset: v1
weight: 50
- destination:
host: service2
subset: v2
weight: 50
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="keep-it-simple"&gt;Keep It Simple!&lt;/h2&gt;
&lt;p&gt;That&amp;rsquo;s it! This trick made something I was working on a &lt;em&gt;lot&lt;/em&gt; easier, but it took a little bit of experimentation to get right. I hope you find the approach useful. Please share any thoughts/questions in the comments.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;Further Reading&lt;/h2&gt;
&lt;p&gt;The following articles were using in working out this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/"&gt;Red Hat: Deep Dive: Code Generation for Custom Resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/"&gt;Kubernetes Docs: Custom Resources&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>CodeProject</category></item><item><title>Patching Kubernetes Resources in Golang</title><link>https://dwmkerr.com/patching-kubernetes-resources-in-golang/</link><pubDate>Tue, 24 Jul 2018 06:33:17 +0000</pubDate><guid>https://dwmkerr.com/patching-kubernetes-resources-in-golang/</guid><description>&lt;p&gt;Recently I needed to be able to quickly adjust the number of replicas in a Kubernetes Replication Controller. The original solution I&amp;rsquo;d seen pulled down the spec, modified it, then updated it. There&amp;rsquo;s a better way!&lt;/p&gt;
&lt;p&gt;&lt;img src="images/patch-1.jpg" alt="Kuberentes Patch API"&gt;&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a &lt;a href="https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/"&gt;patch API for Kubernetes resources&lt;/a&gt;. Patching resources is faster and easier than pulling them and updating the spec wholesale. However, the documentation is a little limited.&lt;/p&gt;
&lt;p&gt;After some trial and error I got it working, here&amp;rsquo;s the solution. I thought it might be helpful to share for others!&lt;/p&gt;
&lt;h3 id="the-solution"&gt;The Solution&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ll start with the solution. If this is all you need, you are good to go. The details of how this works are presented afterwards. In this example I&amp;rsquo;ll update the number of replicas in the &lt;code&gt;my-rc&lt;/code&gt; controller:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;encoding/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;types&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/apimachinery/pkg/types&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/client-go/kubernetes&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/client-go/plugin/pkg/client/auth&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;k8s.io/client-go/tools/clientcmd&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Leave blank for the default context in your kube config.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Name of the replication controller to scale, and the desired number of replicas.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;replicationControllerName&lt;/span&gt; = &lt;span style="color:#e6db74"&gt;&amp;#34;my-rc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;replicas&lt;/span&gt; = uint32(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// patchStringValue specifies a patch operation for a string.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;patchStringValue&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Op&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;op&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;path&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;value&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// patchStringValue specifies a patch operation for a uint32.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;type&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;patchUInt32Value&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Op&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;op&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;path&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint32&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`json:&amp;#34;value&amp;#34;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;scaleReplicationController&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;clientSet&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Clientset&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;replicasetName&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;scale&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;uint32&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;payload&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; []&lt;span style="color:#a6e22e"&gt;patchUInt32Value&lt;/span&gt;{{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Op&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;replace&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Path&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/spec/replicas&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Value&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;scale&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;payloadBytes&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Marshal&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;payload&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;clientSet&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;CoreV1&lt;/span&gt;().
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ReplicationControllers&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;).
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Patch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;replicasetName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;types&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;JSONPatchType&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;payloadBytes&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Get the local kube config.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Connecting to Kubernetes Context %v\n&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;clientcmd&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewNonInteractiveDeferredLoadingClientConfig&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientcmd&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewDefaultClientConfigLoadingRules&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;clientcmd&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ConfigOverrides&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;CurrentContext&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;context&lt;/span&gt;}).&lt;span style="color:#a6e22e"&gt;ClientConfig&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; panic(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Creates the clientset&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientset&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;NewForConfig&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; panic(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Scale our replication controller.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Scaling replication controller %v to %v\n&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;replicationControllerName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;replicas&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;scaleReplicationController&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;clientset&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;replicationControllerName&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;replicas&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; panic(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Error&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code is also available in the &lt;a href="https://gist.github.com/dwmkerr/447692c8bba28929ef914239781c4e59"&gt;k8s-patch.go&lt;/a&gt; gist.&lt;/p&gt;
&lt;h3 id="the-mechanism"&gt;The Mechanism&lt;/h3&gt;
&lt;p&gt;The Kubernetes Patch API supports a few different methods for modifying resources. It is important to be aware that there is not a universally accepted &amp;lsquo;standard&amp;rsquo; approach to representing a &lt;em&gt;change&lt;/em&gt; to a resource in a REST API.&lt;/p&gt;
&lt;p&gt;There are three strategies you can use to patch:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;merge&lt;/code&gt;: follows the &lt;a href="https://tools.ietf.org/html/rfc7386"&gt;JSON Merge Patch Spec (RFC 7386)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stragetic&lt;/code&gt;: A strategic merge, which addresses some limitations of the merge patch (noted in &lt;a href="%5Bdocs/devel/api-conventions.md#patch-operations%5D(https://github.com/kubernetes/kubernetes/blob/release-1.1/docs/devel/api-conventions.md#patch-operations)"&gt;this doc&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;json&lt;/code&gt;: follows the &lt;a href="https://tools.ietf.org/html/rfc6902"&gt;JSON Patch Spec (RFC 6902)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These are documented in detail at:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/kubernetes/kubernetes/blob/release-1.1/docs/devel/api-conventions.md#patch-operations"&gt;docs/devel/api-conventions.md#patch-operations&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The mechanism I&amp;rsquo;ve used here is &lt;code&gt;json&lt;/code&gt;, which I think is the clearest to the reader. To use this strategy we need to build a payload describing what we are changing. This might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;op&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;replace&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/spec/replicas&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;op&lt;/code&gt; field can be &lt;code&gt;remove&lt;/code&gt;, &lt;code&gt;replace&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt; etc etc (all the details are in the &lt;a href="https://tools.ietf.org/html/rfc6902"&gt;RFC 6902)&lt;/a&gt;, or the slightly more readable &lt;a href="jsonpatch.com"&gt;jsonpatch.com&lt;/a&gt;). This allows the operation to be very &lt;em&gt;explicit&lt;/em&gt; to the reader, which is helpful. We create a struct which represents an operation on a string or integer (or whatever data type we need), serialize it and pass to the API.&lt;/p&gt;
&lt;p&gt;Under the hood, the Golang client will simply translate this into an HTTP call which will look like something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PATCH /api/v1/namespaces/default/replicationcontrollers/app-server-blue HTTP/1.1
Host: 127.0.0.1
Content-Type: application/json-patch+json
Content-Length: 70
[{
&amp;#34;op&amp;#34;: &amp;#34;replace&amp;#34;,
&amp;#34;path&amp;#34;: &amp;#34;/spec/replicas&amp;#34;,
&amp;#34;value&amp;#34;: 4
}]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This corresponds to the documentation on the &lt;a href="https://github.com/kubernetes/kubernetes/blob/release-1.1/docs/devel/api-conventions.md#patch-operations"&gt;Patch Operations&lt;/a&gt;. Note that the patch operation type is specified in the &lt;code&gt;Content-Type&lt;/code&gt; header.&lt;/p&gt;
&lt;p&gt;Hopefully this&amp;rsquo;ll help you if you need to patch resources, are struggling with the docs and are a Go noob like me! Any tips on how to make the code cleaner or more idomatic would be welcome.&lt;/p&gt;
&lt;p&gt;Thanks to the following articles and issues which helped me unpick this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackoverflow.com/questions/43415728/kubernetes-go-client-patch-example"&gt;Stack Overflow: Kubernetes Go Client Patch Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/run-application/update-api-object-kubectl-patch/"&gt;Kubernetes Docs: Update API Objects in Place Using kubectl patch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kubernetes/kubernetes/blob/release-1.1/docs/devel/api-conventions.md#patch-operations"&gt;Kubernetes Docs: Patch Operations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><category>CodeProject</category></item><item><title>mongo-monitor - a simple CLI to monitor your MongoDB cluster</title><link>https://dwmkerr.com/mongo-monitor-cli/</link><pubDate>Wed, 16 May 2018 20:09:53 +0000</pubDate><guid>https://dwmkerr.com/mongo-monitor-cli/</guid><description>&lt;p&gt;The &lt;code&gt;mongo-monitor&lt;/code&gt; CLI is a lean and simple tool to check the status of a MongoDB server or cluster. The code is on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/mongo-monitor"&gt;github.com/dwmkerr/mongo-monitor&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how it looks in action:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/overview.gif" alt="Screenshot: Using the mongo-monitor CLI to monitor a sharded cluster"&gt;&lt;/p&gt;
&lt;p&gt;In this animation I am monitoring a simple sharded cluster, and running some example maintenance operations, adding a node to a replicaset, stepping down a primary and shutting down a replicaset node.&lt;/p&gt;
&lt;p&gt;A simple CLI which shows the status in real-time can be very useful to keep open when performing admin, letting you see how your changes affect the cluster as you work on it.&lt;/p&gt;
&lt;h2 id="installing-the-cli"&gt;Installing the CLI&lt;/h2&gt;
&lt;p&gt;The CLI is installed with &lt;code&gt;npm&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install -g mongo-monitor
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="connecting-to-a-cluster"&gt;Connecting to a Cluster&lt;/h2&gt;
&lt;p&gt;Connect to a cluster by providing a connection string. The tool uses &lt;a href="https://github.com/dwmkerr/mongo-connection-string"&gt;&lt;code&gt;mongo-connection-string&lt;/code&gt;&lt;/a&gt; to parse the connection string, so you can be flexible with the input:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Connect to a local instance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mongo-monitor localhost:27107
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Connect to a remote replicaset, authenticated&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mongo-monitor admin:P@sswrd@mdbnode1,mdbnode2,mdbnode3?replicaSet&lt;span style="color:#f92672"&gt;=&lt;/span&gt;rs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once a connection is established, the tool will periodically check the status of the cluster. If the cluster is sharded, it will also inspect each individual replicaset.&lt;/p&gt;
&lt;h2 id="replicaset-status"&gt;Replicaset Status&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the kind of output you might get from a replicaset:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/replicaset.jpg" alt="Screenshot: Replicaset Status"&gt;&lt;/p&gt;
&lt;p&gt;The name of the replicaset is shown, along with each member. The status of each member is also shown, updating automatically every second.&lt;/p&gt;
&lt;p&gt;This is convenient when administering replicasets, stepping down a master, adding or removing nodes and so on.&lt;/p&gt;
&lt;h2 id="sharded-cluster-status"&gt;Sharded Cluster Status&lt;/h2&gt;
&lt;p&gt;When connecting to a sharded cluster, you will get output like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/sharded-cluster.jpg" alt="Screenshot: Sharded Cluster Status"&gt;&lt;/p&gt;
&lt;p&gt;Each shard is shown, along with the details of the replicaset which make it up.&lt;/p&gt;
&lt;p&gt;Keeping a view like this open is useful when administering sharded clusters, adding or removing shards, desharding, updating the replicasets which make up shards and so on.&lt;/p&gt;
&lt;h2 id="get-involved"&gt;Get Involved!&lt;/h2&gt;
&lt;p&gt;If you like the tool, check out the code and feel free to make pull requests with additions! There are a few &lt;a href="https://github.com/dwmkerr/mongo-monitor/issues"&gt;issues&lt;/a&gt; on the project already, and there are all sorts of features I&amp;rsquo;d love to add but haven&amp;rsquo;t had the time, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Being able to see the lag for replicaset members, to see if secondaries are falling behind&lt;/li&gt;
&lt;li&gt;Being able to perform replicaset operations directly from the tool&lt;/li&gt;
&lt;li&gt;Showing the priorities of nodes if they are not the default&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All ideas are welcome, let me know in the comments or repo, and share the tool if you find it useful!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>The Death of Microservice Madness in 2018</title><link>https://dwmkerr.com/the-death-of-microservice-madness-in-2018/</link><pubDate>Fri, 12 Jan 2018 10:52:25 +0000</pubDate><guid>https://dwmkerr.com/the-death-of-microservice-madness-in-2018/</guid><description>&lt;p&gt;&lt;a href="https://www.campusmvp.es/recursos/post/la-muerte-de-la-locura-de-los-microservicios-en-2018.aspx"&gt;En Español&lt;/a&gt; | &lt;a href="https://www.reddit.com/r/programming/comments/7pxriw/the_death_of_microservice_madness_in_2018/"&gt;Reddit Thread&lt;/a&gt; | &lt;a href="https://news.ycombinator.com/item?id=16200007"&gt;Hacker News Thread&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Microservices became a very popular topic over the last couple of years&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. &amp;lsquo;Microservice madness&amp;rsquo; goes something like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Netflix are great at devops.
Netflix do microservices.
Therefore: If I do microservices, I am great at devops.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are many cases where great efforts have been made to adopt microservice patterns without necessarily understanding how the costs and benefits will apply to the specifics of the problem at hand.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m going to describe in detail what microservices are, why the pattern is so appealing, and also some of the key challenges that they present.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll finish with a set of simple questions might be valuable to ask yourself when you are considering whether microservices are the right pattern &lt;em&gt;for you&lt;/em&gt;. The questions are at the end of the article.&lt;/p&gt;
&lt;p&gt;&lt;img src="images/letterbox.png" alt="Letterbox sample of diagram"&gt;&lt;/p&gt;
&lt;h2 id="what-are-microservices-and-why-are-they-so-popular"&gt;What are microservices, and why are they so popular?&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with the basics. Here is how a hypothetical video sharing platform might be implemented, first in the form of a monolith (single large unit) and then in the form of microservices:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/video-platform-monolith-microservices.png" alt="Diagram: Comparison of a Video Sharing Platform, Monolith vs Microservice"&gt;&lt;/p&gt;
&lt;p&gt;The difference between the two systems is that the first is a single large unit; a monolith. The second is a set of small, specific services. Each service has a specific role.&lt;/p&gt;
&lt;p&gt;When the diagram is drawn &lt;em&gt;at this level of detail&lt;/em&gt;, it is easy to see the appeal. There are a whole host of potential benefits:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Independent Development&lt;/strong&gt;: Small, independent components can be built by small, independent teams. A group can work on a change to the &amp;lsquo;Upload&amp;rsquo; service without interfering with the &amp;lsquo;Transcode&amp;rsquo; service, or even knowing about it. The amount of time to learn about a component is greatly reduced, and it is easier to develop new features.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Independent Deployment&lt;/strong&gt;: Each individual component can be deployed independently. This allows new features to be released with greater velocity and less risk. Fixes or features for the &amp;lsquo;Streaming&amp;rsquo; component can be deployed without requiring other components to be deployed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Independent Scalability&lt;/strong&gt;: Each component can be scaled independently of each other. During busy periods when new shows are released, the &amp;lsquo;Download&amp;rsquo; component can be scaled up to handle the increased load, without having to scale up every component, which makes elastic scaling more feasible and reduces costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reusability&lt;/strong&gt;: Components fulfil a small, specific function. This means that they can more easily be adapted for use in other systems, services or products. The &amp;lsquo;Transcode&amp;rsquo; component could be used by other business units, or even turned into a new business, perhaps offering transcoding services for other groups.&lt;/p&gt;
&lt;p&gt;At this level of detail, the benefits of a microservice model over a monolithic model seem obvious. So if that&amp;rsquo;s the case - why is this pattern only recently in vogue? Where has it been all my life?&lt;/p&gt;
&lt;h2 id="if-this-is-so-great-why-hasnt-it-been-done-before"&gt;If this is so great, why hasn&amp;rsquo;t it been done before?&lt;/h2&gt;
&lt;p&gt;There are two answers to this question. One is that &lt;em&gt;it has&lt;/em&gt; - to the best of our technical capabilities, and the other is that more recent technical advances have allowed us to take it to a new level.&lt;/p&gt;
&lt;p&gt;When I started writing the answer to this question, it turned into a &lt;em&gt;long&lt;/em&gt; description, so I&amp;rsquo;m actually going to separate it into another article and publish it a little later&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. At this stage, I will skip the journey from single program to many programs, ignore ESBs and Service Orientated Architecture, component design and bounded contexts, and so on.&lt;/p&gt;
&lt;p&gt;Those who are interested can read more about the journey separately. Instead I&amp;rsquo;ll say that in many ways we&amp;rsquo;ve been doing this for a while, but with the recent explosion in popularity of container technology (Docker in particular) and in orchestration technology (such as Kubernetes, Mesos, Consul and so on) this pattern has become much more viable to implement from a technical standpoint.&lt;/p&gt;
&lt;p&gt;So if we take it as a given that we &lt;em&gt;can&lt;/em&gt; implement a microservice arrangement, we need to think carefully about the &lt;em&gt;should&lt;/em&gt;. We&amp;rsquo;ve seen the high-level theoretical benefits, but what about the challenges?&lt;/p&gt;
&lt;h2 id="whats-the-problem-with-microservices"&gt;What&amp;rsquo;s the problem with microservices?&lt;/h2&gt;
&lt;p&gt;If microservices are so great, what&amp;rsquo;s the big deal? Here are some of the biggest issues I&amp;rsquo;ve seen.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Increased complexity for developers&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Things &lt;em&gt;can&lt;/em&gt; get a lot harder for developers. In the case where a developer wants to work on a &lt;em&gt;journey&lt;/em&gt;, or feature which might span many services, that developer has to run them all on their machine, or connect to them. This is often more complex than simply running a single program.&lt;/p&gt;
&lt;p&gt;This challenge can be partially mitigated with tooling&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, but as the number of services which makes up a system increases, the more challenges developers will face when running the system as a whole.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Increased complexity for operators&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For teams who don&amp;rsquo;t develop services, but maintain them, there is an explosion in potential complexity. Instead of perhaps managing a few running services, they are managing dozens, hundreds or thousands of running services. There are more services, more communication paths, and more areas of potential failure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Increased complexity for devops&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Reading the two points above, it may grate that operations and development are treated separately, especially given the popularity of devops as a practice (which I am a big proponent of). Doesn&amp;rsquo;t devops mitigate this?&lt;/p&gt;
&lt;p&gt;The challenge is that many organisations still run with separated development and operations teams - and a organisation that does is much more likely to struggle with adoption of microservices.&lt;/p&gt;
&lt;p&gt;For organisations which have adopted devops, it&amp;rsquo;s still hard. Being both a developer and an operator is already tough (but critical to build good software), but having to also understand the nuances of container orchestration systems, particularly systems which are evolving at a rapid pace, is very hard. Which brings me onto the next point.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It requires serious expertise&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When done by experts, the results can be wonderful. But imagine an organisation where perhaps things are not running smoothly with a single monolithic system. What possible reason would there be that things would be any better by increasing the number of systems, which increases the operational complexity?&lt;/p&gt;
&lt;p&gt;Yes, with effective automation, monitoring, orchestration and so on, this is all possible. But the challenge is rarely the technology - the challenge is finding people who can use it effectively. These skillsets are currently in very high demand, and may be difficult to find.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real world systems often have poorly defined boundaries&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In all of the examples we used to describe the benefits of microservices, we spoke about &lt;em&gt;independent&lt;/em&gt; components. However in many cases components are simply not independent. On paper, certain domains may look bounded, but as you get into the muddy details, you may find that they are more challenging to model than you anticipated.&lt;/p&gt;
&lt;p&gt;This is where things can get &lt;em&gt;extremely&lt;/em&gt; complex. If your boundaries are actually not well defined, then what happens is that even though &lt;em&gt;theoretically&lt;/em&gt; services can be deployed in isolation, you find that due to the inter-dependencies between services, you have to deploy &lt;em&gt;sets&lt;/em&gt; of services as a group.&lt;/p&gt;
&lt;p&gt;This then means that you need to manage coherent versions of services which are proven and tested when working together, you don&amp;rsquo;t actually have an independently deployable system, because to deploy a new feature, you need to carefully orchestrate the simultaneous deployment of many services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The complexities of state are often ignored&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the previous example, I mentioned that a feature deployment may require the simultaneous rollout of many versions of many services in tandem. It is tempting to assume that sensible deployment techniques will mitigate this, for example blue/green deployments (which most service orchestration platforms handle with little effort), or multiple versions of a service being run in parallel, with consuming channels deciding which version to use.&lt;/p&gt;
&lt;p&gt;These techniques mitigate a large number of the challenges &lt;em&gt;if the services are stateless&lt;/em&gt;. But stateless services are quite frankly, easy to deal with. In fact, if you have stateless services, then I&amp;rsquo;d be inclined to consider skipping microservices altogether and consider using a serverless model.&lt;/p&gt;
&lt;p&gt;In reality, many services require state. An example from our video sharing platform might be the subscription service. A new version of the subscriptions service may store data in the subscriptions database in a different shape. If you are running both services in parallel, you are running the system with two schemas at once. If you do a blue green deployment, and other services depend on data in the new shape, then they must be updated &lt;em&gt;at the same time&lt;/em&gt;, and if the subscription service deployment fails and rolls back, they might need to roll back too, with cascading consequences.&lt;/p&gt;
&lt;p&gt;Again, it might be tempting to think that with NoSQL databases these issues of schema go away, but they don&amp;rsquo;t. Databases which don&amp;rsquo;t enforce schema do not lead to schemaless systems - they just mean that schema tends to be managed at the application level, rather than the database level. The fundamental challenge of understanding the shape of your data, and how it evolves, cannot be eliminated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The complexitities of communication are often ignored&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As you build a large network of services which depend on each other, the liklihood is that there will be a lot of inter-service communication. This leads to a few challenges. Firstly, there are a lot more points at which things can fail. We must expect that network calls will fail, which means when one service calls another, it should expect to have to retry a number of times at the least. Now when a service has to potentially call many services, we end up in a complicated situation.&lt;/p&gt;
&lt;p&gt;Imagine a user uploads a video in the video sharing service. We might need to run the upload service, pass data to the transcode service, update subscriptions, update recommendations and so on. All of these calls require a degree of orchestration, if things fail we need to retry.&lt;/p&gt;
&lt;p&gt;This retry logic can get hard to manage. Trying to do things synchronously often ends up being untenable, there are too many points of failure. In this case, a more reliable solution is to use asynchronous patterns to handle communication. The challenge here is that asynchronous patterns inherently make a system stateful. As mentioned in the previous point, stateful systems and systems with distributed state are very hard to handle.&lt;/p&gt;
&lt;p&gt;When a microservice system uses message queues for intra-service communication, you essentially have a large database (the message queue or broker) glueing the services together. Again, although it might not seem like a challenge at first, schema will come back to bite you. A service at version X might write a message with a certain format, services which depend on this message will also need to be updated when the sending service changes the details of the message it sends.&lt;/p&gt;
&lt;p&gt;It is possible to have services which can handle messages in many different formats, but this is hard to manage. Now when deploying new versions of services, you will have times where two different versions of a service may be trying to process messages from the same queue, perhaps even messages sent by different versions of a sending service. This can lead to complicated edge cases. To avoid these edge cases, it may be easier to only allow certain versions of messages to exist, meaning that you need to deploy a set of versions of a set of services as a coherent whole, ensuring messages of older versions are drained appropriately first.&lt;/p&gt;
&lt;p&gt;This highlights again that the idea of independent deployments may not hold as expected when you get into the details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Versioning can be hard&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To mitigate the challenges mentioned previously, versioning needs to be very carefully managed. Again, there can be a tendency to assume that following a standard such as semver[4] will solve the problem. It doesn&amp;rsquo;t. Semver is a sensible convention to use, but you will still have to track the versions of services and APIs which can work together.&lt;/p&gt;
&lt;p&gt;This can get very challenging very quickly, and may get to the point where you don&amp;rsquo;t know which versions of services will actually work properly together.&lt;/p&gt;
&lt;p&gt;Managing dependencies in software systems is notoriously hard, whether it is node modules, Java modules, C libraries or whatever. The challenges of &lt;em&gt;conflicts between independent components&lt;/em&gt; when consumed by a single entity are very hard to deal with.&lt;/p&gt;
&lt;p&gt;These challenges are hard to deal with when the dependencies are static, and can be patched, updated, edited and so on, but if the dependencies are themselves &lt;em&gt;live services&lt;/em&gt;, then you may not be able to just update them - you may have to run many versions (with the challenges already described) or bring down the system until it is fixed holistically.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distributed Transactions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In situations where you need transaction integrity across an operation, microservices can be very painful. Distributed state is hard to deal with, many small units which can fail make orchestrating transactions very hard.&lt;/p&gt;
&lt;p&gt;It may be tempting to attempt to avoid the problem by making operations idempotent, offering retry mechanisms and so on, and in many cases this might work. But you may have scenarios where you simply need a transaction to fail or succeed, and never be in an intermediate state. The effort involved in working around this or implementing it in a microservice model may be very high.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Microservices can be monoliths in disguise&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yes, individual services and components &lt;em&gt;may&lt;/em&gt; be deployed in isolation, however in most cases you are going to have to be running some kind of orchestration platform, such as Kubernetes. If you are using a managed service, such as Google&amp;rsquo;s GKE&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt; or Amazon&amp;rsquo;s EKS&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt;, then a large amount of the complexity of managing the cluster is handled for you.&lt;/p&gt;
&lt;p&gt;However, if you are managing the cluster yourself, you are managing a large, complicated, mission critical system. Although the individual services may have all of the benefits described earlier, you need to very carefully manage your cluster. Deployments of this system can be hard, updates can be hard, failover can be hard and so on.&lt;/p&gt;
&lt;p&gt;In many cases the overall benefits are still there, but it is important not to trivialise or underestimate the additional complexity of managing another big, complex system. Managed services may help, but in many cases these services are nascent (Amazon EKS was only announced at the end of 2017 for example).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Networking Nightmares&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A more traditional model of services running on known hosts, with known addresses, has a fairly simple networking setup.&lt;/p&gt;
&lt;p&gt;However, when using microservices, generally there will be many services distributed across many nodes, which typically means there&amp;rsquo;s going to be a &lt;em&gt;much&lt;/em&gt; more complicated networking arrangement. There will be load balancing between services, DNS may be more heavily used, virtual networking layers, etc etc, to attempt to &amp;lsquo;hide&amp;rsquo; the complexity of this networking.&lt;/p&gt;
&lt;p&gt;However, as per &lt;a href="https://github.com/dwmkerr/hacker-laws/#the-law-of-conservation-of-complexity-teslers-law"&gt;Tesler&amp;rsquo;s Law&lt;/a&gt; (or the Law of Conservation of Compexlity), this networking complexity is inherent - when you are finding real, runtime issues in larger scale clusters, it can often be at a very low networking level. These sorts of issues can be &lt;em&gt;very&lt;/em&gt; hard to diagnose. I have started tracking some examples at the end of the article, but I think that &lt;a href="https://medium.com/@tinder.engineering/tinders-move-to-kubernetes-cda2a6372f44"&gt;Tinder&amp;rsquo;s Migration to Kuberenetes&lt;/a&gt; shows this challenge very well.&lt;/p&gt;
&lt;p&gt;Overall - the transition is still likely to be for the best, but doesn&amp;rsquo;t come without some serious challenges at the networking level, which will require some serious expertise to deal with!&lt;/p&gt;
&lt;h2 id="the-death-of-microservice-madness"&gt;The Death of Microservice Madness!&lt;/h2&gt;
&lt;p&gt;Avoid the madness by making careful and considered decisions. To help out on this I&amp;rsquo;ve noted a few questions you might want to ask yourself, and what the answers might indicate:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/questions.png" alt="Diagram: Questions to ask yourself when considering microservices"&gt;&lt;/p&gt;
&lt;p&gt;You can download a PDF copy here: &lt;a href="https://github.com/dwmkerr/blog/blob/master/articles/2018/microservice-madness/images/microservice-questions.pdf"&gt;microservice-questions.pdf&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="final-thoughts-dont-confuse-microservices-with-architecture"&gt;Final Thoughts: Don&amp;rsquo;t Confuse Microservices with Architecture&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve deliberately avoided the &amp;lsquo;a&amp;rsquo; word in this article. But my friend &lt;a href="http://twitter.com/zoltanarvai"&gt;Zoltan&lt;/a&gt; made a very good point when proofing this article (which he has contributed to).&lt;/p&gt;
&lt;p&gt;There is no microservice architecture. Microservices are just another pattern or implementation of components, nothing more, nothing less. Whether they are present in a system or not does not mean that the architecture of the system is solved.&lt;/p&gt;
&lt;p&gt;Microservices relate in many ways more to the technical processes around packaging and operations rather than the intrinsic design of the system. Appropriate boundaries for components continues to be one of the most important challenges in engineering systems.&lt;/p&gt;
&lt;p&gt;Regardless of the size of your services, whether they are in Docker containers or not, you will always need to think carefully about how to put a system together. There are no right answers, and there are a &lt;em&gt;lot&lt;/em&gt; of options.&lt;/p&gt;
&lt;p&gt;I hope you found this article interesting! As always, please do comment below if you have any questions or thoughts. You can also follow some lively discussions on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/programming/comments/7pxriw/the_death_of_microservice_madness_in_2018/"&gt;Reddit - The Death of Microservice Madness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=16200007"&gt;Hacker News - The Death of Microservice Madness&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="appendix-further-reading"&gt;Appendix: Further Reading&lt;/h2&gt;
&lt;p&gt;The following links might be of interest:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/bliki/BoundedContext.html"&gt;Martin Fowler - Bounded Context&lt;/a&gt; - Martin&amp;rsquo;s articles are great, I&amp;rsquo;d thoroughly recommend this.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://martinfowler.com/articles/microservices.html"&gt;Martin Fowler - Microservices&lt;/a&gt; - An often recommended introduction to the pattern.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://r2m.se/microservices-good-or-bad/"&gt;Microservices - Good or Bad?&lt;/a&gt; - Björn Frantzén&amp;rsquo;s thoughts on microservices, after reading this article.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://blog.christianposta.com/microservices/when-not-to-do-microservices/"&gt;When Not To Do Microservices&lt;/a&gt; - Excellent post on the topic from Christian Posta&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.iheavy.com/2017/03/13/30-questions-to-ask-a-serverless-fanboy/"&gt;Sean Hull - 30 questions to ask a serverless fanboy&lt;/a&gt; - Interesting thoughts on the challenges of serverless, from a serverless fan!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/NVb7aljfKYo?t=6657"&gt;Dave Kerr - Monoliths to Microservices - Practical tips for CI/CD and DevOps in the Microservice world&lt;/a&gt; - A recent conference presentation I did on devops with microservices.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yermakov.net/microservices-without-fundamentals/"&gt;Alexander Yermakov - Microservices without fundamentals&lt;/a&gt; - A response to this article, with Alex&amp;rsquo;s thoughts and counterpoints to the points raised here (see also &lt;a href="https://yermakov.net/microservices-as-a-self-sufficient-concept/"&gt;Microservices as a self sufficient concept&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please do share anything else you think makes great reading or watching on the topic!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="thanks"&gt;Thanks&lt;/h2&gt;
&lt;p&gt;Thanks José from &lt;a href="https://www.campusmvp.es"&gt;campusmvp.es&lt;/a&gt; for having the article translated in Spanish - &lt;a href="https://www.campusmvp.es/recursos/post/la-muerte-de-la-locura-de-los-microservicios-en-2018.aspx"&gt;La muerte de la locura de los microservicios en 2018&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="case-studies"&gt;Case Studies&lt;/h2&gt;
&lt;p&gt;Some interesting examples of experiences I am collecting of larger organisations who have made large scale transitions to microservices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@tinder.engineering/tinders-move-to-kubernetes-cda2a6372f44"&gt;Tinder&amp;rsquo;s Move to Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;a href="https://trends.google.com/trends/explore?date=today%205-y&amp;amp;q=microservice"&gt;https://trends.google.com/trends/explore?date=today%205-y&amp;amp;q=microservice&lt;/a&gt;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;If you don&amp;rsquo;t want to miss the article, you can subscribe to the &lt;a href="http://www.dwmkerr.com/rss/"&gt;RSS Feed&lt;/a&gt;, or follow me on &lt;a href="https://www.linkedin.com/in/dwmkerr/"&gt;LinkedIn&lt;/a&gt; or &lt;a href="https://twitter.com/dwmkerr"&gt;Twitter&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Docker Compose is a good solution, &lt;a href="https://github.com/apparatus/fuge"&gt;Fuge&lt;/a&gt; is very clever, and there is also the option of running orchestration locally as is the case with something like MiniKube.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;Google Kubernetes Engine, a managed service from Google Cloud Platform for Kubernetes: &lt;a href="https://cloud.google.com/kubernetes-engine/"&gt;https://cloud.google.com/kubernetes-engine/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;Amazon Elastic Container Services for Kubernetes, a managed service from Amazon Web Services for Kubernetes: &lt;a href="https://aws.amazon.com/eks/"&gt;https://aws.amazon.com/eks/&lt;/a&gt;&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item><item><title>Simple Continuous Integration for Docker Images</title><link>https://dwmkerr.com/simple-continuous-integration-for-docker-images/</link><pubDate>Thu, 03 Nov 2016 05:14:35 +0000</pubDate><guid>https://dwmkerr.com/simple-continuous-integration-for-docker-images/</guid><description>&lt;p&gt;In this article I&amp;rsquo;m going to demonstrate a few tips and tricks which can make your life easier when you are building or maintaining Dockerfiles.&lt;/p&gt;
&lt;h2 id="the-need-for-a-build-pipeline"&gt;The need for a Build Pipeline&lt;/h2&gt;
&lt;p&gt;Do we really need any kind of continuous integration or build pipeline for Dockerfiles?&lt;/p&gt;
&lt;p&gt;There will be cases when the answer is no. However, if the answer to any of the following questions is &amp;lsquo;yes&amp;rsquo;, it might be worth considering:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Do you want others to be able to contribute to the Dockerfile, perhaps changing the image over time?&lt;/li&gt;
&lt;li&gt;Are there specific functionalities in your Dockerfiles which could break if altered?&lt;/li&gt;
&lt;li&gt;Do you expect to need to release updates to your Dockerfile?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Essentially, if we are looking at providing some kind of automated quality assurance and automation around building and releasing, then a build pipeline is not a bad idea.&lt;/p&gt;
&lt;h2 id="a-simple-build-pipeline"&gt;A simple Build Pipeline&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what a simple build pipeline could look like. This example is for a Docker Image I just created for local DynamoDB development - &lt;a href="https://github.com/dwmkerr/docker-dynamodb"&gt;dwmkerr/docker-dynamodb&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/Simple-Docker-Image-CI.png" alt="Simple Continous Intergration Pipeline"&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s dissect what we&amp;rsquo;ve got here.&lt;/p&gt;
&lt;h3 id="the-dockerfile"&gt;The Dockerfile&lt;/h3&gt;
&lt;p&gt;This is the main &amp;lsquo;code&amp;rsquo; of the project if you like. The &lt;a href="https://github.com/dwmkerr/docker-dynamodb/blob/master/Dockerfile"&gt;Dockerfile&lt;/a&gt; is the recipe for the image we create.&lt;/p&gt;
&lt;h3 id="the-continuous-integration-service"&gt;The Continuous Integration Service&lt;/h3&gt;
&lt;p&gt;In this case, I am using &lt;a href="https://circleci.com/"&gt;CircleCI&lt;/a&gt;, however the approach described would work fine with most CI systems (such as Jenkins, TravisCI and TeamCity). There &lt;em&gt;is&lt;/em&gt; an option to use the &lt;a href="https://docs.docker.com/docker-hub/builds/"&gt;Docker Hub Automated Builds&lt;/a&gt;, but I&amp;rsquo;ve found this doesn&amp;rsquo;t give the flexibility I need (see &lt;a href="#appendix1whynotdockerhubautomatedbuilds"&gt;Why not Docker Hub Automated Builds&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Essentially the CI service needs to offer the option to have three distinct steps in the pipeline, each of which must pass for process to proceed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build&lt;/li&gt;
&lt;li&gt;Test&lt;/li&gt;
&lt;li&gt;Deploy&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="the-build"&gt;The Build&lt;/h3&gt;
&lt;p&gt;We can build with tools, script files, whatever. At the moment, I am leaning towards &lt;a href="https://www.gnu.org/software/make/"&gt;makefiles&lt;/a&gt;. Normally I only need a few lines of shell script to do a build - anything more complex and the makefile can call a shell script. See also &lt;a href="#appendix2whymakefiles"&gt;Why Makefiles?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what it might look like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;build:
docker build -t dwmkerr/dynamodb:latest .
ifndef BUILD_NUM
$(warning No build number is defined, skipping build number tag.)
else
docker build -t dwmkerr/dynamodb:$(BUILD_NUM) .
endif
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command just builds the &lt;code&gt;Dockerfile&lt;/code&gt; and tags it as &lt;code&gt;dwmkerr/dynamodb:lastest&lt;/code&gt;. If a &lt;code&gt;BUILD_NUM&lt;/code&gt; variable is present, we also create the tag &lt;code&gt;dwmkerr/dynamodb:BUILD_NUM&lt;/code&gt;. This means if we want to deploy to a service such as &lt;a href="https://aws.amazon.com/ecs/"&gt;Amazon ECS&lt;/a&gt; we can push a specific build by referring to the image with that tag.&lt;/p&gt;
&lt;h3 id="the-tests"&gt;The Tests&lt;/h3&gt;
&lt;p&gt;Again I&amp;rsquo;m relying on &lt;code&gt;make&lt;/code&gt;. I just want to be able to run &lt;code&gt;make test&lt;/code&gt; - if zero is returned I&amp;rsquo;m happy. If not, the pipeline should stop and I&amp;rsquo;ll check the output. Here&amp;rsquo;s my test command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test: build
./test/basics.test.sh
./test/ephemeral.test.sh
./test/persistent.test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Not a thing of beauty, but it works. These scripts I&amp;rsquo;ll discuss a little bit later on, in the delightly titled &lt;a href="#appendix3whatarethesetestscripts"&gt;What are these test scripts&lt;/a&gt; section.&lt;/p&gt;
&lt;p&gt;For CircleCI, this is enough to have the main part of our pipeline. Here&amp;rsquo;s how the &lt;code&gt;circle.yml&lt;/code&gt; file looks at this stage:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;machine:
services:
- docker
environment:
# Set the build number, used in makefiles.
BUILD_NUM: $CIRCLE_BUILD_NUM
test:
override:
- make test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(Actually there&amp;rsquo;s a couple of other bits but they&amp;rsquo;re just to make sure circle uses the right version of Docker, &lt;a href="https://github.com/dwmkerr/docker-dynamodb/blob/master/circle.yml"&gt;see the full circle.yml file here&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id="the-deployments"&gt;The Deployments&lt;/h3&gt;
&lt;p&gt;Deployments are trivial as all we need to do is push to the Docker Hub. The &lt;code&gt;make deploy&lt;/code&gt; command looks-a like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;deploy:
docker push dwmkerr/dynamodb:latest
ifndef BUILD_NUM
$(warning No build number is defined, skipping push of build number tag.)
else
docker push dwmkerr/dynamodb:$(BUILD_NUM)
endif
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;re pushing the &lt;code&gt;latest&lt;/code&gt; tag and &lt;code&gt;BUILD_NUM&lt;/code&gt; tag if present. To add this to the CircleCI pipeline, we just add the following to &lt;code&gt;circle.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;deployment:
master:
branch: master
commands:
- docker login -e $DOCKER_EMAIL -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- make deploy
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we have a push to &lt;code&gt;master&lt;/code&gt;, we log in to Docker (using environment variables I configure in the CircleCI UI) and then run &lt;code&gt;make deploy&lt;/code&gt; to push our images.&lt;/p&gt;
&lt;h2 id="thats-it"&gt;That&amp;rsquo;s It&lt;/h2&gt;
&lt;p&gt;That&amp;rsquo;s about it. This is a pretty simple approach, you can see it in action at:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/docker-dynamodb"&gt;github.com/dwmkerr/docker-dynamodb&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The rest of this post is a bit of a deep dive into some specific areas I found interesting.&lt;/p&gt;
&lt;h2 id="appendix-1-why-not-docker-hub-automated-builds"&gt;Appendix 1: Why not Docker Hub Automated Builds?&lt;/h2&gt;
&lt;p&gt;There are automated builds available in the Docker Hub:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/dockerhubbuilds.png" alt="Docker Hub Automated Builds"&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not using this feauture at the moment, here&amp;rsquo;s a brief roundup of what I think are the current pros and cons:&lt;/p&gt;
&lt;p&gt;Pros&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You don&amp;rsquo;t have to goof around installing Docker on a CI platform.&lt;/li&gt;
&lt;li&gt;It allows you to update the description of your Docker image automatically, from the GitHub &lt;code&gt;README.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It allows you to associate the image with a specific GitHub repo (rather than just linking from the image description).&lt;/li&gt;
&lt;li&gt;Branch management - allowing tags to be built for specific branches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cons&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It doesn&amp;rsquo;t &lt;em&gt;seem&lt;/em&gt; to support any kind of configurable gating, such as a running a test command prior to deploying.&lt;/li&gt;
&lt;li&gt;It doesn&amp;rsquo;t &lt;em&gt;seem&lt;/em&gt; to support any kind of triggering of downstream processes, such as updating environments, sending notifications or whatever.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lack of ability to perform tests on the image before deploying it why I&amp;rsquo;m currently not using the service.&lt;/p&gt;
&lt;p&gt;By doing the testing in a CI system for every pull request and only merging PRs where the tests pass we could mitigate the risk here. This service is worth watching as I&amp;rsquo;m sure it will evolve quickly.&lt;/p&gt;
&lt;h2 id="appendix-2-why-makefiles"&gt;Appendix 2: Why Makefiles?&lt;/h2&gt;
&lt;p&gt;I started coding with a commandline compiler in DOS. When I used my first GUI (Borland Turbo C++) it felt like a huge leap:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/turbocpp.png" alt="Borland Turbo C++"&gt;&lt;/p&gt;
&lt;p&gt;Later on I moved onto Microsoft Visual C++ 4.2:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/visualcpp.png" alt="Visual C++ 4.2"&gt;&lt;/p&gt;
&lt;p&gt;And you cannot imagine the excitement when I got my boxed edition of Visual Studio .NET:&lt;/p&gt;
&lt;p&gt;&lt;img src="images/visualstudiodotnet.jpg" alt="Visual Studio .NET"&gt;&lt;/p&gt;
&lt;p&gt;Wow!&lt;/p&gt;
&lt;p&gt;Anyway, I digress. GNU &lt;code&gt;make&lt;/code&gt; was invented by Leonardo Da Vinci in 1473 to allow you to build something from the commandline, using a fairly consistent syntax.&lt;/p&gt;
&lt;p&gt;It is near ubiquitous on *nix systems. I am increasingly using it as an &amp;rsquo;entry point&amp;rsquo; to builds, as I use variety of languages and platforms. Being able to know that most of the time:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;make build
make test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Will build and test something is convenient. Makefiles actually are not that great to work with (see &lt;a href="http://stackoverflow.com/questions/448910/makefile-variable-assignment"&gt;this&lt;/a&gt;, &lt;a href="http://stackoverflow.com/questions/10121182/multiline-bash-commands-in-makefile"&gt;this&lt;/a&gt; and &lt;a href="http://www.conifersystems.com/whitepapers/gnu-make/"&gt;this&lt;/a&gt;). I&amp;rsquo;ve found as long as you keep the commands simple, they&amp;rsquo;re OK. For anything really complex, I normally have a &lt;code&gt;scripts/&lt;/code&gt; folder, but call the scripts &lt;em&gt;from&lt;/em&gt; the makefile, so that there&amp;rsquo;s still a simple entrypoint.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not entirely sold on makefiles, but they tend to be my default at the moment if I know I&amp;rsquo;m going to use the commandline for builds (for example, in Java projects I&amp;rsquo;ll often write a makefile to call Maven or Gradle).&lt;/p&gt;
&lt;p&gt;For things like Node.js, where you have commands like &lt;code&gt;npm test&lt;/code&gt; or &lt;code&gt;npm run xyz&lt;/code&gt; I &lt;em&gt;still&lt;/em&gt; sometimes use makefiles, using &lt;code&gt;npm&lt;/code&gt; for day-to-day dev tests (&lt;code&gt;npm start&lt;/code&gt;) and &lt;code&gt;make&lt;/code&gt; if it&amp;rsquo;s something more complex (e.g. &lt;code&gt;make deploy-sit&lt;/code&gt; to deploy to an SIT environment).&lt;/p&gt;
&lt;h2 id="appendix-3-what-are-these-test-scripts"&gt;Appendix 3: What are these test scripts?&lt;/h2&gt;
&lt;p&gt;You may have noticed:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test: build
./test/basics.test.sh
./test/ephemeral.test.sh
./test/persistent.test.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What&amp;rsquo;s going on here?&lt;/p&gt;
&lt;p&gt;My Docker image is just a wrapper around &lt;a href="http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.html"&gt;Amazon&amp;rsquo;s Local DynamoDB tool&lt;/a&gt;. I don&amp;rsquo;t really need to test that tool. But what I wanted to test was the capabilities which lie at the &lt;em&gt;intersection&lt;/em&gt; between &amp;rsquo;native&amp;rsquo; Docker and &amp;rsquo;native&amp;rsquo; DynamoDB.&lt;/p&gt;
&lt;p&gt;For example, I know Docker supports volume mapping. I know DynamoDB supports using a data directory, to allow persistent between runs. I want to test I can combine Docker volume mapping and the DynamoDB data directory features. I know Docker images should default to being ephemeral, I want to test this holds true by default for my image.&lt;/p&gt;
&lt;p&gt;Testing Docker is a little hard - I want to test that I can run containers, start, stop, check state before and after and so on. This is essentially an integration test, it can be tricky to make it truly isolated and deterministic.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve given it my best go with these scripts. Here&amp;rsquo;s an example for the &amp;rsquo;ephemeral&amp;rsquo; test, where I&amp;rsquo;m trying to assert that if I run a container, create a table, stop the container and run a new one, I no longer have the table. Here&amp;rsquo;s the test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Bomb if anything fails.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;set -e
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Kill any running dynamodb containers.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Cleaning up old containers...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker ps -a | grep dwmkerr/dynamodb | awk &lt;span style="color:#e6db74"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt; | xargs docker rm -f &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Run the container.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Checking we can run the container...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ID&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;docker run -d -p 8000:8000 dwmkerr/dynamodb&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sleep &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Create a table.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;aws dynamodb --endpoint-url http://localhost:8000 --region us-east-1 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; create-table &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --table-name Supervillains &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --attribute-definitions AttributeName&lt;span style="color:#f92672"&gt;=&lt;/span&gt;name,AttributeType&lt;span style="color:#f92672"&gt;=&lt;/span&gt;S &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --key-schema AttributeName&lt;span style="color:#f92672"&gt;=&lt;/span&gt;name,KeyType&lt;span style="color:#f92672"&gt;=&lt;/span&gt;HASH &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --provisioned-throughput ReadCapacityUnits&lt;span style="color:#f92672"&gt;=&lt;/span&gt;1,WriteCapacityUnits&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Clean up the container. On CircleCI the FS is BTRFS, so this might fail...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Stopping and restarting...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker stop $ID &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker rm $ID &lt;span style="color:#f92672"&gt;||&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ID&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;docker run -d -p 8000:8000 dwmkerr/dynamodb&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sleep &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# List the tables - there shouldn&amp;#39;t be any!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;COUNT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;aws dynamodb --endpoint-url http://localhost:8000 --region us-east-1 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; list-tables &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; | jq &lt;span style="color:#e6db74"&gt;&amp;#39;.TableNames | length&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt; $COUNT -ne &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;]&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Expected to find no tables, found &lt;/span&gt;$COUNT&lt;span style="color:#e6db74"&gt;...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s a bit dirty - it removes containers from the host, changes things and so on. But it works.&lt;/p&gt;
&lt;p&gt;I did experiment with running these tests &lt;em&gt;in a container&lt;/em&gt;, which has the benefit of giving you a clean host to start with, which you can throw away after each test.&lt;/p&gt;
&lt;p&gt;I had to give up after a little while due to time constraints, but will probably revisit this process. The benefits of running these integration tests in a container is that we get a degree of isolation from the host.&lt;/p&gt;
&lt;p&gt;If anyone is interested, my attempts so far are on this &lt;a href="https://github.com/dwmkerr/docker-dynamodb/pull/2"&gt;RFC Pull Request&lt;/a&gt;. Feel free to jump in!&lt;/p&gt;</description><category>CodeProject</category></item><item><title>Learn Docker by building a Microservice</title><link>https://dwmkerr.com/learn-docker-by-building-a-microservice/</link><pubDate>Tue, 19 Apr 2016 08:54:39 +0000</pubDate><guid>https://dwmkerr.com/learn-docker-by-building-a-microservice/</guid><description>&lt;p&gt;If you are looking to get your hands dirty and learn all about &lt;a href="https://docker.com"&gt;Docker&lt;/a&gt;, then look no further!&lt;/p&gt;
&lt;p&gt;In this article I&amp;rsquo;m going to show you how Docker works, what all the fuss is about, and how Docker can help with a basic development task - building a microservice.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll use a simple Node.js service with a MySQL backend as an example, going from code running locally to containers running a microservice and database.&lt;/p&gt;
&lt;p align="center"&gt;
&lt;img src="images/Article.png" /&gt;
&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve read the article, you can find the source code here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/dwmkerr/node-docker-microservice"&gt;github.com/dwmkerr/node-docker-microservice&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="what-is-docker"&gt;What is Docker?&lt;/h2&gt;
&lt;p&gt;At its heart, Docker is software which lets you create an &lt;em&gt;image&lt;/em&gt; (which is a lot like a template for a virtual machine) and then run instances of that image in a &lt;em&gt;container&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Docker maintains a vast repository of images, called the &lt;a href="https://hub.docker.com"&gt;Docker Hub&lt;/a&gt; which you can use as starting points or as free storage for your own images. You can install Docker, choose an image you&amp;rsquo;d like to use, then run an instance of it in a container.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to build images, create containers from images and more in this article.&lt;/p&gt;
&lt;h3 id="install-docker"&gt;Install Docker&lt;/h3&gt;
&lt;p&gt;To follow along and use this article, you&amp;rsquo;ll need Docker.&lt;/p&gt;
&lt;p&gt;Check the installation guide for your platform, &lt;a href="https://docs.docker.com/engine/installation/"&gt;docs.docker.com/engine/installation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are on Mac or Windows, consider using a Virtual Machine. I use Parallels on Mac OS X to run an Ubuntu machine for most development activities. Being able to take snapshots, break things and then revert back is very handy when experimenting.&lt;/p&gt;
&lt;h3 id="try-it-out"&gt;Try It Out&lt;/h3&gt;
&lt;p&gt;Enter this command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run -it ubuntu
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a bit of spinning, you&amp;rsquo;ll see a prompt like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@719059da250d:/#
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Try out a few commands and then exit the container:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;root@719059da250d:/# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.4 LTS
Release: 14.04
Codename: trusty
root@719059da250d:/# exit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This doesn&amp;rsquo;t look like much, but a lot has happened!&lt;/p&gt;
&lt;p&gt;What you are seeing is the bash shell of an &lt;em&gt;isolated&lt;/em&gt; container running Ubuntu, on your machine. It&amp;rsquo;s yours to place with - you can install things on it, run software, whatever you want.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a diagram and breakdown of what just happened (the digram is from the &lt;a href="https://docs.docker.com/v1.8/introduction/understanding-docker/"&gt;&amp;lsquo;Understanding the Architecture&amp;rsquo; Docker Documentation&lt;/a&gt;, which is great):&lt;/p&gt;
&lt;p&gt;&lt;img src="images/Flow.png" alt="Docker Run Flow"&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We issue a docker command:&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docker&lt;/code&gt;: run the docker client&lt;/li&gt;
&lt;li&gt;&lt;code&gt;run&lt;/code&gt;: the command to run a new container&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-it&lt;/code&gt;: option to give the container an interactive terminal&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ubuntu&lt;/code&gt;: the image to base the container on&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start="2"&gt;
&lt;li&gt;The docker service running on the host (our machine) checks to see if we have a copy of the requested image locally- which there isn&amp;rsquo;t.&lt;/li&gt;
&lt;li&gt;The docker service checks the public registry (the docker hub) to see if there&amp;rsquo;s an image named &lt;code&gt;ubuntu&lt;/code&gt; available- which there is.&lt;/li&gt;
&lt;li&gt;The docker service downloads the image and stores it in its local cache of images (ready for next time).&lt;/li&gt;
&lt;li&gt;The docker service creates a new container, based on the &lt;code&gt;ubuntu&lt;/code&gt; image.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Try any of these:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run -it haskell
docker run -it java
docker run -it python
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;re not going to use &lt;a href="https://xkcd.com/1312/"&gt;Haskell&lt;/a&gt; today, but you can see, running an environment is very easy.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a snap to build images of our own, with our apps or services on them, databases, whatever we need. We can then run them on any machine with Docker installed - and the image will run in the same, predictable way. We can build our software &lt;em&gt;and the environment it runs on&lt;/em&gt; as code and deploy easily.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s look into a simple microservice as an example.&lt;/p&gt;
&lt;h2 id="the-brief"&gt;The Brief&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;re going to build a microservice which lets us manage a directory of email addresses to phone numbers, using Node.js and MySQL.&lt;/p&gt;
&lt;h2 id="getting-started"&gt;Getting Started&lt;/h2&gt;
&lt;p&gt;For doing local development we&amp;rsquo;ll need to install MySQL and create a test database for us to&amp;hellip;&lt;/p&gt;
&lt;p&gt;&amp;hellip;nope.&lt;/p&gt;
&lt;p&gt;Creating a local database and running scripts on it is an easy start, but can get messy. Lots of uncontrolled stuff going on. It might work, we could even control it with some shell scripts checked in to our repo, but what if other developers already have MySQL installed? What if they have a database already with the creative name &amp;lsquo;users&amp;rsquo; which we want to create?&lt;/p&gt;
&lt;h3 id="step-1-creating-a-test-database-server---in-docker"&gt;Step 1: Creating a Test Database Server - in Docker&lt;/h3&gt;
&lt;p&gt;This is a great Docker use case. We might not want to run our production database in Docker (perhaps we&amp;rsquo;ll just use Amazon RDS for example), but we can spin up a clean MySQL database in no time as a Docker container for development - leaving our development machine clean and keeping everything we do controlled and repeatable.&lt;/p&gt;
&lt;p&gt;Run the following command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run --name db -d -e MYSQL_ROOT_PASSWORD=123 -p 3306:3306 mysql:latest
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This starts a MySQL instance running, allowing access through port 3306 using the root password 123.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;docker run&lt;/code&gt; tells the engine we want to run an image (the image comes at the end, &lt;a href="https://hub.docker.com/_/mysql/"&gt;mysql:vlatest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--name db&lt;/code&gt; names this container &lt;code&gt;db&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&gt; (or &lt;code&gt;--detach&lt;/code&gt;) detach - i.e. run the container in the background.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-e MYSQL_ROOT_PASSWORD=123&lt;/code&gt; (or &lt;code&gt;--env&lt;/code&gt;) environment variables - tells docker we want to provide an environment variable. The variable following it is what the MySQL image checks for setting the default root password.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p 3306:3306&lt;/code&gt; (or &lt;code&gt;--publish&lt;/code&gt; tells the engine that we want to map the port 3306 from inside the container to out port 3306.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The last part is really important - even though that&amp;rsquo;s the MySQL default port, if we don&amp;rsquo;t tell docker explicitly we want to map it, it will block access through that port (because containers are isolated until you tell them you want access).&lt;/p&gt;
&lt;p&gt;The return value of this function is the &lt;em&gt;container id&lt;/em&gt;, a reference to the container which you can use to stop it, restart it, issue commands on it and so on. Let&amp;rsquo;s see which containers are running:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ docker ps
CONTAINER ID IMAGE ... NAMES
36e68b966fd0 mysql:latest ... db
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The key information is the container ID, image and name. Let&amp;rsquo;s connect to this image and see what&amp;rsquo;s there:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ docker exec -it db /bin/bash
root@36e68b966fd0:/# mysql -uroot -p123
mysql&amp;gt; show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
+--------------------+
1 rows in set (0.01 sec)
mysql&amp;gt; exit
Bye
root@36e68b966fd0:/# exit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is pretty clever too:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;docker exec -it db&lt;/code&gt; tells docker we want to execute a command on the container named &lt;code&gt;db&lt;/code&gt; (we could also use the id, or just the first few letters of the id). &lt;code&gt;-it&lt;/code&gt; ensures we have an interactive terminal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mysql -uroot -p123&lt;/code&gt; the command we actually run as a process in the container, which in this case is just the mysql client.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We can create databases, tables, users, whatever we need.&lt;/p&gt;
&lt;h3 id="wrapping-up-the-test-database"&gt;Wrapping up the Test Database&lt;/h3&gt;
&lt;p&gt;Running MySQL inside a container has already introduced a few Docker tricks, but let&amp;rsquo;s pause here and move onto the service. For now, we&amp;rsquo;ll have create a &lt;code&gt;test-database&lt;/code&gt; folder with a script to start the database, stop the database and setup test data:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test-database\setup.sql
test-database\start.sh
test-database\stop.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Start is simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Run the MySQL container, with a database named &amp;#39;users&amp;#39; and credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# for a users-service user which can access it.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Starting DB...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run --name db -d &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e MYSQL_ROOT_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;123&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -e MYSQL_DATABASE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;users -e MYSQL_USER&lt;span style="color:#f92672"&gt;=&lt;/span&gt;users_service -e MYSQL_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;123&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -p 3306:3306 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mysql:latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Wait for the database service to start up.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Waiting for DB to start up...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec db mysqladmin --silent --wait&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; -uusers_service -p123 ping &lt;span style="color:#f92672"&gt;||&lt;/span&gt; exit &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Run the setup script.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#e6db74"&gt;&amp;#34;Setting up initial data...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -i db mysql -uusers_service -p123 users &amp;lt; setup.sql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This script runs the database image in a detached container (i.e. in the background), with a user set up to access a &lt;code&gt;users&lt;/code&gt; database, then waits for the database server to start up, then runs a &lt;code&gt;setup.sql&lt;/code&gt; script to set initial data.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;setup.sql&lt;/code&gt; is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;create&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;table&lt;/span&gt; directory (user_id INT &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt; AUTO_INCREMENT &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;, email TEXT, phone_number TEXT);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; directory (email, phone_number) &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;homer@thesimpsons.com&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;+1 888 123 1111&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; directory (email, phone_number) &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;marge@thesimpsons.com&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;+1 888 123 1112&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; directory (email, phone_number) &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;maggie@thesimpsons.com&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;+1 888 123 1113&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; directory (email, phone_number) &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;lisa@thesimpsons.com&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;+1 888 123 1114&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;insert&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;into&lt;/span&gt; directory (email, phone_number) &lt;span style="color:#66d9ef"&gt;values&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;bart@thesimpsons.com&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;+1 888 123 1115&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;stop.sh&lt;/code&gt; script will stop the container and remove it (containers are left around by docker by default so that they can be restared quickly, we don&amp;rsquo;t really need that feature for this example):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Stop the db and remove the container.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker stop db &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker rm db
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re going to make this even more slick later on, simplifying this further. Check the code at this stage by looking at the &lt;a href="https://github.com/dwmkerr/node-docker-microservice/tree/step1"&gt;step1&lt;/a&gt; branch of the repo.&lt;/p&gt;
&lt;h3 id="step-2-creating-a-microservice-in-nodejs"&gt;Step 2: Creating a Microservice in Node.js&lt;/h3&gt;
&lt;p&gt;This article is really focused on learning Docker, so I&amp;rsquo;m not going to spend ages on the Node.js microservice. Instead, I&amp;rsquo;ll highlight the areas and takeaways.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;test-database/ # contains the code seen in Step 1
users-service/ # root of our node.js microservice
- package.json # dependencies, metadata
- index.js # main entrypoint of the app
- api/ # our apis and api tests
- config/ # config for the app
- repository/ # abstraction over our db
- server/ # server setup code
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s take this apart bit by bit. The first section to look at is &lt;code&gt;repository&lt;/code&gt;. It can be useful to wrap your database access in some kind of class or abstraction, to allow to mock it for testing purposes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// repository.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Exposes a single function - &amp;#39;connect&amp;#39;, which returns
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// a connected repository. Call &amp;#39;disconnect&amp;#39; on this object when you&amp;#39;re done.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;use strict&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mysql&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;mysql&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Class which holds an open connection to a repository
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// and exposes some simple functions for accessing data.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Repository&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;constructor&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;getUsers&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Promise((&lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;SELECT email, phone_number FROM directory&amp;#39;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;results&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;An error occured getting the users: &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;results&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; []).&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;phone_number&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;phone_number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;getUserByEmail&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Promise((&lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Fetch the customer.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;SELECT email, phone_number FROM directory WHERE email = ?&amp;#39;&lt;/span&gt;, [&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;], (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;results&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;An error occured getting the user: &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;results&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;undefined&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;results&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;phone_number&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;results&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;phone_number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;disconnect&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;end&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// One and only exported function, returns a connected repo.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;module&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exports&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;connect&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;connectionSettings&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Promise((&lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;connectionSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;host&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;A host must be specified.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;connectionSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;A user must be specified.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;connectionSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;A password must be specified.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;connectionSettings&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;A port must be specified.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Repository&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;mysql&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;createConnection&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;connectionSettings&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s probably a lot of better ways to do this! But basically we can create a &lt;code&gt;Repository&lt;/code&gt; object like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;connect&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;database&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;users&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;users_service&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;123&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3306&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}).&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;repo&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;repo&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getUsers&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;users&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;users&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;repo&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getUserByEmail&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;homer@thesimpsons.com&amp;#39;&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ...when you are done...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;repo&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;disconnect&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There&amp;rsquo;s also a set of unit tests in the &lt;code&gt;repository/repository.spec.js&lt;/code&gt; file. Now that we&amp;rsquo;ve got a repo, we can create a server. This is &lt;code&gt;server/server.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// server.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;express&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;morgan&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;morgan&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;module&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exports&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;start&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Promise((&lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Make sure we have a repository and port provided.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;A server must be started with a connected repository.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;A server must be started with a port.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Create the app, add some logging.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;morgan&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;dev&amp;#39;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Add the APIs to the app.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;../api/users&amp;#39;&lt;/span&gt;)(&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Start the app, creating a running server which we return.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;server&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;listen&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resolve&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;server&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This module exposes a &lt;code&gt;start&lt;/code&gt; function, which we can use like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;server&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;./server/server);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;server.start({port: 8080, repo: repository}).then((svr) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; // we&amp;#39;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ve&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;got&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;running&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;server&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Notice that &lt;code&gt;server.js&lt;/code&gt; uses &lt;code&gt;api/users/js&lt;/code&gt;? Here it is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// users.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Defines the users api. Add to a server by calling:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// require(&amp;#39;./users&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;use strict&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Only export - adds the API to the app with the given options.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;module&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exports&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/users&amp;#39;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;next&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getUsers&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;users&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;users&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;map&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) =&amp;gt; { &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;phoneNumber&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;phone_number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;next&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/search&amp;#39;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Get the email.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Error(&lt;span style="color:#e6db74"&gt;&amp;#34;When searching for a user, the email must be specified, e.g: &amp;#39;/search?email=homer@thesimpsons.com&amp;#39;.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Get the user from the repo.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getUserByEmail&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;(&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;404&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;User not found.&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;phoneNumber&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;phone_number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;next&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Both of these files have unit tests adjacent to the source.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll need config. Rather than using a specialised library, a simple file will do the trick - &lt;code&gt;config/config.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// config.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Simple application configuration. Extend as needed.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;module&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exports&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;PORT&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8123&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DATABASE_HOST&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;database&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;users&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;users_service&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;123&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3306&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can &lt;code&gt;require&lt;/code&gt; config as needed. Currently, most config is hard coded, but as you can see from &lt;code&gt;port&lt;/code&gt; it&amp;rsquo;s easy to add environment variables as an option.&lt;/p&gt;
&lt;p&gt;Final step - stringing it together with an &lt;code&gt;index.js&lt;/code&gt; file which composes everything:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// index.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Entrypoint to the application. Opens a repository to the MySQL
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// server and starts the server.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;server&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;./server/server&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;./repository/repository&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;./config/config&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Lots of verbose logging when we&amp;#39;re starting up...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;--- Customer Service---&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Connecting to customer repository...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Log unhandled exceptions.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;uncaughtException&amp;#39;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;Unhandled Exception&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;unhandledRejection&amp;#39;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;function&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;promise&lt;/span&gt;){
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;Unhandled Rejection&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;connect&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;host&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;database&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;database&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}).&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;repo&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Connected. Starting server...&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;server&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;start&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}).&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Server started successfully, running on port &amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;port&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;on&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;close&amp;#39;&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;repository&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;disconnect&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We have a little error handling and beyond that we&amp;rsquo;re just loading config, creating a repo and starting our server.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the microservice. It allows us to get all users, or search a user:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;HTTP GET /users # gets all users
HTTP GET /search?email=homer@thesimpons.com # searches by email
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you checkout the code, you&amp;rsquo;ll see that there&amp;rsquo;s a few commands available for you:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd ./users-service
npm install # setup everything
npm test # unit test - no need for a test database running
npm start # run the server - you must have a test database running
npm run debug # run the server in debug mode, opens a browser with the inspector
npm run lint # check to see if the code is beautiful
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Asides from the code you&amp;rsquo;ve seen we have:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Node Inspector for debugging&lt;/li&gt;
&lt;li&gt;Mocha/shoud/supertest for unit tests&lt;/li&gt;
&lt;li&gt;ESLint for linting&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That&amp;rsquo;s it!&lt;/p&gt;
&lt;p&gt;Run the test database with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd test-database/
./start.sh
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then the service with:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd ../users-service/
npm start
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can point your browser to &lt;a href="http://localhost:8123/users"&gt;localhost:8123/users&lt;/a&gt; and see it in action. If you are using Docker Machine (i.e. you&amp;rsquo;re on Mac or Windows) then &lt;code&gt;localhost&lt;/code&gt; won&amp;rsquo;t work, you need the IP of the docker machine instead. You can use &lt;code&gt;docker-machine ip&lt;/code&gt; to get it.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve whipped through building the service quickly. If you&amp;rsquo;d like to see this code before we continue, check the &lt;a href="https://github.com/dwmkerr/node-docker-microservice/tree/step2"&gt;step2&lt;/a&gt; branch.&lt;/p&gt;
&lt;h1 id="step-3-dockerising-our-microservice"&gt;Step 3: Dockerising our Microservice&lt;/h1&gt;
&lt;p&gt;OK now it gets fun!&lt;/p&gt;
&lt;p&gt;So we have a microservice which we can run on a dev box, as long as it has a compatible version of Node.js installed. What we&amp;rsquo;d like to do is set up our service so that we can create a &lt;em&gt;Docker Image&lt;/em&gt; from it, allowing us to deploy our service anywhere which supports docker.&lt;/p&gt;
&lt;p&gt;The way we do this is create a &lt;em&gt;Dockerfile&lt;/em&gt;. A Dockerfile is a recipe that tells the Docker engine how to build your image. We&amp;rsquo;ll create a simple Dockerfile in our &lt;code&gt;users-service&lt;/code&gt; directory and start to explore how we can adapt it to our needs.&lt;/p&gt;
&lt;h2 id="creating-the-dockerfile"&gt;Creating the Dockerfile&lt;/h2&gt;
&lt;p&gt;Create a new text file called &lt;code&gt;Dockerfile&lt;/code&gt; at &lt;code&gt;users-service/&lt;/code&gt; with the content below:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Use Node v4 as the base image.
FROM node:4
# Run node
CMD [&amp;#34;node&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now run the commands below to build the image and run the a container from it:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker build -t node4 . # Builds a new image
docker run -it node4 # Run a container with this image, interactive
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;rsquo;s look at the build command first.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;docker build&lt;/code&gt; tell the engine we want to create a new image.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-t node4&lt;/code&gt; tag this image with the tag &lt;code&gt;node4&lt;/code&gt;. We can refer to this image by tag from now on.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt; use the current directory to find the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After some console output you&amp;rsquo;ll see we have a new image created. You can see all images on your system with &lt;code&gt;docker images&lt;/code&gt;. The next command should be fairly familiar from what we&amp;rsquo;ve done so far:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;docker run&lt;/code&gt; run a new container from an image.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-it&lt;/code&gt; use an interactive terminal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;node4&lt;/code&gt; the tag of the image we want to use in the container.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When we run this image, we get a node repl, check the current version like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;version&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;v4.4.0&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exit&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is potentially different to the node version on your current machine.&lt;/p&gt;
&lt;h2 id="examining-the-dockerfile"&gt;Examining the Dockerfile&lt;/h2&gt;
&lt;p&gt;Looking at the dockerfile we can see quite easily what is going on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;FROM node:4&lt;/code&gt; the first thing we specify in a Dockerfile is the base image. A quick google finds the &lt;a href="https://hub.docker.com/_/node/"&gt;node organisation page on the docker hub&lt;/a&gt; showing all of the available images. This is essentially bare bones ubuntu with node installed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CMD [&amp;quot;node&amp;quot;]&lt;/code&gt; the &lt;code&gt;CMD&lt;/code&gt; command tells docker that this image should run the node executable. When the executable terminates, the container shuts down.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With the addition of a few more commands, we can update our Dockerfile so that it runs our service:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Use Node v4 as the base image.
FROM node:4
# Add everything in the current directory to our image, in the &amp;#39;app&amp;#39; folder.
ADD . /app
# Install dependencies
RUN cd /app; \
npm install --production
# Expose our server port.
EXPOSE 8123
# Run our app.
CMD [&amp;#34;node&amp;#34;, &amp;#34;/app/index.js&amp;#34;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The only addition here is that we use the &lt;code&gt;ADD&lt;/code&gt; command to copy everything&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; in the current directory to a folder in the container called &lt;code&gt;app/&lt;/code&gt; . We then use &lt;code&gt;RUN&lt;/code&gt; to run a command in the image, which installs our modules. Finally, we &lt;code&gt;EXPOSE&lt;/code&gt; the server port, telling docker we intend to support inbound connections on &lt;code&gt;8123&lt;/code&gt;, then run our server code.&lt;/p&gt;
&lt;p&gt;Ensure the test-database service is running, then build and run the image again:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker build -t users-service .
docker run -it -p 8123:8123 users-service
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you navigate to &lt;code&gt;localhost:8123/users&lt;/code&gt; in a browser you should see an error, checking the console shows our container is reporting some issues:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;--- Customer Service---
Connecting to customer repository...
Connected. Starting server...
Server started successfully, running on port 8123.
GET /users 500 23.958 ms - 582
Error: An error occured getting the users: Error: connect ECONNREFUSED 127.0.0.1:3306
at Query._callback (/app/repository/repository.js:21:25)
at Query.Sequence.end (/app/node_modules/mysql/lib/protocol/sequences/Sequence.js:96:24)
at /app/node_modules/mysql/lib/protocol/Protocol.js:399:18
at Array.forEach (native)
at /app/node_modules/mysql/lib/protocol/Protocol.js:398:13
at nextTickCallbackWith0Args (node.js:420:9)
at process._tickCallback (node.js:349:13)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Yikes! So the connection from our &lt;code&gt;users-service&lt;/code&gt; container to the &lt;code&gt;test-database&lt;/code&gt; container is being refused. We might try running &lt;code&gt;docker ps&lt;/code&gt; to see all containers running:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;CONTAINER ID IMAGE PORTS NAMES
a97958850c66 users-service 0.0.0.0:8123-&amp;gt;8123/tcp kickass_perlman
47f91343db01 mysql:latest 0.0.0.0:3306-&amp;gt;3306/tcp db
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;They&amp;rsquo;re both there, so what is going on?&lt;/p&gt;
&lt;h2 id="linking-containers"&gt;Linking Containers&lt;/h2&gt;
&lt;p&gt;The issue we&amp;rsquo;ve seen is actually to be expected. Docker containers are supposed to be isolated, so it wouldn&amp;rsquo;t make much sense if we could create connections between containers without us explicitly allowing it.&lt;/p&gt;
&lt;p&gt;Yes, we can connect from our machine (the host) to a container, because we&amp;rsquo;ve opened ports for that (using the &lt;code&gt;-p 8123:8123&lt;/code&gt; argument for example). If we allowed containers to talk to each other in the same way, then two containers running on the same machine would be able to communicate, even if the developers didn&amp;rsquo;t intend it, and that&amp;rsquo;s a recipe for disaster, especially when we might have a cluster of machines whos job it is to run containers from different applications.&lt;/p&gt;
&lt;p&gt;If we&amp;rsquo;re going to connect from one container to another, we need to &lt;em&gt;link&lt;/em&gt; them, which tells docker that we explicitly want to allow communication between the two. There are two ways of doing this, the first is the &amp;lsquo;old fasioned&amp;rsquo; but quite simple way, the second we&amp;rsquo;ll see a little later.&lt;/p&gt;
&lt;h3 id="linking-containers-with-the-link-parameter"&gt;Linking Containers with the &amp;rsquo;link&amp;rsquo; parameter&lt;/h3&gt;
&lt;p&gt;When we run a container, we can tell docker that we intend to connect to another container using the &lt;code&gt;link&lt;/code&gt; parameter. In our case, we can run our service correctly like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker run -it -p 8123:8123 --link db:db -e DATABASE_HOST=DB users-service
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;&lt;code&gt;docker run -it&lt;/code&gt; run a docker image in a container, with an interactive terminal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p 8123:8123&lt;/code&gt; map the host port 8123 to the container port 8123.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;link db:db&lt;/code&gt; link to the container named &lt;code&gt;db&lt;/code&gt; and refer to it as &lt;code&gt;db&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-e DATABASE_HOST=db&lt;/code&gt; set the &lt;code&gt;DATABASE_HOST&lt;/code&gt; environment variable to &lt;code&gt;db&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;users-service&lt;/code&gt; the name of the image to run in our container.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now when we go to &lt;code&gt;localhost:8123/users&lt;/code&gt; everything works.&lt;/p&gt;
&lt;h4 id="how-it-works"&gt;How it works&lt;/h4&gt;
&lt;p&gt;Remember our config file for the service? It allowed us to specify a database host with an environment variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// config.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Simple application configuration. Extend as needed.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;module&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;exports&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;PORT&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8123&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;db&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;host&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;process&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;DATABASE_HOST&lt;/span&gt; &lt;span style="color:#f92672"&gt;||&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;127.0.0.1&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;database&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;users&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;users_service&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;password&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;123&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3306&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;When we run the container, we set this environment variable to &lt;code&gt;DB&lt;/code&gt;, which means we&amp;rsquo;re connecting to a host called &lt;code&gt;DB&lt;/code&gt;. This is &lt;em&gt;automatically&lt;/em&gt; set up for us by the docker engine when we link to a container.&lt;/p&gt;
&lt;p&gt;To see this in action, try running &lt;code&gt;docker ps&lt;/code&gt; to list all running containers. Look up the name of the container running the &lt;code&gt;users-service&lt;/code&gt;, which will be a random name such as &lt;code&gt;trusting_jang&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker ps
CONTAINER ID IMAGE ... NAMES
ac9449d3d552 users-service ... trusting_jang
47f91343db01 mysql:latest ... db
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we can look at the hosts available on our container:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker exec trusting_jang cat /etc/hosts
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2 db 47f91343db01 # linking magic!!
172.17.0.3 ac9449d3d552
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remember how &lt;code&gt;docker exec&lt;/code&gt; works? Choose a container name and then whatever follows is the command you&amp;rsquo;ll execute on the container, in our case &lt;code&gt;cat /etc/hosts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;OK the hosts file doesn&amp;rsquo;t have the &lt;code&gt;# linking magic!!&lt;/code&gt; comment, that&amp;rsquo;s so you can see - docker has added &lt;code&gt;db&lt;/code&gt; to our hosts file so we can refer to the linked container by hostname. This is one consequence of linking. Here&amp;rsquo;s the other:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker exec trusting_jang printenv | grep DB
DB_PORT=tcp://172.17.0.2:3306
DB_PORT_3306_TCP=tcp://172.17.0.2:3306
DB_PORT_3306_TCP_ADDR=172.17.0.2
DB_PORT_3306_TCP_PORT=3306
DB_PORT_3306_TCP_PROTO=tcp
DB_NAME=/trusting_jang/db
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;From this command we can also see that when docker links a container, it also provides a set of environment variables with some helpful information. We know the host, tcp port and container name.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s step 3 complete - we have a MySQL database running happily in a container, we have a node.js microservice which we can run locally or in a container of its own, and we know how to link them together.&lt;/p&gt;
&lt;p&gt;You can check out how the code looks at this stage by going to the &lt;a href="https://github.com/dwmkerr/node-docker-microservice/tree/step3"&gt;step3&lt;/a&gt; branch.&lt;/p&gt;
&lt;h1 id="step-4-integration-testing-the-environment"&gt;Step 4: Integration Testing the Environment&lt;/h1&gt;
&lt;p&gt;We can now write an integration test which calls the actual server, running as a docker container, calling the containerised test database.&lt;/p&gt;
&lt;p&gt;Writing the integration test can be done in whatever language or on whatever platform you want, within reason, but to keep things simple I&amp;rsquo;m using Node.js as we&amp;rsquo;ve already seen Mocha and Supertest in our project.&lt;/p&gt;
&lt;p&gt;In a new folder, called &lt;code&gt;integration-tests&lt;/code&gt; we&amp;rsquo;ve got a single &lt;code&gt;index.js&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;supertest&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;supertest&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;should&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;should&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;describe&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;users-service&amp;#39;&lt;/span&gt;, () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;api&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;supertest&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;http://localhost:8123&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;it&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;returns a 200 for a known user&amp;#39;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;done&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;api&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/search?email=homer@thesimpsons.com&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;expect&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;done&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will check an API call and show the results of the test&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;As long as your &lt;code&gt;users-service&lt;/code&gt; and &lt;code&gt;test-database&lt;/code&gt; are running, the tests will pass. However, at this stage the services are getting a little harder to handle:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We have to use a shell script to start and stop the database&lt;/li&gt;
&lt;li&gt;We have to remember a sequence of commands to start the users service against the database&lt;/li&gt;
&lt;li&gt;We have to use node directly to run the integration tests&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we&amp;rsquo;re a little more familiar with Docker we can fix these issues.&lt;/p&gt;
&lt;h3 id="simplifiying-the-test-database"&gt;Simplifiying the Test Database&lt;/h3&gt;
&lt;p&gt;Currently we have the following files for the test database:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;/test-database/start.sh
/test-database/stop.sh
/test-database/setup.sql
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now that we&amp;rsquo;re more familar with Docker, we can improve on this. Looking into the &lt;a href="https://hub.docker.com/_/mysql/"&gt;mysql image documentation&lt;/a&gt; on Docker Hub there&amp;rsquo;s a note which tells us any &lt;code&gt;.sql&lt;/code&gt; or &lt;code&gt;.sh&lt;/code&gt; file added to the image&amp;rsquo;s &lt;code&gt;/docker-entrypoint-initdb.d&lt;/code&gt; folder will be executed when setting up the DB.&lt;/p&gt;
&lt;p&gt;This means we can replace our &lt;code&gt;start.sh&lt;/code&gt; and &lt;code&gt;stop.sh&lt;/code&gt; scripts with a &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;FROM mysql:5
ENV MYSQL_ROOT_PASSWORD 123
ENV MYSQL_DATABASE users
ENV MYSQL_USER users_service
ENV MYSQL_PASSWORD 123
ADD setup.sql /docker-entrypoint-initdb.d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now to run our test database it is just:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker build -t test-database .
docker run --name db test-database
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="composing"&gt;Composing&lt;/h3&gt;
&lt;p&gt;Building and running each container is still somewhat time consuming. We can take things a step further with the &lt;a href="https://docs.docker.com/compose/"&gt;Docker Compose&lt;/a&gt; tool.&lt;/p&gt;
&lt;p&gt;Docker Compose lets you create a file which defines each container in your system, the relationships between them, and build or run them all.&lt;/p&gt;
&lt;p&gt;First, &lt;a href="https://docs.docker.com/compose/install/"&gt;install Docker Compose&lt;/a&gt;. Now create a new file in the root of your project called &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;2&amp;#39;
services:
users-service:
build: ./users-service
ports:
- &amp;#34;8123:8123&amp;#34;
depends_on:
- db
environment:
- DATABASE_HOST=db
db:
build: ./test-database
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now check this out:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;docker-compose build
docker-compose up
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Docker Compose has built all of the images needed for our application, created containers fromthem, run them in the correct order and started the whole stack!&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;docker-compose build&lt;/code&gt; command builds each image which is listed in the &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;version: &amp;#39;2&amp;#39;
services:
users-service:
build: ./users-service
ports:
- &amp;#34;8123:8123&amp;#34;
depends_on:
- db
environment:
- DATABASE_HOST=db
db:
build: ./test-database
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; value for each of our services tells docker where to go to find the &lt;code&gt;Dockerfile&lt;/code&gt;. When we run &lt;code&gt;docker-compose up&lt;/code&gt;, docker starts all of our services. Notice from the &lt;code&gt;Dockerfile&lt;/code&gt; we can specify ports and dependencies. Actually, there&amp;rsquo;s a whole bunch of config we can change here.&lt;/p&gt;
&lt;p&gt;In another terminal, run &lt;code&gt;docker compose down&lt;/code&gt; to gracefully shut down the containers.&lt;/p&gt;
&lt;h1 id="winding-up"&gt;Winding Up&lt;/h1&gt;
&lt;p&gt;We&amp;rsquo;ve seen a lot of docker in this article, but there&amp;rsquo;s a lot more to it. I hope this has shown some of the interesting and useful things that you can use docker for in your workflow.&lt;/p&gt;
&lt;p&gt;As usual, questions and comments are welcomed! I&amp;rsquo;d also strongly recommend the document &lt;a href="https://docs.docker.com/engine/understanding-docker/"&gt;Understanding Docker&lt;/a&gt; to get a deeper understanding of how docker works.&lt;/p&gt;
&lt;p&gt;You can see the final source code for the project built in this article at &lt;a href="https://github.com/dwmkerr/node-docker-microservice"&gt;github.com/dwmkerr/node-docker-microservice&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="notes"&gt;Notes&lt;/h1&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Copying everything is actually a bad idea, because we will also copy the node_modules folder. Generally it is a better idea explicitly list the files or folders you want to copy, or use a .dockerignore file, which works just like the .gitignore file.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;If the server isn&amp;rsquo;t running, it will actually show a rather annoying exception, due to a bug in supertest, see &lt;a href="https://github.com/visionmedia/supertest/issues/314"&gt;github.com/visionmedia/supertest/issues/314&lt;/a&gt;.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>CodeProject</category></item></channel></rss>