<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>kipters.dev</title><link>https://kipters.gitlab.io/</link><description>Recent content on kipters.dev</description><generator>Hugo -- 0.156.0</generator><language>en-us</language><copyright>&amp;copy; Fabio Di Peri 2025</copyright><lastBuildDate>Sun, 25 Dec 2022 00:00:00 +0200</lastBuildDate><atom:link href="https://kipters.gitlab.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Setting up Docker on Windows ARM64</title><link>https://kipters.gitlab.io/posts/docker-windows-arm64/</link><pubDate>Sun, 25 Dec 2022 00:00:00 +0200</pubDate><author>Fabio Di Peri</author><guid>https://kipters.gitlab.io/posts/docker-windows-arm64/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>Given the recent push for Windows on ARM64 for developers, with the release of
<del>Project Volterra</del> Windows Dev Kit 2023, one weird missing component was
Docker Desktop.</p>
<p>Kind of annoying, but at the end of the day it&rsquo;s not the only way to have
Docker working on a machine, even on other platforms, and this is what we&rsquo;ll
talk about today, with a mostly-step-by-step guide on how to set everything up.</p>
<p>And speaking of other platform, even if this article is mostly aimed at ARM64,
most of this applies to regular x64 Windows too if you just want to avoid
Docker Desktops&rsquo;s <a href="https://docs.docker.com/subscription/" title="Docker subscription docs">license requirements</a>.</p>
<p>Word of warning first though: the end result will not be 1:1 equivalent to what
Docker Desktop gets you. No easy-to-use UI, everything will be CLI-based and
mostly important it will not be possible to mount directories from the Windows
filesystem as volumes. Both are minor annoyances (and irrelevant to me
personally), but something to note about the volume limitation is that this one
is really a non-issue, since performance would be abysmal anyway.</p>
<p>But that&rsquo;s enough introduction, let&rsquo;s get to the important stuff.</p>
<hr>
<h2 id="the-general-idea">The general idea<a href="#the-general-idea" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>In short, what we&rsquo;ll be doing is to install and run Docker Engine inside a WSL
distro and then use a feature called <a href="https://docs.docker.com/engine/context/working-with-contexts/" title="Docker context docs">Docker Contexts</a> to expose the engine
to the Windows side via HTTPS, so that Windows tools can use it too
(like Visual Studio Code&rsquo;s Remote Development feature).</p>
<h2 id="prerequisites">Prerequisites<a href="#prerequisites" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>WSL 2. That&rsquo;s it. And git.</p>
<p>More specifically, at least version 1.0 which is only distributed via the
Microsoft Store and which is compatible with either Windows 10 or 11.</p>
<p>That means that if you&rsquo;ve installed the old non-Store version, you&rsquo;ll need to
switch to the Store release.</p>
<p>As the Store version is now the default, you can either install it via
<a href="https://www.microsoft.com/store/productId/9P9TQF7MRM4R" title="WSL page on the Store">this store link</a> or by just running <code>wsl --install</code>.</p>
<p><img src="/docker/wsl-store.png" alt="&ldquo;WSL page on the Store&rdquo;"></p>
<p>(By the way, if you haven&rsquo;t <a href="https://www.microsoft.com/store/productId/9N0DX20HK701" title="Windows Terminal page on the Store">installed Windows Terminal</a> or
<a href="https://www.microsoft.com/store/productId/9MZ1SNWT0N5D" title="PowerShell page on the Store">PowerShell 7</a> it&rsquo;s a good time to do so and set them as default).</p>
<p>Of course we&rsquo;ll also need a Linux distribution to run Docker into.
You can pick whatever you prefer, but since Ubuntu 22.04 is my distro of choice,
this is what I will use for the rest of the article, and the only one I&rsquo;ve
tested so far.</p>
<p>Keep in mind though, the only distro that Docker officially supports on arm64
are CentOS, Debian, Fedora and Ubuntu.</p>
<p><img src="/docker/ubuntu-store.png" alt="&ldquo;Ubuntu 22.04 page on the Store&rdquo;"></p>
<hr>
<h2 id="enabling-systemd">Enabling systemd<a href="#enabling-systemd" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>After setting up the distro and configuring it to your liking, it&rsquo;s time to
make a choice: whether to enable <a href="https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl" title="Systemd support is now available in WSL!">systemd</a> or not.</p>
<p>In my case I chose to do so for convenience, but it&rsquo;s not mandatory.
You will just need to manually start <code>dockerd</code> before using Docker.</p>
<p>This step is extremely easy: from WSL, just edit the <code>/etc/wsl.conf</code> file by
adding these two lines:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[boot]</span>
</span></span><span class="line"><span class="cl"><span class="na">systemd</span><span class="o">=</span><span class="s">true</span>
</span></span></code></pre></div><p><img src="/docker/wsl-conf.png" alt="&quot;/etc/wsl.conf open in vi&quot;"></p>
<p>Save, quit your Linux distro, switch back to PowerShell, shut WSL down
running <code>wsl --shutdown</code> and re-launch your distro.</p>
<p>This next boot will take slightly longer, but now commands like <code>systemctl</code> will
work:</p>
<p><img src="/docker/systemctl-list-units.png" alt="&ldquo;Listing service units via systemctl&rdquo;"></p>
<hr>
<h2 id="installing-docker-engine">Installing Docker Engine<a href="#installing-docker-engine" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Now it&rsquo;s time to actually install the engine. For this step you can refer to
the <a href="https://docs.docker.com/engine/install/" title="Docker engine installation docs">official documentation</a> for the most appropriate steps for your distro
of choice.</p>
<p>Since I&rsquo;m extremely lazy, I&rsquo;ll just use the official install script by
committing the cardinal sin of using <code>curl | sh</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -sSL get.docker.com <span class="p">|</span> sh
</span></span></code></pre></div><p><img src="/docker/curl-sh.png" alt="&ldquo;Docker install script&rdquo;"></p>
<p>No, the irony of the install script recommending Docker Desktop is not lost
on me.</p>
<p>Don&rsquo;t forget to add your user to the <code>docker</code> group
(<code>sudo usermod -aG docker $(whoami)</code>)</p>
<p>Great, now we can run Docker locally inside WSL!</p>
<p><img src="/docker/docker-hello-world.png" alt="&ldquo;Docker hello-world image&rdquo;"></p>
<hr>
<h2 id="building-windows-tools">Building Windows tools<a href="#building-windows-tools" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>To use Docker from Windows we&rsquo;ll need two tools: the Docker CLI itself and
docker-compose.</p>
<p>The easiest way to get to get the CLI for windows/arm64 is to just build it
locally, using Docker itself, reducing the dependencies required to zero.</p>
<p>Just clone the <a href="https://github.com/docker/cli" title="Docker CLI repository">repo</a>, <code>cd</code> into it and run one single command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker buildx bake --set binary.platform<span class="o">=</span>windows/arm64
</span></span></code></pre></div><p>(of course, replace arm64 with amd64 if you&rsquo;re on a regular x64 machine).</p>
<p>The build will take a couple minutes at maximum and leave a .exe in the <code>build</code>
directory</p>
<p><img src="/docker/docker-cli-build.png" alt="&ldquo;Cross-building the Docker CLI&rdquo;"></p>
<p>We&rsquo;ll need to move this binary somewhere accessible to Windows. What I like to
do is to create a &ldquo;tools&rdquo; directory somewhere in C:\ and just add this folder
to PATH. In this way, I can just drop any random tool I need there, as if it
was <code>/usr/local/bin</code> or similar in any Linux distro.</p>
<p>Fortunately, we can access the Windows filesystem from inside WSL:</p>
<p><img src="/docker/move-docker.png" alt="&ldquo;Creating the directory and moving the binary inside of it&rdquo;"></p>
<p>Then we can just use the System Properties dialog to edit the PATH with the new
folder</p>
<p><img src="/docker/win-env-vars.png" alt="&ldquo;Editing the PATH variable&rdquo;"></p>
<p>Don&rsquo;t forget to fully close Terminal and reopen it for the changes to apply.</p>
<p><img src="/docker/win-docker-version.png" alt="&ldquo;Checking Docker version on Windows&rdquo;"></p>
<p>Now that the CLI is in our PATH we can start configuring it to connect over to
the engine running in WSL.</p>
<h2 id="a-word-about-security">A word about security<a href="#a-word-about-security" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>At this point we could just setup the engine to expose its API over HTTP in
addition to the usual UNIX socket, configure the Windows side and be done with
it. It would already work.</p>
<p>Enabling HTTP as-is though will mean that everyone on the network will be able
to call the engine running in our distro, and unless you&rsquo;ve enabled rootless
docker that would imply <strong>giving everyone on your network root access to your
WSL distro</strong>, and by extension give them the ability to run any arbitrary piece
of code on the Windows side, by abusing the interop feature.</p>
<p>Not even binding to localhost only is safe, as that would give root access to
any malicious script running in your browser!</p>
<p>Sounds like a bad idea? It is. Big time.</p>
<p>This is why in the next step we&rsquo;ll deal with this aspect of the setup, with
TLS certificates. We&rsquo;ll create a local Certification Authority,
and then use this CA to create two certificates: one for the engine (so that
the CLI can authenticate it) and another client certificate for the CLI itself.</p>
<p>Then we&rsquo;ll configure the engine to only accept connections from clients using
credentials signed by our CA, making the entire setup as secure as possible.</p>
<p>(If you&rsquo;re wondering why we need HTTP at all, contrary to WSL 1 <code>AF_UNIX</code>
sockets <a href="https://github.com/microsoft/WSL/issues/5961" title="Windows/WSL AF_UNIX Interop doesn't work in WSL2">are not supported</a> on WSL2, which gives us no other choice.)</p>
<h2 id="generating-all-certificates">Generating all certificates<a href="#generating-all-certificates" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>This part will be based upon the <a href="https://docs.docker.com/engine/security/protect-access/#use-tls-https-to-protect-the-docker-daemon-socket" title="Official documentation on how to protect the daemon TLS socket">official Docker docs</a>.</p>
<p>We&rsquo;ll use <code>openssl</code>, which should be already installed (at least, it is on
Ubuntu 22.04 I&rsquo;m using).</p>
<h3 id="the-ca">The CA<a href="#the-ca" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>First step is generating the CA key:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl genrsa -aes256 -out ca-key.pem <span class="m">4096</span>
</span></span></code></pre></div><p>and then its certificate</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl req -new -x509 -days <span class="m">365</span> -key ca-key.pem -sha256 -out ca.pem
</span></span></code></pre></div><p>All fields are optional, but make sure to use <code>localhost</code> as FQDN.</p>
<p><img src="/docker/ca-cert.png" alt="&ldquo;Generating the CA key and certificate&rdquo;"></p>
<h3 id="the-server">The Server<a href="#the-server" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>Now that we have a CA, we can generate a key for our server (the Docker daemon)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl genrsa -out server-key.pem <span class="m">4096</span>
</span></span></code></pre></div><p>And then a Certificate Signing Request</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl req -subj <span class="s2">&#34;/CN=localhost&#34;</span> -sha256 -new -key server-key.pem -out server.csr
</span></span></code></pre></div><p>A CSR is basically us asking a CA to cross-sign the public key for the
certificate we just generated ourselves, never sending the private key to
anyone else. In this case the CA is under our control and its key is actually
sitting next to the server key, but it may not always be the case.</p>
<p>To cross-sign the key, we&rsquo;ll need to create a file with this content
(I&rsquo;ll call it <code>extfile.cnf</code> just like the docs do)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">subjectAltName</span> <span class="o">=</span> <span class="s">DNS:localhost</span>
</span></span><span class="line"><span class="cl"><span class="na">extendedKeyUsage</span> <span class="o">=</span> <span class="s">serverAuth</span>
</span></span></code></pre></div><p>And now generate the certificate</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl x509 -req -days <span class="m">365</span> -sha256 -in server.csr<span class="se">\
</span></span></span><span class="line"><span class="cl">  -CA ca.pem -CAkey ca-key.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  -CAcreateserial -out server-cert.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  -extfile extfile.cnf
</span></span></code></pre></div><h3 id="the-client">The Client<a href="#the-client" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>The last piece of the puzzle is the certificate we&rsquo;ll use to authenticate
the Windows-side CLI, the client.</p>
<p>For simplicity we&rsquo;ll perform this step on WSL too and then copy all the
required files over to Windows.</p>
<p>The process is similar to the previous two, first we generate a key</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl genrsa -out client-key.pem <span class="m">4096</span>
</span></span></code></pre></div><p>Then generate a CSR</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl req -subj <span class="s1">&#39;/CN=client&#39;</span> -new -key client-key.pem -out client.csr
</span></span></code></pre></div><p>Then create the configuration file (<code>extfile-client.cnf</code>)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">extendedKeyUsage</span> <span class="o">=</span> <span class="s">clientAuth</span>
</span></span></code></pre></div><p>Last step, generate and sign the certificate itself</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">openssl x509 -req -days <span class="m">365</span> -sha256 -in client.csr -CA ca.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  -CAkey ca-key.pem -CAcreateserial -out client-cert.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  -extfile extfile-client.cnf
</span></span></code></pre></div><h3 id="cleanup">Cleanup<a href="#cleanup" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>Now that we have our keys and certificates for CA, daemon and CLI we can delete
the configuration and CSR files</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">rm extfile.cnf extfile-client.cnf client.csr server.csr
</span></span></code></pre></div><p>To protect the private keys, we&rsquo;ll need to change their permissions</p>
<pre tabindex="0"><code>chmod 0400 ca-key.pem client-key.pem server-key.pem
</code></pre><p>And then update the public keys to be world-readable</p>
<pre tabindex="0"><code>chmod 0444 ca.pem server-cert.pem client-cert.pem
</code></pre><h2 id="configure-docker-engine-to-use-https-with-tls">Configure Docker Engine to use HTTPS with TLS<a href="#configure-docker-engine-to-use-https-with-tls" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>This step is different whether you use Systemd to manage the engine or not.
If not, the only difference from just launching <code>dockerd</code> is that you&rsquo;ll need
to pass a few args with the paths of the certs.</p>
<p>In my case I moved certs and keys to <code>/opt/docker-certs</code> for convenience.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">sudo dockerd <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tlsverify <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tlscacert<span class="o">=</span>/opt/docker-certs/ca.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tlscert<span class="o">=</span>/opt/docker-certs/server-cert.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  --tlskey<span class="o">=</span>/opt/docker-certs/server-key.pem <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H<span class="o">=</span>0.0.0.0:2376 <span class="se">\
</span></span></span><span class="line"><span class="cl">  -H<span class="o">=</span>unix///var/run/docker.sock
</span></span></code></pre></div><p>With systemd we&rsquo;ll need to edit its unit which is usually located at
<code>/lib/systemd/system/docker.service</code> and add the same arguments to the
<code>ExecStart</code> line.</p>
<p><img src="/docker/docker-unit.png" alt="&ldquo;Changing the docker unit to use TLS and HTTP&rdquo;"></p>
<p>run <code>systemctl daemon-reload</code> to reload the unit file,
<code>systemctl restart docker</code> to restart the service and voila, the daemon will be
running with the new settings.</p>
<p><img src="/docker/docker-status.png" alt="&ldquo;Docker service status and logs&rdquo;"></p>
<h2 id="configure-a-docker-context-on-the-windows-side">Configure a Docker Context on the Windows Side<a href="#configure-a-docker-context-on-the-windows-side" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Now it&rsquo;s time to setup the last part, the Windows-side CLI.</p>
<p>First of all, we&rsquo;ll need to copy the CA certificate, the client certificate and
the client key over to the Windows filesystem. This is easy with the interop
feature in WSL</p>
<pre tabindex="0"><code>mkdir /mnt/c/docker-certs
cp ca.pem /mnt/c/docker-certs
cp client-cert.pem /mnt/c/docker-certs
cp client-key.pem /mnt/c/docker-certs
</code></pre><p>Create the context</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker context create wsl --docker <span class="s2">&#34;host=tcp://localhost:2376,ca=c:/docker-certs/ca.pem,cert=c:/docker-certs/client-cert.pem,key=c:/docker-certs/client-key.pem&#34;</span>
</span></span></code></pre></div><p>and finally enable it</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">docker context use wsl
</span></span></code></pre></div><p>And we&rsquo;re done!</p>
<p><img src="/docker/docker-context.png" alt="&ldquo;Docker version on Windows&rdquo;"></p>]]></content></item><item><title>Uniform developer experience using Visual Studio Code Dev Containers</title><link>https://kipters.gitlab.io/posts/vscode-dev-containers/</link><pubDate>Sun, 27 Dec 2020 21:00:00 +0200</pubDate><author>Fabio Di Peri</author><guid>https://kipters.gitlab.io/posts/vscode-dev-containers/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>Local development environments may be simultaneously the best and worst
part of a developer day-to-day life.</p>
<p>It&rsquo;s frustrating when you onboard a project just to discover that there is
absolutely no guidance about how to setup your machine to actually start
working on it. And it&rsquo;s equally frustrating when the documentation exists but
either it&rsquo;s not actively maintained, obsolete or maybe just too specific to
someone else&rsquo;s preferences, leaving you to troubleshoot any issues you&rsquo;ll
encounter while trying to bring everything up.</p>
<p>That&rsquo;s even before everyone&rsquo;s environment starts diverging or even &ldquo;rot&rdquo;.
That&rsquo;s a completely different kettle of fish.</p>
<p>For this reason, containerization technologies have increasingly become a staple
of local environments. Instead of installing a database engine on your machine
to share across different codebases, you can spin up a container just for the
one you&rsquo;re working on.</p>
<p>Need different versions? Just update the image. Oh, we&rsquo;re switching to Postgres?
Just pull the new one. You completely busted something and want to start from
a clean state?</p>
<p><img src="/devcontainers/ohnoanyway.jpg" alt="&ldquo;Oh no. Anyway.&rdquo;"></p>
<p>Just create a new one. What would have been an entire day lost reinstalling your
machine, is now a sub-30 seconds command.</p>
<h2 id="downsides">Downsides<a href="#downsides" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Containers are useful but not perfect. Unless you plan to work entirely in vim inside
the container (or emacs, for people who prefer it), you&rsquo;re still going to need
an editor that is installed locally on your machine and some way to share code
and other data between the host machine for your editor to consume and the
container.</p>
<p>This is, to put it lightly, less than optimal in many cases, file sharing in
Docker for macOS and Windows is notoriously slow. There are ways to alleviate
it but you can&rsquo;t completely work around it, and it&rsquo;s also something that&rsquo;s going
to aggravate the setup steps outlined in your docs, ultimately making it longer,
more error-prone and difficult to maintain.</p>
<p>You will also need other tooling <em>outside</em> of your container, to some degree,
like linters, static analyzers and other things that are going to work within
your editor or IDE to give you the modern developer experience you&rsquo;d expect
today.</p>
<p>And this is where Visual Studio Code&rsquo;s Remote Development extensions come into
the picture.</p>
<h2 id="remote-development-extensions">Remote Development extensions<a href="#remote-development-extensions" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>This is a set of extensions for the editor that let you work with codebases
that are not on your local machine, but rather on different machines.</p>
<p>It might not seem to be something particularly new, tons of editors in the past
have supported something like that, but they often worked by passing files back
and forth between the local and remote machines using tecnologies like FTP or
(hopefully) SFTP.</p>
<p>Visual Studio Code goes one step further: it connects to the machine,
<strong>downloads and runs a headless version of the editor</strong> and then connects to
it.</p>
<p>I also like how it treats other installed extensions: some of them are
installed locally, like themes, others like language servers are installed
remotely, and then there are API that extensions can leverage to work across
both environments.</p>
<p>There are three extensions in the pack, for three different technologies:</p>
<ul>
<li>
<p>One lets you connect to other machines via SSH (and even supports ARMv7 and
Arch64, which means working with headless SBCs like the Raspberry Pi is a
breeze).</p>
</li>
<li>
<p>Another one is for WSL (Windows Subsystem for Linux), and basically does what
it says. This is particularly nice on Windows because it means you can keep
the source code on the Linux side of WSL, avoiding the cross-VM file sharing
performance hit we talked about earlier. And the UI itself renders natively on
the Windows side, avoiding all the issues you would have got by installing
(a separate copy of) Visual Studio Code on Linux and then using a Windows X11
server like VcXsrv or X410 to have it running on screen. A nice touch is that
installing VSCode in Windows automatically adds a <code>code</code> command in WSL, which
will let you run VSCode-on-WSL from a WSL shell like you would do in native
Linux. It&rsquo;s one of the little things that makes the experience less jarring
than it otherwise would have been.</p>
</li>
<li>
<p>But the main star of the show is the Docker-specific extension. Like the other
two, it&rsquo;s pretty straightforward: it spins up a container, copies the
headless server into it, runs it and connects to it. The best part is that
everything required to know including what image to use, what ports to expose, which environment variables to set etc is all defined in a JSON file, which
can then be checked in your source control repository.</p>
</li>
</ul>
<p>Long story short, the documentation for your local environment would then boil
down to:</p>
<ol>
<li>Install Visual Studio Code</li>
<li>Install the Remote Development Extensions Pack</li>
</ol>
<p>Then, when you open a workspace that has been setup you&rsquo;ll get a prompt like
this:</p>
<p><img src="/devcontainers/remoteprompt.png" alt="&ldquo;Folder contains a Dev Container configuration file.&rdquo;"></p>
<p>Click &ldquo;Reopen in Container&rdquo; and a couple of minutes later you&rsquo;ll be off to the
races.</p>
<h2 id="thats-just-for-the-repo-how-about-the-rest">That&rsquo;s just for the repo, how about the rest?<a href="#thats-just-for-the-repo-how-about-the-rest" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Setting up a local development environment is of course more than that, but
since reading about something can be very boring (and I know I am), maybe
it&rsquo;s better to show you how to actually use it.</p>
<p>For this example, we&rsquo;ll start with a .NET 5.0 web application.
Nothing fancy, it&rsquo;s just an app that exposes some REST API, a Swagger UI
frontend, uses Postgres as its main database, a couple of S3 buckets and Redis
for caching.</p>
<p><img src="/devcontainers/sample-1.png" alt=""></p>
<p>This is our starting point. We won&rsquo;t go into the actual app code (also because
I&rsquo;m cheating and the code is just what comes out of scaffolding) but notice
we also have an <code>.editorconfig</code> file there to define a styleguide.</p>
<p>Normally this would require the developer to manually install the EditorConfig
extensions for this to be actually useful, but just keep this in the back of
your mind for now.</p>
<p>Noticed the green button in the lower left corner of the window? That&rsquo;s the sign
at least one of the Remote Development extensions is installed.</p>
<p>The first step will be to create the JSON with all the container settings.
We have two options: it can either be in the root (named <code>.devcontainer.json</code>)
or inside a separate directory (<code>.devcontainer/devcontainer.json</code> in this case).</p>
<p>We&rsquo;ll go for the separate directory route and place this inside the file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;image&#34;</span><span class="p">:</span> <span class="s2">&#34;mcr.microsoft.com/dotnet/sdk:5.0-alpine&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Just the image name for now. Now invoke the Command palette
(F1 or Ctrl/Cmd + Shift + P) and select &ldquo;Remote-Containers: Reopen in container&rdquo;
and wait (you can also find this command clicking on the button in the lower
left corner).</p>
<p>A couple of seconds or minutes later, we&rsquo;ll be greeted by a similar-looking
window, the only noticeable difference will be the &ldquo;Dev Container&rdquo; string
inside the lower left corner button.</p>
<p>But opening any of the .cs file will make this notification appear:</p>
<p><img src="/devcontainers/sample-2.png" alt=""></p>
<p>That&rsquo;s because the headless instance has no extensions installed, you can also
notice it from the left sidebar, which has a lot less icons compared to the
previous screenshot.</p>
<p>Of course, we could install it by clicking on the button and call it a day, but
we would be required to do so every time we rebuild the container.</p>
<p>Kind of defeats the purpose of making things simpler, doesn&rsquo;t it?</p>
<p>Luckily, the JSON files comes to help once again: we can list there the
extensions we want to use, and it will automatically install them at build time.
Since we&rsquo;re using EditorConfig, we can also add its extension there too!</p>
<p>The JSON files now becomes like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;image&#34;</span><span class="p">:</span> <span class="s2">&#34;mcr.microsoft.com/dotnet/sdk:5.0-alpine&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;extensions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ms-dotnettools.csharp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;editorconfig.editorconfig&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You can add any extension there, and you can find the IDs for each one from
its details page in the Extensions section:</p>
<p><img src="/devcontainers/extension-id.png" alt=""></p>
<p>After invoking the &ldquo;Rebuild container&rdquo; command from the Palette and waiting a
couple of minutes we&rsquo;ll see this:</p>
<p><img src="/devcontainers/sample-3.png" alt=""></p>
<p>Notice how the C# Extension is installing its dependencies. That&rsquo;s because the
main extension was already preinstalled during bring-up and we&rsquo;re greeted by its
first-launch dependency installation. Also notice that we&rsquo;re installing the
Linux dependencies even if this screenshot was taken on Windows.</p>
<h2 id="dockerfiles">Dockerfiles<a href="#dockerfiles" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>But we&rsquo;re not done here! Sometimes it might be enough to start from an image
(either a public one or some developer image that&rsquo;s maintained internally), but
it also might be useful to personalize this image.</p>
<p>Of course the extension supports this too, we just need to define a Dockerfile
inside of our <code>.devcontainer</code> directory and change our JSON to use it instead of
the prebuilt image.</p>
<p>Let&rsquo;s suppose we want to install the <code>dotnet-format</code> tool, that automatically
checks and formats your code according to the EditorConfig rules:</p>
<p>What we need is a Dockerfile like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-docker" data-lang="docker"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">mcr.microsoft.com/dotnet/sdk:5.0-alpine</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># This is needed for Global Tools to work</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">ENV</span> <span class="nv">PATH</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">PATH</span><span class="si">}</span><span class="s2">:/root/.dotnet/tools&#34;</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="c"># Install .NET Global Tools</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> dotnet tool install -g dotnet-format<span class="err">
</span></span></span></code></pre></div><p>and also to change our JSON like this (I also threw in the Docker extensions):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;dockerFile&#34;</span><span class="p">:</span> <span class="s2">&#34;Dockerfile&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;extensions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ms-dotnettools.csharp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;editorconfig.editorconfig&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ms-azuretools.vscode-docker&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And now we can:</p>
<ul>
<li>Edit the source code, with all the bells and whistles like IntelliSende, code
navigation, smart refactorings etc.</li>
<li>EditorConfig integration in the editor</li>
<li>Use the CLI to autoformat the code according to the styleguide</li>
</ul>
<p>Of course this can be used to install <strong>anything</strong> you might need.
The JSON also offers some fields where you can define commands to be run at
certain stages, like before/after building the container, after starting it etc.</p>
<p>For example, you could use <code>postCreateCommand</code> to run a script that configures
<a href="https://localstack.cloud/">LocalStack</a> using the same templates you would use
to define your infrastructure on the real AWS, using
<a href="https://github.com/localstack/awscli-local">awscli-local</a>.
In our example, we would use it to create (and maybe seed) the S3 buckets the
app uses, the database etc.</p>
<p>Talking about LocalStack, S3 buckets and databases, our sample app uses all of
them. Are we supposed to install everything inside the container?</p>
<p>Of course not. Docker Compose is supported too!</p>
<p>This makes our JSON slightly more complicated now, since we have to tell the
extension something that in the previous sample was automatically configured by
itself.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;dockerComposeFile&#34;</span><span class="p">:</span> <span class="s2">&#34;docker-compose.yml&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;service&#34;</span><span class="p">:</span> <span class="s2">&#34;devcontainer&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;shutdownAction&#34;</span><span class="p">:</span> <span class="s2">&#34;stopCompose&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;workspaceFolder&#34;</span><span class="p">:</span> <span class="s2">&#34;/workspace&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;extensions&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ms-dotnettools.csharp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;editorconfig.editorconfig&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="s2">&#34;ms-azuretools.vscode-docker&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now the <code>dockerFile</code> field has been replaced by <code>dockerComposeFile</code>, we have to
specify which of the services defined inside the YAML is the one vscode-server
will be launched from, what to do when shutting down and most importantly which
workspace to open since now it will be our responsibility to mount the correct
folder inside the workspace.</p>
<p>(just a sidenote, override files are supported too)</p>
<p>In our case, the <code>docker-compose.yml</code> we&rsquo;ll put inside the <code>.devcontainer</code>
directory will be similar to this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;3&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">devcontainer</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">build</span><span class="p">:</span><span class="w"> </span><span class="l">.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">LOCALSTACK_HOST=localstack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CONNECTIONSTRINGS__REDIS=redis</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">CONNECTIONSTRINGS__POSTGRES=Host=postgres;Username=postgres;Password=MySecretPassword</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">..:/workspace</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./persistence/nuget:/root/.nuget</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/var/run/docker.sock:/var/run/docker.sock</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">command</span><span class="p">:</span><span class="w"> </span><span class="l">sleep infinity</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5000</span><span class="p">:</span><span class="m">5000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5001</span><span class="p">:</span><span class="m">5001</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">localstack</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">localstack/localstack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w"> 
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">SERVICES=s3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">DATA_DIR=/tmp/localstack/data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./persistence/localstack:/tmp/localstack</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">redis</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">redis:5.0.6-alpine</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">postgres</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">postgres:12</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_USER=postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">POSTGRES_PASSWORD=MySecretPassword</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">PGDATA=/pgdata</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="m">5432</span><span class="p">:</span><span class="m">5432</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./persistence/postgres:/pgdata</span><span class="w">
</span></span></span></code></pre></div><p>There is A TON to unpack here.</p>
<p>For starters, we&rsquo;re now defining four containers. One is the &ldquo;devcontainer&rdquo;
we&rsquo;ve worked with until now, but we&rsquo;ll come back to that later.</p>
<p>We&rsquo;ll start with the <code>redis</code> service, which just defines which image to use.
Since all containers are on the same virtual network, it means that if we
installed the <code>redis-tools</code> package inside our main container, we&rsquo;ll be able to
connect to it using the <code>redis</code> hostname.</p>
<p>Then we have <code>postgres</code> which is a little more complicated: in addition to the
image we also define some required environment variables for the default
username and password to use. We also move the PGDATA into a different location,
that&rsquo;s mounted externally in order to maintain persistence of its data.
This way, if we rebuild the container we won&rsquo;t lose all the data in the
database. Its port is exposed externally, so we can use local tools to connect
to it if there&rsquo;s the need.</p>
<p>The same thing is done with LocalStack (you can find more info about its
environment variables in its own documentation), but there too we used an
external volume to avoid losing data upon rebuilds.</p>
<p>And then we have our <code>devcontainer</code>. Of course this one doesn&rsquo;t define an image
to use, rather builds it from our old Dockerfile.</p>
<p>Some of its configuration is required for everything to work, specifically the
first volume that&rsquo;s defined there, its purpose is to mount the real workspace
inside the container, something that up until now was done automatically by the
extension (it&rsquo;s the same path we put in the JSON). More importantly we also
have to override its command, otherwise the container would have exited
immediately after starting. This is an easy way to keep it running indefinitely.</p>
<p>I also added a volume mounting the Docker socket: after doing this we can
just install the Docker tools inside the container, allowing us to interact with
our machine&rsquo;s docker installation. It&rsquo;s pretty useful if you don&rsquo;t want to jump
to a separate terminal when you need to build images for example.</p>
<p>Also notice the environment variables: the first one contains the hostname
for the LocalStack container, which we can use at runtime on our application to
detect whether we should use the real S3 or rather change the service
endpoint URL to the LocalStack one.</p>
<p>The other two instead contain connection strings for Redis and Postgres in the
format .NET Core <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#environment-variables">expects</a>
which means that we can use our YAML to seamlessly
override configuration keys only when running inside a devcontainer.</p>
<h2 id="conclusions">Conclusions<a href="#conclusions" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>If I might seem enthusiastic about this feature it&rsquo;s because I am: it opens
so many scenarios and solves so many problems that it&rsquo;s hard not to use it.</p>
<p>And this is exactly what we&rsquo;re doing internally, authoring standard developer
images and templates to rollout across projects.</p>
<p>We first tested it out in one of our .NET projects while it was still in preview
and we immediately saw its advantages for making onboards easier, and how it
made sharing samples and experiments much easier as part of our tech Fridays.
Or it might make it simpler to setup a lab for an internal workshop.</p>
<p>If you want to learn even more you can head to the <a href="https://code.visualstudio.com/docs/remote/remote-overview">official docs</a>
which also describes more advanced scenarios like Docker over SSH.</p>
<hr>
<p><em>This article also appeared on <a href="https://medium.com/tuimm/uniform-developer-experiences-using-visual-studio-code-dev-containers-6df92a17d44a">TUI Musement&rsquo;s tech blog.</a></em></p>]]></content></item><item><title>UIAppearance and custom properties in Xamarin.iOS</title><link>https://kipters.gitlab.io/posts/ios-custom-uiappearance/</link><pubDate>Wed, 01 Apr 2020 21:00:00 +0200</pubDate><author>Fabio Di Peri</author><guid>https://kipters.gitlab.io/posts/ios-custom-uiappearance/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p><a href="https://developer.apple.com/documentation/uikit/uiappearance">UIAppearance</a>
is a fairly known and used class on iOS. It lets you configure the default
appearance for controls, even custom ones.</p>
<p>Its use is trivial, just assign any property like you would do on the &ldquo;real&rdquo;
control, like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="n">UITextfield</span><span class="p">.</span><span class="n">appearance</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="n">UIColor</span><span class="p">.</span><span class="n">grayColor</span><span class="p">;</span>
</span></span></code></pre></div><p>But what if we have more complex, custom properties?</p>
<p>Objective-C has as an interesting feature called &ldquo;categories&rdquo; that basically
lets you extend classes outside of your control adding methods (and properties),
implementing protocols etc. Swift of course has extensions, but for the purposes
of this post we&rsquo;re going to focus on Objective-C categories.</p>
<p>If you wanted to add a <code>placeholderTextColor</code> property on a UIText field, you
just need to define a category like <a href="https://stackoverflow.com/questions/23373219/is-there-a-way-to-set-the-placeholder-color-of-a-uitextfield-with-uiappearance/58424431#58424431">this</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="k">@interface</span> <span class="nc">UITextField</span> <span class="nl">(Placeholder)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">@property</span> <span class="n">UIColor</span><span class="o">*</span> <span class="n">placeholderTextColor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">@end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">@implementation</span> <span class="nc">UITextField</span> <span class="nl">(Placeholder)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-(</span><span class="n">UIColor</span><span class="o">*</span><span class="p">)</span><span class="nf">placeholderTextColor</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// omitted for brevity
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">-(</span><span class="kt">void</span><span class="p">)</span><span class="nf">setPlaceholderTextColor:</span><span class="p">(</span><span class="n">UIColor</span><span class="o">*</span><span class="p">)</span><span class="nv">placeholderTextColor</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// omitted for brevity
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">@end</span>
</span></span></code></pre></div><p>and then use it as any other property:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="n">UITextField</span> <span class="n">someField</span> <span class="o">=</span> <span class="p">...;</span>
</span></span><span class="line"><span class="cl"><span class="n">someField</span><span class="p">.</span><span class="n">placeholderTextColor</span> <span class="o">=</span> <span class="n">UIColor</span><span class="p">.</span><span class="n">grayColor</span><span class="p">;</span>
</span></span></code></pre></div><p>Objective-C even lets you use it in UIAppearance:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-objc" data-lang="objc"><span class="line"><span class="cl"><span class="n">UITextField</span><span class="p">.</span><span class="n">appearance</span><span class="p">.</span><span class="n">placeholderTextColor</span> <span class="o">=</span> <span class="n">UIColor</span><span class="p">.</span><span class="n">purpleColor</span><span class="p">;</span>
</span></span></code></pre></div><hr>
<p>If all this looks familiar, it&rsquo;s probably because C# has a similar feature
called <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods">Extension Methods</a>
that has a similar purpose, albeit a bit limited compared to the Objective-C
counterpart (you can just add methods).</p>
<h2 id="extending-native-controls-with-extension-methods">Extending native controls with Extension Methods.<a href="#extending-native-controls-with-extension-methods" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Our PlaceholderTextColor sample above would be written like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">UITextFieldPlaceholderTextColorExtensions</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="n">UIColor</span> <span class="n">PlaceholderTextColor</span><span class="p">(</span><span class="k">this</span> <span class="n">UITextField</span> <span class="n">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">attributeName</span> <span class="p">=</span> <span class="n">UIStringAttributeKey</span><span class="p">.</span><span class="n">ForegroundColor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">color</span> <span class="p">=</span> <span class="n">self</span><span class="p">.</span><span class="n">AttributedPlaceholder</span><span class="p">.</span><span class="n">GetAttribute</span><span class="p">(</span><span class="n">attributeName</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="k">out</span> <span class="n">_</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">UIColor</span><span class="p">)</span> <span class="n">color</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">SetPlaceholderTextColor</span><span class="p">(</span><span class="k">this</span> <span class="n">UITextField</span> <span class="n">self</span><span class="p">,</span> <span class="n">UIColor</span> <span class="n">placeholderTextColor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">AttributedPlaceholder</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">Placeholder</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// Neither Placeholder nor AttributedPlaceholder are set</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// nothing to do</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// We&#39;ve got a plain Placeholder, apply the right attribute and set</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// it as AttributedPlaceHolder</span>
</span></span><span class="line"><span class="cl">            <span class="n">self</span><span class="p">.</span><span class="n">AttributedPlaceholder</span> <span class="p">=</span> <span class="k">new</span> <span class="n">NSAttributedString</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">Placeholder</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="k">new</span> <span class="n">NSDictionary</span><span class="p">(</span><span class="n">UIStringAttributeKey</span><span class="p">.</span><span class="n">ForegroundColor</span><span class="p">,</span> <span class="n">placeholderTextColor</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span> <span class="c1">// An AttributedPlaceholder is already set</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">attributedString</span> <span class="p">=</span> <span class="k">new</span> <span class="n">NSMutableAttributedString</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">AttributedPlaceholder</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// When passed a null UIColor we remove the attribute</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">placeholderTextColor</span> <span class="k">is</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">attributedString</span><span class="p">.</span><span class="n">RemoveAttribute</span><span class="p">(</span><span class="n">UIStringAttributeKey</span><span class="p">.</span><span class="n">ForegroundColor</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="k">new</span> <span class="n">NSRange</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">AttributedPlaceholder</span><span class="p">.</span><span class="n">Length</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">attributedString</span><span class="p">.</span><span class="n">SetAttributes</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                    <span class="k">new</span> <span class="n">NSDictionary</span><span class="p">(</span><span class="n">UIStringAttributeKey</span><span class="p">.</span><span class="n">ForegroundColor</span><span class="p">,</span> <span class="n">placeholderTextColor</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">                    <span class="k">new</span> <span class="n">NSRange</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="n">self</span><span class="p">.</span><span class="n">AttributedPlaceholder</span><span class="p">.</span><span class="n">Length</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="n">self</span><span class="p">.</span><span class="n">AttributedPlaceholder</span> <span class="p">=</span> <span class="n">attributedString</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This lets us set a color for the Placeholder text like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">someField</span> <span class="p">=</span> <span class="p">...;</span>
</span></span><span class="line"><span class="cl"><span class="n">someField</span><span class="p">.</span><span class="n">SetPlaceHolderTextColor</span><span class="p">(</span><span class="n">UIColor</span><span class="p">.</span><span class="n">Gray</span><span class="p">);</span>
</span></span></code></pre></div><p>Sorry, no nice property syntax with extension methods (<a href="https://github.com/dotnet/csharplang/issues/192">yet</a>).</p>
<p>Sadly Extension Methods are not (automatically) exposed to Objective-C, and of
course the Xamarin bindings for the UIAppearance-derived classes have no idea
about our extension methods!</p>
<p>But yet, we still want to fully take advantage to UIAppearance, after all one of
the biggest reasons to use Xamarin is that it gives you full access to all the
native features!</p>
<p>Enter the <a href="https://docs.microsoft.com/en-us/dotnet/api/objcruntime.categoryattribute?view=xamarin-ios-sdk-12">CategoryAttribute</a>.</p>
<h1 id="exporting-extension-methods-as-categories">Exporting extension methods as categories<a href="#exporting-extension-methods-as-categories" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>CategoryAttribute lets you export a static class containing extension methods
as a category for an Objective-C class to its runtime, basically letting you
write categories using C#. All we need to do is decorate the class with the
attribute, passing the type of the class we want to extend:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[Category(typeof(UITextField))]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">UITextFieldPlaceholderTextColorExtensions</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Please note that if you use the category only from Objective-C world and never
from managed C# code, it may get removed by the linker! One way to avoid it is
to apply the <a href="https://docs.microsoft.com/en-us/dotnet/api/foundation.preserveattribute?view=xamarin-ios-sdk-12">PreserveAttribute</a>
to the class. It&rsquo;s not our case, but it&rsquo;s something to keep in mind.</p>
<p>Exporting the <em>category</em> is just part of the story though.
We also need to export the two extension methods using the usual
<a href="https://docs.microsoft.com/en-us/dotnet/api/foundation.exportattribute?view=xamarin-ios-sdk-12">ExportAttribute</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[Category(typeof(UITextField))]</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">UITextFieldPlaceholderTextColorExtensions</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="na">    [Export(&#34;placeholderTextColor&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="n">UIColor</span> <span class="n">PlaceholderTextColor</span><span class="p">(</span><span class="k">this</span> <span class="n">UITextField</span> <span class="n">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [Export(&#34;setPlaceholderTextColor:&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">SetPlaceholderTextColor</span><span class="p">(</span><span class="k">this</span> <span class="n">UITextField</span> <span class="n">self</span><span class="p">,</span> <span class="n">UIColor</span> <span class="n">placeholderTextColor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Note the colon at the end of the setter&rsquo;s selector, it means the method accepts
one single parameter (and it&rsquo;s required).</p>
<hr>
<p>This won&rsquo;t change anything from the C# on-the-spot usage point of view, but
makes it possible for UIAppearance to set a default for that property.
The problem is, as we said earlier, UIAppearance bindings have no idea our
extension/category exists, so this code simply won&rsquo;t compile:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">UITextField</span><span class="p">.</span><span class="n">Appearance</span><span class="p">.</span><span class="n">SetPlaceholderTextColor</span><span class="p">(</span><span class="n">UIColor</span><span class="p">.</span><span class="n">Brown</span><span class="p">);</span>
</span></span></code></pre></div><h1 id="wiring-our-extension-to-uiappearance">Wiring our extension to UIAppearance<a href="#wiring-our-extension-to-uiappearance" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>Objective-C is a message-passing-based environment. It&rsquo;s quite dynamic and
classes can dynamically support any property, not unlike C#&rsquo;s Reflection APIs.
The way Objective-C does it is via &ldquo;selectors&rdquo;, which are objects that describe
a message (roughly equivalent to .NET&rsquo;s MethodInfo class).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">selector</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;setPlaceholderTextColor:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">UITextField</span><span class="p">.</span><span class="n">Appearance</span><span class="p">.</span><span class="n">PerformSelector</span><span class="p">(</span><span class="n">selector</span><span class="p">,</span> <span class="n">UIColor</span><span class="p">.</span><span class="n">Brown</span><span class="p">);</span>
</span></span></code></pre></div><p>Note the parameter to the Selector constructor is the same as the one passed in
the setter&rsquo;s Export attribute.</p>
<p>We can use the same approach to call the getter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">selector</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;placeholderTextColor&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">color</span> <span class="p">=</span> <span class="n">UITextField</span><span class="p">.</span><span class="n">Appearance</span><span class="p">.</span><span class="n">PerformSelector</span><span class="p">(</span><span class="n">selector</span><span class="p">)</span> <span class="k">as</span> <span class="n">UIColor</span><span class="p">;</span>
</span></span></code></pre></div><p>Of course having to type a literal string everythime is less than ideal.
One way to encapsulate everything nicely is to write two extension methods for
UITextFieldAppearance:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">readonly</span> <span class="n">Selector</span> <span class="n">PlaceholderTextColorSetter</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;setPlaceholderTextColor:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">SetPlaceholderTextColor</span><span class="p">(</span><span class="k">this</span> <span class="n">UITextField</span><span class="p">.</span><span class="n">UITextFieldAppearance</span> <span class="n">self</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">UIColor</span> <span class="n">placeholderTextColor</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">self</span><span class="p">.</span><span class="n">PerformSelector</span><span class="p">(</span><span class="n">PlaceholderTextColorSetter</span><span class="p">,</span> <span class="n">placeholderTextColor</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><hr>
<h1 id="putting-it-all-together">Putting it all together<a href="#putting-it-all-together" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h1>
<p>In <a href="https://gist.github.com/kipters/c5c7087954e406e3530237747fd2b67a">this Gist</a>
you can find the complete code for the category and the extensions, hope you&rsquo;ll
find it useful!</p>]]></content></item><item><title>UIDebuggingInformationOverlay and Xamarin</title><link>https://kipters.gitlab.io/posts/ios-debugging-overlay/</link><pubDate>Fri, 26 May 2017 21:20:22 +0100</pubDate><author>Fabio Di Peri</author><guid>https://kipters.gitlab.io/posts/ios-debugging-overlay/</guid><description>&amp;lt;no value&amp;gt;</description><content type="text/html" mode="escaped"><![CDATA[<p>Earlier today I was browsing <a href="http://news.ycombinator.com">Hacker News</a> as I usually do
when <del>I&rsquo;m bored</del> I want to learn something cool and I stumbled across
<a href="http://ryanipete.com/blog/ios/swift/objective-c/uidebugginginformationoverlay/">this post</a>
by Ryan Peterson about a private iOS API called <code>UIDebuggingInformationOverlay</code>.</p>
<p>As Ryan puts it:</p>
<blockquote>
<p><code>UIDebuggingInformationOverlay</code> is a private <code>UIWindow</code> subclass created by
Apple, presumably to help developers and designers debug Apple’s own iOS apps.</p>
</blockquote>
<p>The post also has code for using it in your Swift apps, but what if you&rsquo;d want
to use it in your Xamarin apps?</p>
<p>Turns out it&rsquo;s quite easy, let&rsquo;s see what Ryan&rsquo;s code does step-by-step:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">overlayClass</span> <span class="p">=</span> <span class="n">NSClassFromString</span><span class="p">(</span><span class="s">&#34;UIDebuggingInformationOverlay&#34;</span><span class="p">)</span> <span class="k">as</span><span class="p">?</span> <span class="n">UIWindow</span><span class="p">.</span><span class="kr">Type</span>
</span></span></code></pre></div><p>This gets us the <code>UIDebuggingInformationOverlay</code>&rsquo;s <code>class</code> object, the
equivalent of a <code>Type</code> object in C#.</p>
<p>Next, he proceeds calling a static method, <code>prepareDebuggingOverlay</code>, using a
selector:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kc">_</span> <span class="p">=</span> <span class="n">overlayClass</span><span class="p">?.</span><span class="n">perform</span><span class="p">(</span><span class="n">NSSelectorFromString</span><span class="p">(</span><span class="s">&#34;prepareDebuggingOverlay&#34;</span><span class="p">))</span>
</span></span></code></pre></div><p>We need to call this method, otherwise the overlay will be empty. Now we can
just get a reference to the overlay itself, calling the <code>overlay</code> static method:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kd">let</span> <span class="nv">overlay</span> <span class="p">=</span> <span class="n">overlayClass</span><span class="p">?.</span><span class="n">perform</span><span class="p">(</span><span class="n">NSSelectorFromString</span><span class="p">(</span><span class="s">&#34;overlay&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                           <span class="p">.</span><span class="n">takeUnretainedValue</span><span class="p">()</span> <span class="k">as</span><span class="p">?</span> <span class="n">UIWindow</span>
</span></span></code></pre></div><p>At this point showing the overlay is as simple as calling <code>toggleVisibility</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-Swift" data-lang="Swift"><span class="line"><span class="cl"><span class="kc">_</span> <span class="p">=</span> <span class="n">overlay</span><span class="p">?.</span><span class="n">perform</span><span class="p">(</span><span class="n">NSSelectorFromString</span><span class="p">(</span><span class="s">&#34;toggleVisibility&#34;</span><span class="p">))</span>
</span></span></code></pre></div><p>And that&rsquo;s it!</p>
<h2 id="its-c-time">It&rsquo;s C# time<a href="#its-c-time" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>Now, let&rsquo;s do it with Xamarin!</p>
<p>The first step is to get an handle to the native Objective-C <code>class</code>.
Luckily, Xamarin provides a method just for that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">overlayClass</span> <span class="p">=</span> <span class="n">Class</span><span class="p">.</span><span class="n">GetHandle</span><span class="p">(</span><span class="s">&#34;UIDebuggingInformationOverlay&#34;</span><span class="p">);</span>
</span></span></code></pre></div><p><code>overlayClass</code> is a <code>IntPtr</code> object since it&rsquo;s a pointer to an underlying
native object.</p>
<p>Now we need to call <code>prepareDebuggingOverlay</code>, but how? We have just an
opaque pointer!</p>
<h3 id="dllimport-to-the-rescue">DllImport to the rescue<a href="#dllimport-to-the-rescue" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h3>
<p>We must do it like how the Objective-C runtime does: we must send a message to
the object, using the native <code>objc_msgSend</code> function. Since that&rsquo;s a C
function we can just use P/Invoke.</p>
<p>We don&rsquo;t need any arguments, so we can import the most basic version of it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="na">[DllImport(Constants.ObjectiveCLibrary, EntryPoint = &#34;objc_msgSend&#34;)]</span>
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="kd">extern</span> <span class="n">IntPtr</span> <span class="n">objc_msgSend</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">target</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">selector</span><span class="p">);</span>
</span></span></code></pre></div><p>and then just call it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">objc_msgSend</span><span class="p">(</span><span class="n">overlayClass</span><span class="p">,</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;prepareDebuggingOverlay&#34;</span><span class="p">).</span><span class="n">Handle</span><span class="p">);</span>
</span></span></code></pre></div><p>we can now reuse this function for the next step, getting a reference to the
overlay:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">overlay</span> <span class="p">=</span> <span class="n">objc_msgSend</span><span class="p">(</span><span class="n">overlayClass</span><span class="p">,</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;overlay&#34;</span><span class="p">).</span><span class="n">Handle</span><span class="p">);</span>
</span></span></code></pre></div><p>and also for toggling it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">objc_msgSend</span><span class="p">(</span><span class="n">ovrlay</span><span class="p">,</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;toggleVisibility&#34;</span><span class="p">).</span><span class="n">Handle</span><span class="p">);</span>
</span></span></code></pre></div><p>And we&rsquo;re done! We should now see the debugging overlay over our app!</p>
<h2 id="lets-clean-it-up">Let&rsquo;s clean it up<a href="#lets-clean-it-up" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>I like my code to be well contained within its own class, I wouldn&rsquo;t use this
code as is, so I encapsulated all of it in a simple wrapper:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">System.Runtime.InteropServices</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="nn">ObjCRuntime</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nn">debugoverlay</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">class</span> <span class="nc">UIDebuggingInformationOverlay</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="na">        [DllImport(Constants.ObjectiveCLibrary, EntryPoint = &#34;objc_msgSend&#34;)]</span>
</span></span><span class="line"><span class="cl">        <span class="kd">static</span> <span class="kd">extern</span> <span class="n">IntPtr</span> <span class="n">objc_msgSend</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">target</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">selector</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">static</span> <span class="n">IntPtr</span> <span class="n">_overlayClass</span> <span class="p">=</span> <span class="n">Class</span><span class="p">.</span><span class="n">GetHandle</span><span class="p">(</span><span class="s">&#34;UIDebuggingInformationOverlay&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kd">static</span> <span class="n">Selector</span> <span class="n">_prepareSelector</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;prepareDebuggingOverlay&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kd">static</span> <span class="n">Selector</span> <span class="n">_overlaySelector</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;overlay&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kd">static</span> <span class="n">Selector</span> <span class="n">_toggleSelector</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Selector</span><span class="p">(</span><span class="s">&#34;toggleVisibility&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">IntPtr</span> <span class="n">_overlay</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">PrepareDebuggingOverlay</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">objc_msgSend</span><span class="p">(</span><span class="n">_overlayClass</span><span class="p">,</span> <span class="n">_prepareSelector</span><span class="p">.</span><span class="n">Handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="n">UIDebuggingInformationOverlay</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">_overlay</span> <span class="p">=</span> <span class="n">objc_msgSend</span><span class="p">(</span><span class="n">_overlayClass</span><span class="p">,</span> <span class="n">_overlaySelector</span><span class="p">.</span><span class="n">Handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="k">void</span> <span class="n">ToggleVisibility</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">objc_msgSend</span><span class="p">(</span><span class="n">_overlay</span><span class="p">,</span> <span class="n">_toggleSelector</span><span class="p">.</span><span class="n">Handle</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It may need another bit of polishing (like making it a proper NSObject binding),
but for debugging purposes it&rsquo;s good enough. Feel free to use it!</p>
<h2 id="wrap-up">Wrap-up<a href="#wrap-up" class="anchor" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
      stroke-linecap="round" stroke-linejoin="round" class="feather">
      <path d="M15 7h3a5 5 0 0 1 5 5 5 5 0 0 1-5 5h-3m-6 0H6a5 5 0 0 1-5-5 5 5 0 0 1 5-5h3"></path>
      <line x1="8" y1="12" x2="16" y2="12"></line>
   </svg></a></h2>
<p>I&rsquo;ll try to come up with a better version of the wrapper just for the sake of
learning a bit more about C#/Objective-C bindings, if you have some advice feel
free to reach out!</p>
<hr>
<p><strong>UPDATE 7 July 2017:</strong> The binding code is now on <a href="https://github.com/kipters/xamarin-uidebugginginformationoverlay">GitHub</a> and on <a href="https://www.nuget.org/packages/Kipware.UIDebuggingInformationOverlay/">NuGet</a>.</p>]]></content></item></channel></rss>