<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Ci-Cd on Haseeb Majid</title>
    <link>https://haseebmajid.dev/tags/ci-cd/</link>
    <description>Recent content in Ci-Cd on Haseeb Majid</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <lastBuildDate>Wed, 21 Dec 2022 00:00:00 +0000</lastBuildDate><atom:link href="https://haseebmajid.dev/tags/ci-cd/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>TIL: How to Use DinD, localhost &amp; Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2022-12-21-til-how-to-use-dind-localhost-gitlab-ci/</link>
      <pubDate>Wed, 21 Dec 2022 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2022-12-21-til-how-to-use-dind-localhost-gitlab-ci/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: How to Use DinD, localhost &amp;amp; Gitlab CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this post, I will go over how you can use docker-compose and Gitlab CI.
In this example, we will be running playwright tests directly on the Gitlab runner.
The tests will start a SvelteKit server also running on the Gitlab runner. The SvelteKit
server will connect to PocketBase (backend) running in docker-compose.&lt;/p&gt;
&lt;p&gt;So essentially we need a way for something running locally to connect to something running in
docker in Gitlab CI (on a runner). This is a pattern I am using in my new app &lt;a href=&#34;https://bookmarkey.app&#34;&gt;Bookmarkey&lt;/a&gt; &lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: How to Use DinD, localhost &amp; Gitlab CI</strong></p>
<p>In this post, I will go over how you can use docker-compose and Gitlab CI.
In this example, we will be running playwright tests directly on the Gitlab runner.
The tests will start a SvelteKit server also running on the Gitlab runner. The SvelteKit
server will connect to PocketBase (backend) running in docker-compose.</p>
<p>So essentially we need a way for something running locally to connect to something running in
docker in Gitlab CI (on a runner). This is a pattern I am using in my new app <a href="https://bookmarkey.app">Bookmarkey</a> <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Let&rsquo;s pretend we have a <code>docker-compose.yml</code> file which looks something like 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">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">pocketbase</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">ghcr.io/muchobien/pocketbase:latest</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="s1">&#39;9090:8090&#39;</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">./pb_data:/pb_data</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/pb_public</span><span class="w">
</span></span></span></code></pre></div><p>and our <code>package.json</code> scripts section looks 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;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="s2">&#34;playwright test&#34;</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></code></pre></div><p>Finally the most important file let&rsquo;s look at the gitlab ci file <code>.gitlab-ci.yml</code>:</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">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tests:e2e</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</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">mcr.microsoft.com/playwright:v1.29.0-jammy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><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="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">VITE_POCKET_BASE_URL</span><span class="p">:</span><span class="w"> </span><span class="s1">&#39;http://docker:9090&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="c"># ... Installing docker and docker compose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose up -d</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npm run test</span><span class="w">
</span></span></span></code></pre></div><p>Since all Gitlab CI jobs run in Docker. If we want to run docker-compose inside a job we need to use the dind service <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
We can then use docker normally i.e. using <code>docker compose</code> to start PocketBase. The most important line here is normally
to connect to PocketBase I would use <code>http://localhost:8090</code>. However, since we are using the <code>dind</code> service we need to use
<code>docker</code> instead of <code>localhost</code>. Hence this <code>VITE_POCKET_BASE_URL: 'http://docker:9090'</code>, passed to my SvelteKit app.</p>
<p>I spent about two days debugging this issue, even though I&rsquo;d solved this problem before 🤦‍♂️. So I decided to make a quick
post so hopefully you can avoid wasting your time.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gitlab.com/banter-bus/bookmarkey/gui/-/blob/e575e5a97feb70227fd6aae366ce4fc9beacafe2/.gitlab-ci.yml">https://gitlab.com/banter-bus/bookmarkey/gui/-/blob/e575e5a97feb70227fd6aae366ce4fc9beacafe2/.gitlab-ci.yml</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Read more about <a href="/posts/20-05-01-how-to-use-dind-with-gitlab-ci/">dind here</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    
    <item>
      <title>TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/</link>
      <pubDate>Wed, 23 Nov 2022 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/</guid>
      <description>&lt;p&gt;&lt;strong&gt;TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This post in fact will be a scheduled post I&amp;rsquo;m writing it on the 20th of November but it will be published on the 23rd of November.
Assuming we are using Netlify to deploy our website, what we will need to do is trigger a rebuild of our site every day using Gitlab CI.
Since Hugo builds a static site to publish our content after a set date we will need to rebuild our site. So let&amp;rsquo;s see how we can automate
that. Else the post won&amp;rsquo;t be visible until I next make a commit to my main branch.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><strong>TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI</strong></p>
<p>This post in fact will be a scheduled post I&rsquo;m writing it on the 20th of November but it will be published on the 23rd of November.
Assuming we are using Netlify to deploy our website, what we will need to do is trigger a rebuild of our site every day using Gitlab CI.
Since Hugo builds a static site to publish our content after a set date we will need to rebuild our site. So let&rsquo;s see how we can automate
that. Else the post won&rsquo;t be visible until I next make a commit to my main branch.</p>
<p>I use the <code>date</code> field to determine when to make this post visible on my blog, set in the front matter of the post (markdown).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;TIL: You Can Schedule Posts on Your Hugo Blog using Netlify and Gitlab CI&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">canonicalURL</span><span class="p">:</span><span class="w"> </span><span class="l">https://haseebmajid.dev/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">date</span><span class="p">:</span><span class="w"> </span><span class="ld">2022-11-23</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span></code></pre></div><p>We can do this by first creating a build hook on Netlify, go to your <code>Site Settings</code> (on Netlify) then <code>Build and Deploy</code>. Copy the build hook
cURL request example which will look like <code>curl -X POST -d {} https://api.netlify.com/build_hooks/[id]</code>.</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/images/netlify_build_hook.png"
        type=""
        alt="Netlify Build Hook"
        
      /></p>
<p>Next, go to Gitlab and open your <code>Settings &gt; CI/CD</code> settings and add a new variable called <code>NETLIFY_DEPLOY_WEBHOOK</code> and copy your build hook URL
<code>https://api.netlify.com/build_hooks/[id]</code>. Mark the variable as protected.</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/images/gitlab_ci_variables.png"
        type=""
        alt="Gitlab CI Variables"
        
      /></p>
<p>Then update our <code>.gitlab-ci.yml</code> with the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">deploy_site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</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">curl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">GIT_STRATEGY</span><span class="p">:</span><span class="w"> </span><span class="l">none</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">curl -X POST -d &#39;{}&#39; $NETLIFY_BUILD_WEBHOOK</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">schedules</span><span class="w">
</span></span></span></code></pre></div><p>This job will run on a schedule, i.e. once a day, that will trigger the build hook and cause Netlify to rebuild our site.
This means any posts due to be published that day will be visible on our site.</p>
<p>Finally, let&rsquo;s schedule our CI job, go to <code>CI/CD &gt; Schedules</code>, and then create a new schedule. Set the <a href="https://crontab.guru">cron</a> to whatever
you want. My pipeline schedule is set as so:</p>
<p><img
        loading="lazy"
        src="/posts/2022-11-23-til-you-can-schedule-posts-on-your-hugo-blog-using-netlify-and-gitlab-ci/images/gitlab_ci_schedule.png"
        type=""
        alt="Gitlab CI Schedule"
        
      /></p>
<p>This will cause our website to build once a day, triggering a build on Netlify. So when the Gitlab CI job runs at midnight on the 23rd of November
this post will be visible to you all.</p>
<p>You can test your website using the <code>--buildFuture</code> flag i.e. <code>hugo server -D --buildFuture</code> when testing your site locally.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://hamy.xyz/labs/hugo-schedule-rebuilds-on-netlify">Inspired by</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Running Gitlab CI jobs in Docker using docker-compose</title>
      <link>https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</link>
      <pubDate>Mon, 08 Aug 2022 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2022-08-08-running-gitlab-ci-jobs-in-docker-using-docker-compose/</guid>
      <description>&lt;p&gt;Shameless plug: This is related to a EuroPython 2022 talk I am giving, &lt;a href=&#34;../../talks/my-journey-using-docker-as-a-developer-tool&#34;&gt;My Journey Using Docker as a Development Tool&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For most of my common dev tasks, I&amp;rsquo;ve started to rely on &lt;code&gt;docker&lt;/code&gt;/&lt;code&gt;docker compose&lt;/code&gt; to run commands locally. I have also
started using vscode&amp;rsquo;s &lt;code&gt;.devcontainers&lt;/code&gt;, to provide a consistent environment for all developers using a project.&lt;/p&gt;
&lt;p&gt;The main reason for this is to avoid needing to install dependencies on my host machine. In theory, all I
should need is a Docker daemon and a CLI (docker CLI) to interact with that Daemon. This also makes it
far easier for any new developer to start working on my project and get set up.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Shameless plug: This is related to a EuroPython 2022 talk I am giving, <a href="/talks/my-journey-using-docker-as-a-developer-tool">My Journey Using Docker as a Development Tool</a>.</p>
<p>For most of my common dev tasks, I&rsquo;ve started to rely on <code>docker</code>/<code>docker compose</code> to run commands locally. I have also
started using vscode&rsquo;s <code>.devcontainers</code>, to provide a consistent environment for all developers using a project.</p>
<p>The main reason for this is to avoid needing to install dependencies on my host machine. In theory, all I
should need is a Docker daemon and a CLI (docker CLI) to interact with that Daemon. This also makes it
far easier for any new developer to start working on my project and get set up.</p>
<p>What inspired me to do this change now (in my banter bus project) was I wanted to upgrade to
python 3:10 to use some of the new typing features released. However when I tried to upgrade my CI pipeline
started failing, after hours of trying to debug it. I ended up using Docker and everything ran smoothly.</p>
<p>Now to have a more consistent environment between my local environment and CI. So in theory, it means
less chance of something passing locally but failing in CI.</p>
<p>Now we know why we want to do it. let&rsquo;s look at how we do it.</p>
<h2 id="before">Before</h2>
<p>Let&rsquo;s take a look at what a typical CI pipeline may look for a Python project (using banter bus).
In this example, we will be using a FastAPI web service which uses Poetry to manage its dependencies.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">python:3.10.5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">PIP_CACHE_DIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${CI_PROJECT_DIR}/.cache/pip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">PIP_DOWNLOAD_DIR</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;.pip&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;${CI_JOB_NAME}&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.cache/pip</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.venv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">.test</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><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">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pip download --dest=${PIP_DOWNLOAD_DIR} poetry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pip install --find-links=${PIP_DOWNLOAD_DIR} poetry</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">poetry config virtualenvs.in-project true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">poetry install -vv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pre-commit run --all-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/unit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extends</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>The above looks quite complicated, but very simply we install our dependencies for each job the <code>before_script</code> section is used in all jobs.
All jobs also use <code>python:3.9.8</code> image, this is where our code is cloned into the CI pipeline.</p>
<p>Where our <code>.pre-commit-config.yaml</code> looks something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">repos</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/pre-commit/pre-commit-hooks</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">rev</span><span class="p">:</span><span class="w"> </span><span class="l">v3.3.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">check-yaml</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">&#34;--allow-multiple-documents&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">local</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">forbidden-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">forbidden files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">found copier update rejection files; review them and remove them</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">fail</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">files</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;\\.rej$&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run black</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run flake8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">isort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">isort</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run isort --settings-path=.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run pyupgrade</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">args</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>--<span class="l">py310-plus]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">description</span><span class="p">:</span><span class="w"> </span><span class="l">Check python types.</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">entry</span><span class="p">:</span><span class="w"> </span><span class="l">poetry run mypy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">system</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">types</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">python]</span><span class="w">
</span></span></span></code></pre></div><p><code>pre-commit</code> is a library we can use to add pre-commit hooks before we commit our code to git. Adding some checks that
the code is consistent with the rules we defined. We can also just use it as a lint job, multiple linting tools together. Simplified. Hence
here we are checking for code formatting, linting, import sorting etc. The details don&rsquo;t matter but at the moment we need to have
a virtualenv locally to run this.</p>
<h3 id="integration-tests">Integration Tests</h3>
<p>A slightly more interesting job is integration tests, it requires other docker containers, as our tests need Postgres and Redis to run.
We can define these as services and then reference them in our job like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">extends</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">.test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>Note the <code>extends</code> clause, which essentially merges the <code>.test</code> section with our job so it will look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:integration-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><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">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">redis:6.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  	</span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>We also need to define a bunch of environment variables in this case so our containers can communicate
with each other. Now, these are of course specific to my apps. But you can imagine a real-life project also
needing a bunch of environment variables. As you can see this can get a bit messy and what is
running locally may differ slightly from what is running in CI.</p>
<p>I have been caught out by these env variables in the past. Note variables like
<code>BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api</code>. The name of the
container must match the URL we have provided</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span></code></pre></div><p>Docker DNS (link to DNS) is clever enough to work out the IP address.
This is also different now to how we are running it locally.</p>
<h2 id="after">After</h2>
<p>Now we are running all our dev tasks in docker. We will use docker-compose to manage all of the containers,
docker-compose makes managing multiple containers a lot easier. We define all of them in our
<code>docker-compose.yml</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><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">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-core-api</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></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">context</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">dockerfile</span><span class="p">:</span><span class="w"> </span><span class="l">Dockerfile</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">target</span><span class="p">:</span><span class="w"> </span><span class="l">development</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">cache_from</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">registry.gitlab.com/banter-bus/banter-bus-core-api:development</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="nt">XDG_DATA_HOME</span><span class="p">:</span><span class="w"> </span><span class="l">/commandhistory/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_CORE_API_MESSAGE_QUEUE_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">6379</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">127.0.0.1</span><span class="p">:</span><span class="m">8080</span><span class="p">:</span><span class="m">8080</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">./:/app</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">/app/.venv/</span><span class="w"> </span><span class="c"># This stops local .venv getting mounted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">message-queue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">management-api</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</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">registry.gitlab.com/banter-bus/banter-bus-management-api:test</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="nt">BANTER_BUS_MANAGEMENT_API_DB_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_DB_NAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter_bus_management_api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_WEB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_CLIENT_ID</span><span class="p">:</span><span class="w"> </span><span class="l">client_id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">BANTER_BUS_MANAGEMENT_API_USE_AUTH</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;False&#34;</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">127.0.0.1</span><span class="p">:</span><span class="m">8090</span><span class="p">:</span><span class="m">8090</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</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">mongo:4.4.4</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="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</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">/data/db</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">27017</span><span class="p">:</span><span class="m">27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database-gui</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-gui</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">mongoclient/mongoclient:4.0.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</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">MONGOCLIENT_DEFAULT_CONNECTION_URL=mongodb://banterbus:banterbus@banter-bus-database:27017</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">/data/db mongoclient/mongoclient</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">127.0.0.1</span><span class="p">:</span><span class="m">4000</span><span class="p">:</span><span class="m">3000</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">database-seed</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</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">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</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="nt">MONGO_INITDB_ROOT_USERNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">banterbus</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_INITDB_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">banter_bus_management_api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">MONGO_HOSTNAME</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database:27017</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">depends_on</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">message-queue</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-message-queue</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:6.2.4</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">/data/datastore /data</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">127.0.0.1</span><span class="p">:</span><span class="m">6379</span><span class="p">:</span><span class="m">6379</span><span class="w">
</span></span></span></code></pre></div><p>Note: This file was already defined just not used in CI because I wanted to provide an easy way to start up my &ldquo;tech stack&rdquo;.
So the file had gone unused.</p>
<p>How do run our dev tasks?</p>
<ul>
<li>lint: <code>docker compose run app poetry run pre-commit run --all-files</code></li>
<li>integration tests: <code>docker compose run app poetry run pytest -v tests/integration</code></li>
</ul>
<p>Then our CI pipelines could look simply like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">tcp://docker:2375</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker compose build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose run app poetry run pre-commit run --all-files</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker compose run app poetry run pytest -v tests/unit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">test:integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="w"> </span><span class="l">docker compose run app poetry run pytest -v tests/integration</span><span class="w">
</span></span></span></code></pre></div><p>Now before job we build our docker images, <code>docker compose build</code>.
Then to run the dev task we do something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose run app &lt;<span class="nb">command</span> to run&gt;
</span></span></code></pre></div><p>So to run unit tests we could do:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">docker compose run app poetry run pytest -v tests/unit
</span></span></code></pre></div><h3 id="aside">Aside</h3>
<p>We could simplify this if we use <code>makefile</code> and make the target be  <code>poetry run pytest -v tests/unit</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nf">.PHONY</span><span class="o">:</span> <span class="n">unit_tests</span>
</span></span><span class="line"><span class="cl"><span class="nf">unit_tests</span><span class="o">:</span> <span class="c">## Run all the unit tests
</span></span></span><span class="line"><span class="cl">	@poetry run pytest -v tests/unit
</span></span></code></pre></div><p>Then our ci job would look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">test:unit-tests</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_request</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">make unit_tests</span><span class="w">
</span></span></span></code></pre></div><p>Which I think is a lot more readable and a lot easier to type. We can also
leverage auto-complete on the terminal and add help targets. So a user can see all the targets
they can run.</p>
]]></content:encoded>
    </item>
    
    <item>
      <title>E2E tests with Gitlab CI services</title>
      <link>https://haseebmajid.dev/posts/2021-12-25-e2e-tests-with-gitlab-ci-services/</link>
      <pubDate>Sat, 25 Dec 2021 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2021-12-25-e2e-tests-with-gitlab-ci-services/</guid>
      <description>&lt;h2 id=&#34;background&#34;&gt;Background&lt;/h2&gt;
&lt;p&gt;This will be a slightly shorter article. In this article I will show you how I&amp;rsquo;ve managed to do some
end-to-end testing with Gitlab CI services.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m building a browser-based multiplayer game called Banter Bus. Banter Bus consists of three main components,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gui: A SvelteKit based frontend the user will interact with to play the game&lt;/li&gt;
&lt;li&gt;core-api: A Socketio API written in Python&lt;/li&gt;
&lt;li&gt;management-api: A simple RESTful API written in Python (FastAPI)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now say I want to write some e2e Cypress tests, that will test all of these components interacting with each other.
Which mainly will look something like &lt;code&gt;gui -&amp;gt; core-api -&amp;gt; management-api&lt;/code&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>This will be a slightly shorter article. In this article I will show you how I&rsquo;ve managed to do some
end-to-end testing with Gitlab CI services.</p>
<p>I&rsquo;m building a browser-based multiplayer game called Banter Bus. Banter Bus consists of three main components,</p>
<ul>
<li>gui: A SvelteKit based frontend the user will interact with to play the game</li>
<li>core-api: A Socketio API written in Python</li>
<li>management-api: A simple RESTful API written in Python (FastAPI)</li>
</ul>
<p>Now say I want to write some e2e Cypress tests, that will test all of these components interacting with each other.
Which mainly will look something like <code>gui -&gt; core-api -&gt; management-api</code>.</p>
<p>Each of these project deploys its own Docker container, which we can then use for testing it. So how can we do this with Gitlab CI ?</p>
<h2 id="gitlab-services">Gitlab Services</h2>
<p>What is a Gitlab CI service ?</p>
<blockquote>
<p>The services keyword defines a Docker image that runs during a job linked to the Docker image that the image keyword defines. This allows you to access the service image during build time. - <a href="https://docs.gitlab.com/ee/ci/services/">https://docs.gitlab.com/ee/ci/services/</a></p>
</blockquote>
<p>Essentially they are Docker containers we can use in our CI jobs.</p>
<h2 id="packagejson">package.json</h2>
<p>For the examples below assume our <code>package.json</code> scipts section looks something like:</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;dev&#34;</span><span class="p">:</span> <span class="s2">&#34;svelte-kit dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;e2e&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress run --browser chrome&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;e2e:ci&#34;</span><span class="p">:</span> <span class="s2">&#34;start-server-and-test dev http://localhost:3000 e2e&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="gitlab-ci">Gitlab CI</h2>
<p>Let&rsquo;s take a look at an example <code>.gitlab-ci.yml</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cypress-e2e-chrome</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">cypress/browsers:node14.17.0-chrome88-ff89</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="nt">FF_NETWORK_PER_BUILD</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span><span class="c"># Hidden the rest of the variables as not to clutter the file</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><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">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-core-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-core-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">	</span>- <span class="l">npm ci</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">npm run e2e:ci</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">expire_in</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="l">week</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">cypress/screenshots</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">cypress/videos</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">reports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">junit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span>- <span class="l">results/TEST-*.xml</span><span class="w">
</span></span></span></code></pre></div><h3 id="services">Services</h3>
<p>Let&rsquo;s break file down a bit, these are essentially all the dependencies of our <code>gui</code> application. We need all of
these containers running.</p>
<p>In this case we need four containers (this doesn&rsquo;t really matter):</p>
<ul>
<li>banter-bus-database: A database for the core-api and management-api</li>
<li>banter-bus-core-api: The main API the gui will interact with</li>
<li>banter-bus-management-api: Used to help manage our available games, questions etc</li>
<li>banter-bus-database-seed: A short lived container which pre-fills the database with some values</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><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">name</span><span class="p">:</span><span class="w"> </span><span class="l">mongo:4.4.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-core-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-core-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api:test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">registry.gitlab.com/banter-bus/banter-bus-management-api/database-seed:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">alias</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database-seed</span><span class="w">
</span></span></span></code></pre></div><p>In our examples the <code>name</code> field is the image name, this is the same name you&rsquo;d use when using the <code>docker pull</code>
command. The next field is <code>alias</code> this is the name we&rsquo;ll use to reference that container. This is the container name.</p>
<p>To see how the <code>alias</code> is used, is to look at the environment variables we have provided for the job
<code>BANTER_BUS_CORE_API_DB_HOST: banter-bus-database</code>. So core-api will try to connect to database using
this host. You can read more about Docker is able to resolve this to an <a href="/blog/dns-docker-explained/">IP address here</a>. Another example is how the URL core-api will use to connect to the management-api
<code>BANTER_BUS_CORE_API_MANAGEMENT_API_URL: http://banter-bus-management-api</code>.</p>
<details
  class="notice danger"
  open="true"
>
    <summary class="notice-title">ENV Variable</summary>
  
  One environment variable we must provide is <code>FF_NETWORK_PER_BUILD</code> set to <code>1</code> (or true). Docker then
creates a bridge network so all the services can communicate amognst themselves. You can read more about
<a href="https://docs.gitlab.com/runner/executors/docker.html#create-a-network-for-each-job">it here</a>
</details>

<p>We&rsquo;ve discussed the most important part of the CI file, but lets quickly discuss the rest for completeness</p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Optional</summary>
  
  I&rsquo;ve discussed the main point of this article, how to use services and how to get them to work together.
</details>

<h3 id="variables">Variables</h3>
<p>We&rsquo;ve already spoken about this above, but lets take a quick look at the <code>variables</code> section.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">BANTER_BUS_CORE_API_MANAGEMENT_API_URL</span><span class="p">:</span><span class="w"> </span><span class="l">http://banter-bus-management-api</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">BANTER_BUS_CORE_API_DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">banter-bus-database</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">FF_NETWORK_PER_BUILD</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Hidden the rest of the variables as not to clutter the f</span><span class="w">
</span></span></span></code></pre></div><p>These are environment variables that are shared both with the job and the services. Some of these are
config passed to the application, such as <code>BANTER_BUS_CORE_API_MANAGEMENT_API_URL</code> and <code>BANTER_BUS_CORE_API_DB_HOST</code>.</p>
<h3 id="script">Script</h3>
<p>Since we are using the <code>cypress/browsers:node14.17.0-chrome88-ff89</code> image, we have access to chrome
(headless) browser we can use with Cypress.</p>
<p>So we can do something like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">npm ci</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">npm run e2e:ci</span><span class="w">
</span></span></span></code></pre></div><ul>
<li><code>npm ci</code>: Installs our npm dependencies for the gui app</li>
<li><code>export VITE_BANTER_BUS_CORE_API_URL=http://banter-bus-core-api:8080</code> exports an enviroment variable which will be used by the gui app so it knows the URL of the core-api. Note the use of the alias name here (and port <code>:8080</code> default port for the core-api)</li>
<li><code>npm run e2e:ci</code>: Starts the dev server and then runs the cypress test, see <code>start-server-and-test dev http://localhost:3000 e2e</code> where <code>e2e</code> is <code>cypress run --browser chrome</code></li>
</ul>
<h3 id="artifacts">Artifacts</h3>
<p>Finally, the artifacts are &ldquo;things&rdquo; that are left over after the build. In this case we use them in two ways:</p>
<ul>
<li>One to generate a coverage report with <code>junit</code></li>
<li>Two to save our Cypress screenshots and videos</li>
</ul>
<p>The downloadable artifacts will expire after 1 week. The Cypress files can be useful when debugging a problem
with your tests. You get a video of perhaps why the tests failed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">expire_in</span><span class="p">:</span><span class="w"> </span><span class="m">1</span><span class="w"> </span><span class="l">week</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">when</span><span class="p">:</span><span class="w"> </span><span class="l">always</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cypress/screenshots</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cypress/videos</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">reports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">junit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">results/TEST-*.xml</span><span class="w">
</span></span></span></code></pre></div><h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/banter-bus/banter-bus-gui/-/tree/350f1f986b077ac86da924b830fed88ffcd3cde0">Example Project</a></li>
<li><a href="https://gitlab.com/banter-bus/banter-bus-gui/-/jobs/1920396599">Example Job</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Testing a Gatsby application with Cypress on Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2021-03-22-testing-a-gatsby-application-with-cypress-on-gitlab-ci/</link>
      <pubDate>Mon, 22 Mar 2021 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2021-03-22-testing-a-gatsby-application-with-cypress-on-gitlab-ci/</guid>
      <description>&lt;p&gt;In this blog post, we will go over how we can automatically test a Gatsby site end-to-end (e2e), using Cypress on Gitlab CI.&lt;/p&gt;
&lt;h1 id=&#34;introduction&#34;&gt;Introduction&lt;/h1&gt;
&lt;h2 id=&#34;gatsby&#34;&gt;Gatsby&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.gatsbyjs.com&#34;&gt;Gatsby&lt;/a&gt; is a static site generator (SSG) built upon React. It allows us to create &amp;ldquo;blazing&amp;rdquo; fast websites.
In this example, we will use a simple blog starter template available and add a Cypress test.&lt;/p&gt;
&lt;h2 id=&#34;cypress&#34;&gt;Cypress&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Fast, easy and reliable testing for anything that runs in a browser. - Cypress README&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this blog post, we will go over how we can automatically test a Gatsby site end-to-end (e2e), using Cypress on Gitlab CI.</p>
<h1 id="introduction">Introduction</h1>
<h2 id="gatsby">Gatsby</h2>
<p><a href="https://www.gatsbyjs.com">Gatsby</a> is a static site generator (SSG) built upon React. It allows us to create &ldquo;blazing&rdquo; fast websites.
In this example, we will use a simple blog starter template available and add a Cypress test.</p>
<h2 id="cypress">Cypress</h2>
<blockquote>
<p>Fast, easy and reliable testing for anything that runs in a browser. - Cypress README</p>
</blockquote>
<p><a href="http://cypress.io/">Cypress</a> allows us to test a web application, how a real user would use the application.
Cypress will be used to test our Gatsby application, though if it&rsquo;s a site you can test it using Cypress.</p>
<h2 id="gitlab-ci">Gitlab CI</h2>
<p><a href="https://docs.gitlab.com/ee/ci/">Gitlab CI</a> is a continuous integration pipeline that will allow us to run our
tests automatically, such as when we merge code into the master branch.</p>
<h1 id="getting-started">Getting Started</h1>
<h2 id="gatsby-1">Gatsby</h2>
<p>Create a new Gatsby site, using this default Gatsby starter:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">gatsby new gatsby-starter-blog https://github.com/gatsbyjs/gatsby-starter-blog
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> gatsby-starter-blog
</span></span></code></pre></div><h3 id="optional-typescript">(Optional) Typescript</h3>
<p>Adding Typescript to a Gatsby web application.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add typescript @types/react @types/react-dom @types/node -D
</span></span><span class="line"><span class="cl">yarn add gatsby-plugin-typescript
</span></span></code></pre></div><p>Add the following to your <code>gatsby-config.js</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">plugins</span><span class="o">:</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="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-plugin-typescript`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">isTSX</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// defaults to false
</span></span></span><span class="line"><span class="cl">        <span class="nx">jsxPragma</span><span class="o">:</span> <span class="sb">`jsx`</span><span class="p">,</span> <span class="c1">// defaults to &#34;React&#34;
</span></span></span><span class="line"><span class="cl">        <span class="nx">allExtensions</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// defaults to false
</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>Then create a new file <code>tsconfig.json</code> (in the project root, where the <code>gatsby-config.js</code> is).</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;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;module&#34;</span><span class="p">:</span> <span class="s2">&#34;commonjs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;target&#34;</span><span class="p">:</span> <span class="s2">&#34;esnext&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;jsx&#34;</span><span class="p">:</span> <span class="s2">&#34;preserve&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;lib&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;dom&#34;</span><span class="p">,</span> <span class="s2">&#34;esnext&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;strict&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noEmit&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;isolatedModules&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;esModuleInterop&#34;</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;noUnusedLocals&#34;</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allowJs&#34;</span><span class="p">:</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;exclude&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;node_modules&#34;</span><span class="p">,</span> <span class="s2">&#34;public&#34;</span><span class="p">,</span> <span class="s2">&#34;.cache&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="cypress-1">Cypress</h2>
<p>Now to finally add Cypress to our application so we can test it. First, install the dependencies.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add -D cypress cypress-axe axe-core start-server-and-test
</span></span><span class="line"><span class="cl"><span class="c1"># Add types</span>
</span></span><span class="line"><span class="cl">yarn add -D @types/cypress-axe
</span></span></code></pre></div><p>Next, let&rsquo;s create a <code>cypress.json</code> folder in the project root.</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;baseUrl&#34;</span><span class="p">:</span> <span class="s2">&#34;http://localhost:8000/&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&#34;integrationFolder&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress/e2e&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Next, let&rsquo;s add some new &ldquo;scripts&rdquo; to the <code>package.json</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line hl"><span class="cl">  <span class="s2">&#34;scripts&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;cy:open&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress open&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;cy:run&#34;</span><span class="p">:</span> <span class="s2">&#34;cypress run&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby build&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;develop&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;format&#34;</span><span class="p">:</span> <span class="s2">&#34;prettier --write \&#34;**/*.{js,jsx,ts,tsx,json,md}\&#34;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;start&#34;</span><span class="p">:</span> <span class="s2">&#34;npm run develop&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;serve&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby serve&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;clean&#34;</span><span class="p">:</span> <span class="s2">&#34;gatsby clean&#34;</span><span class="p">,</span>
</span></span><span class="line hl"><span class="cl">    <span class="nt">&#34;test&#34;</span><span class="p">:</span> <span class="s2">&#34;echo \&#34;Write tests! -&gt; https://gatsby.dev/unit-testing\&#34; &amp;&amp; exit 1&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;test:e2e&#34;</span><span class="p">:</span> <span class="s2">&#34;start-server-and-test &#39;yarn develop&#39; http://localhost:8000 &#39;yarn cy:open&#39;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;test:e2e:ci&#34;</span><span class="p">:</span> <span class="s2">&#34;start-server-and-test &#39;yarn develop&#39; http://localhost:8000 &#39;yarn cy:run&#39;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span></code></pre></div><p>These scripts allow us to start Cypress, <code>cy:open</code> opens a GUI to visualise our tests whereas <code>cy:run</code> does it all
in the terminal (the browser runs in headless mode). Where we will run <code>test:e2e:ci</code> in our CI pipeline, here we use
the <code>start-server-and-test</code> command to start our Gatsby server using <code>yarn develop</code>. Then we run <code>cy:run</code> to
start our tests.</p>
<h3 id="structure">Structure</h3>
<p>Create a new folder called <code>cypress</code> which will look something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">├── e2e
</span></span><span class="line"><span class="cl">│   └── accessibility.test.ts
</span></span><span class="line"><span class="cl">├── fixtures
</span></span><span class="line"><span class="cl">│   └── graphql.json
</span></span><span class="line"><span class="cl">├── plugins
</span></span><span class="line"><span class="cl">│   └── index.js
</span></span><span class="line"><span class="cl">├── support
</span></span><span class="line"><span class="cl">│   ├── commands.js
</span></span><span class="line"><span class="cl">│   ├── index.d.ts
</span></span><span class="line"><span class="cl">│   └── index.js
</span></span><span class="line"><span class="cl">└── tsconfig.json
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p cypress/support
</span></span></code></pre></div><p>Create a file at <code>cypress/support/commands.js</code></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">Cypress</span><span class="p">.</span><span class="nx">Commands</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="sb">`assertRoute`</span><span class="p">,</span> <span class="p">(</span><span class="nx">route</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">cy</span><span class="p">.</span><span class="nx">url</span><span class="p">().</span><span class="nx">should</span><span class="p">(</span><span class="sb">`equal`</span><span class="p">,</span> <span class="sb">`</span><span class="si">${</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">origin</span><span class="si">}${</span><span class="nx">route</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Add some custom types for Cypress <code>index.d.ts</code> if you are using Typescript.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;cypress&#34; /&gt;
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">declare</span> <span class="kr">namespace</span> <span class="nx">Cypress</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">interface</span> <span class="nx">Chainable</span><span class="p">&lt;</span><span class="nt">Subject</span><span class="p">&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * Assert the current URL
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @param route
</span></span></span><span class="line"><span class="cl"><span class="cm">     * @example cy.assertRoute(&#39;/page-2&#39;)
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">assertRoute</span><span class="p">(</span><span class="nx">route</span>: <span class="kt">string</span><span class="p">)</span><span class="o">:</span> <span class="nx">Chainable</span><span class="p">&lt;</span><span class="nt">any</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm">     * Waits for Gatsby to finish the route change, in order to ensure event handlers are properly setup
</span></span></span><span class="line"><span class="cl"><span class="cm">     */</span>
</span></span><span class="line"><span class="cl">    <span class="nx">waitForRouteChange</span><span class="p">()</span><span class="o">:</span> <span class="nx">Chainable</span><span class="p">&lt;</span><span class="nt">any</span><span class="p">&gt;;</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>Next create the <code>index.js</code> file, which should look something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;cypress-axe&#34;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="s2">&#34;./commands&#34;</span>
</span></span></code></pre></div><p>Next, let&rsquo;s create a plugin folder <code>mkdir -p cypress/plugins</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="c1">// / &lt;reference types=&#34;cypress&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1">// ***********************************************************
</span></span></span><span class="line"><span class="cl"><span class="c1">// This example plugins/index.js can be used to load plugins
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">// You can change the location of this file or turn off loading
</span></span></span><span class="line"><span class="cl"><span class="c1">// the plugins file with the &#39;pluginsFile&#39; configuration option.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span><span class="line"><span class="cl"><span class="c1">// You can read more here:
</span></span></span><span class="line"><span class="cl"><span class="c1">// https://on.cypress.io/plugins-guide
</span></span></span><span class="line"><span class="cl"><span class="c1">// ***********************************************************
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// This function is called when a project is opened or re-opened (e.g. due to
</span></span></span><span class="line"><span class="cl"><span class="c1">// the project&#39;s config changing)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/**
</span></span></span><span class="line"><span class="cl"><span class="cm"> * @type {Cypress.PluginConfig}
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">(</span><span class="nx">on</span><span class="p">,</span> <span class="nx">config</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// `on` is used to hook into various events Cypress emits
</span></span></span><span class="line"><span class="cl">  <span class="c1">// `config` is the resolved Cypress config
</span></span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nx">config</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now finally let&rsquo;s create our tests folder <code>mkdir -p cypress/e2e</code>.</p>
<h3 id="cypress-axe">cypress-axe</h3>
<p>In this blog post we won&rsquo;t go over any complicated Cypress test we will simply use <code>cypress-axe</code> to test
the accessibility of our (a11y) of our website.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;../support/index&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;cypress&#34; /&gt;
</span></span></span><span class="line"><span class="cl"><span class="c1">/// &lt;reference types=&#34;@types/cypress-axe&#34; /&gt;
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s2">&#34;Component accessibility test&#34;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">it</span><span class="p">(</span><span class="s2">&#34;Main Page&#34;</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s2">&#34;/&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">wait</span><span class="p">(</span><span class="mi">500</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">injectAxe</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="nx">cy</span><span class="p">.</span><span class="nx">checkA11y</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">include</span><span class="o">:</span> <span class="p">[[</span><span class="s2">&#34;#___gatsby&#34;</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>Note the <code>///</code> comments at the top used to add types for cypress. The test above will go to our
home page and test if it has any a11y violations and if so will fail the test.</p>
<p>We can now run our tests locally by running this command:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn run test:e2e
</span></span></code></pre></div><h2 id="gitlab-ci-1">Gitlab CI</h2>
<p>Now how can we automate this, so the tests will run say every time we make changes on the master branch to make
sure we haven&rsquo;t broken any a11y. Create a new <code>.gitlab-ci.yml</code> or add the following job to an existing CI file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:12.14.1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">CYPRESS_CACHE_FOLDER</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;$CI_PROJECT_DIR/cache/Cypress&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_COMMIT_REF_SLUG}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cache/Cypress</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">node_modules</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn install</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tests</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">cypress/browsers:node12.14.1-chrome83-ff77</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn test:e2e:ci</span><span class="w">
</span></span></span></code></pre></div><p>I won&rsquo;t go into the details of what makes up a Gitlab CI file. At the top of the file, we will cache the <code>node_modules</code>
file so we can share it between the job and the Cypress cache.
The job itself is very simple it uses a <code>cypress/browsers:node12.14.1-chrome83-ff77</code> Docker
image which provides a headless chrome browser that Cypress can leverage to run the tests.
As we won&rsquo;t have access to a GUI in the Gitlab CI runner. The <code>tests</code> job is very simple it runs <code>yarn test:e2e:ci</code> to
run our Cypress tests.</p>
<p>That&rsquo;s it, quite simple to add Cypress tests that run in our CI pipeline.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2021-03-22-testing-a-gatsby-application-with-cypress-on-gitlab-ci/source_code">Example source code</a></li>
<li><a href="http://cypress.io/">Cypress</a></li>
<li><a href="https://gitlab.com/hmajid2301/portfolio-site/-/jobs/1080367107">Example Job</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to source your Gatsby posts from another repo</title>
      <link>https://haseebmajid.dev/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/</link>
      <pubDate>Fri, 18 Sep 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/</guid>
      <description>&lt;p&gt;In this article, we will go over how you can manage your markdown blog posts from another git repository (repo). Separate to the git repository for your Gatsby site.
This is the same process that I use to manage &lt;a href=&#34;https://gitlab.com/hmajid2301/articles&#34;&gt;this repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So what this entails is the source code for my Gatsby site is in a repo called &lt;code&gt;portfolio-site&lt;/code&gt; on Gitlab.
Then I have another repo for all of my blog posts (in markdown) called &lt;code&gt;articles&lt;/code&gt;. During build time of the
Gatsby blog, we will import the markdown files from our &lt;code&gt;articles&lt;/code&gt; git repo and use it as a source of data for
our Gatsby blog.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how you can manage your markdown blog posts from another git repository (repo). Separate to the git repository for your Gatsby site.
This is the same process that I use to manage <a href="https://gitlab.com/hmajid2301/articles">this repo</a>.</p>
<p>So what this entails is the source code for my Gatsby site is in a repo called <code>portfolio-site</code> on Gitlab.
Then I have another repo for all of my blog posts (in markdown) called <code>articles</code>. During build time of the
Gatsby blog, we will import the markdown files from our <code>articles</code> git repo and use it as a source of data for
our Gatsby blog.</p>
<h2 id="git-plugin">Git Plugin</h2>
<p>First, install the <a href="https://www.gatsbyjs.com/plugins/gatsby-source-git/?=git">Gatsby git plugin</a>, so that we can source our data from git.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add gatsby-source-git
</span></span></code></pre></div><p>Then add the plugin to your <code>gatsby-config.js</code> to tell it where to source its data from.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Gatsby Filesystem</summary>
  
  You need to use the <code>gatsby-source-filesystem</code> before the <code>gatsby-source-git</code>.
You can read more about it <a href="https://github.com/stevetweeddale/gatsby-source-git/issues/22">here at this Github issue</a>.
</details>

<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">resolve</span><span class="o">:</span> <span class="sb">`gatsby-source-git`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">options</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">name</span><span class="o">:</span> <span class="sb">`Articles`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">remote</span><span class="o">:</span> <span class="s2">&#34;https://gitlab.com/hmajid2301/articles.git&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">branch</span><span class="o">:</span> <span class="sb">`master`</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">patterns</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;**/*&#34;</span><span class="p">,</span> <span class="s2">&#34;!**/*/index.md&#34;</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></code></pre></div><p>In our example, I will use <a href="https://gitlab.com/hmajid2301/articles">this</a>, the same repo this blog post originates from.
You can specify the branch to use if you want. The most interesting bit is the <code>patterns</code> section. This is where you can
specify which files to include and which to ignore <code>[&quot;**/*&quot;, &quot;!**/*/index.md&quot;]</code>. In this example, I want to ignore
all files called <code>index.md</code> because these are the ones that I use in my example Gatsby blogs in this repo for.
You can read more about the <a href="https://github.com/mrmlnc/fast-glob">pattern here</a>.</p>
<h2 id="graphql">GraphQL</h2>
<p>We can now check if the articles are being imported correctly by using the GraphQL IDE that comes with Gatsby.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn develop
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Go to localhost:8000/__graphql</span>
</span></span></code></pre></div><p>Then run the following query.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-graphql" data-lang="graphql"><span class="line"><span class="cl"><span class="kd">query</span><span class="w"> </span><span class="nc">MyQuery</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="py">allMarkdownRemark</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="py">edges</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="py">node</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="py">fileAbsolutePath</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>You should see output like this, where it will list all of your blog posts/markdown files. Here you can verify your git
repo is being sourced correctly.</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;data&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allMarkdownRemark&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;edges&#34;</span><span class="p">:</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="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;fileAbsolutePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/home/haseeb/projects/personal/articles/35. Gatsby source git/source_code/.cache/gatsby-source-git/Articles/1. Expo with VirtualBox and Genymotion/README.md&#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><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;fileAbsolutePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/home/haseeb/projects/personal/articles/35. Gatsby source git/source_code/.cache/gatsby-source-git/Articles/11. React Navigation with React Native/README.md&#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><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;fileAbsolutePath&#34;</span><span class="p">:</span> <span class="s2">&#34;/home/haseeb/projects/personal/articles/35. Gatsby source git/source_code/.cache/gatsby-source-git/Articles/13. REST API using OpenAPI, Flask &amp; Connexions/README.md&#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><span class="line"><span class="cl">        <span class="c1">// ...
</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><h2 id="gatsby-node">Gatsby Node</h2>
<p>Now make sure you have logic in your <code>gatsby-node.js</code> file to create a blog post page for every
markdown file that we source, i.e. one blog post for every item in the list above.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">exports</span><span class="p">.</span><span class="nx">createPages</span> <span class="o">=</span> <span class="kr">async</span> <span class="p">({</span> <span class="nx">graphql</span><span class="p">,</span> <span class="nx">actions</span> <span class="p">})</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">createPage</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">actions</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">blogPost</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="sb">`./src/templates/blog-post.js`</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="kr">await</span> <span class="nx">graphql</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="sb">`
</span></span></span><span class="line"><span class="cl"><span class="sb">      {
</span></span></span><span class="line"><span class="cl"><span class="sb">        allMarkdownRemark(
</span></span></span><span class="line"><span class="cl"><span class="sb">          sort: { fields: [frontmatter___date], order: DESC }
</span></span></span><span class="line"><span class="cl"><span class="sb">          limit: 1000
</span></span></span><span class="line"><span class="cl"><span class="sb">        ) {
</span></span></span><span class="line"><span class="cl"><span class="sb">          edges {
</span></span></span><span class="line"><span class="cl"><span class="sb">            node {
</span></span></span><span class="line"><span class="cl"><span class="sb">              frontmatter {
</span></span></span><span class="line"><span class="cl"><span class="sb">                title
</span></span></span><span class="line"><span class="cl"><span class="sb">                slug
</span></span></span><span class="line"><span class="cl"><span class="sb">              }
</span></span></span><span class="line"><span class="cl"><span class="sb">            }
</span></span></span><span class="line"><span class="cl"><span class="sb">          }
</span></span></span><span class="line"><span class="cl"><span class="sb">        }
</span></span></span><span class="line"><span class="cl"><span class="sb">      }
</span></span></span><span class="line"><span class="cl"><span class="sb">    `</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">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">errors</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">throw</span> <span class="nx">result</span><span class="p">.</span><span class="nx">errors</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">// Create blog posts pages.
</span></span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="nx">result</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">allMarkdownRemark</span><span class="p">.</span><span class="nx">edges</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="nx">posts</span><span class="p">.</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">post</span><span class="p">,</span> <span class="nx">index</span><span class="p">)</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">previous</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">===</span> <span class="nx">posts</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="nx">posts</span><span class="p">[</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">].</span><span class="nx">node</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">next</span> <span class="o">=</span> <span class="nx">index</span> <span class="o">===</span> <span class="mi">0</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="nx">posts</span><span class="p">[</span><span class="nx">index</span> <span class="o">-</span> <span class="mi">1</span><span class="p">].</span><span class="nx">node</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nx">createPage</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">      <span class="nx">path</span><span class="o">:</span> <span class="nx">post</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">component</span><span class="o">:</span> <span class="nx">blogPost</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">context</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">slug</span><span class="o">:</span> <span class="nx">post</span><span class="p">.</span><span class="nx">node</span><span class="p">.</span><span class="nx">frontmatter</span><span class="p">.</span><span class="nx">slug</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">previous</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nx">next</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><h2 id="gitlab-ci">Gitlab CI</h2>
<p>So every time we make a change in our article repo we want to trigger a rebuild of our site. Since I use Gitlab, I will
show you how you can do this with Gitlab CI. Every commit on the master branch, on our repo that contains our
articles, will trigger a rebuild on the Gatsby repo.</p>
<details
  class="notice warning"
  open="true"
>
    <summary class="notice-title">Assumption</summary>
  
  This next section assumes that you use Gitlab to host your repos.
It also assumes that for your Gatsby blog you use Gitlab CI to build/publish it.
</details>

<p>For example, in my use case <a href="https://gitlab.com/hmajid2301/articles">the article repo</a> will trigger a rebuild for
<a href="https://gitlab.com/hmajid2301/portfolio-site/-/tree/7258fe7ca1366024f17da5952077cdc00f00a3a8">the Gatsby repo</a>.</p>
<p>First, go to your Gatsby repo then go to <code>Settings &gt; CI/CD &gt; Pipeline triggers</code>. Then create a new pipeline trigger,
save the newly created token to your CI/CD variables.</p>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/JbAk6xpBRxc"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>

<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/X9m8UxmZgy8"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>
</p>
<p>Then also copy the <code>cURL</code> command shown and add the following to your <code>.gitlab-ci.yml</code>, with the <code>cURL</code> command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">rebuild:portfolio-site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</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">curlimages/curl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="s2">&#34;curl -X POST -F token=${TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v4/projects/19260161/trigger/pipeline&#34;</span></span></span></code></pre></div>
<p>Make sure you replace <code>19260161</code> with the project ID of your Gatsby blog, as this is the repo we want to trigger a
rebuild of. This means every time we push a new commit (i.e. an article) to the master branch of the articles,
it will trigger the pipeline to run on our Gatsby blog.</p>
<p>This will mean when it runs <code>yarn build</code> or <code>gatsby build</code> it&rsquo;ll source the markdown data from the latest commit on
our article git repo and will have the new article or whatever changes were made. The <code>.gitlab-ci</code> for our
Gatsby blog may look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:12.13.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_COMMIT_REF_SLUG}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">node_modules</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn install</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">build:site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn run build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">public</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">deploy:site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">deploy</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="l">npm i -g netlify-cli</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="l">yarn deploy --site $NETLIFY_SITE_ID --auth $NETLIFY_PERSONAL_TOKEN --message &#34;$CI_COMMIT_TITLE&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">build:site</span></span></span></code></pre></div>
<details
  class="notice tip"
  open="true"
>
    <summary class="notice-title">Artifcats</summary>
  
  The <code>deploy:site</code> job uses the build artifacts from the previous <code>build:site</code> job which has the site data stored in the <code>public</code>
folder. Due to the sites default settings on Netlify, this is what is uploaded when we use <code>netlify-cli</code>.
</details>

<p>I build and deploy the site from Gitlab CI to save the build minutes on Netlify. All you need to do this is to get your
<code>NETLIFY_SITE_ID</code> and create a <code>NETLIFY_PERSONAL_TOKEN</code> that can make the API request to publish the site on your behalf.</p>
<p><img
        loading="lazy"
        src="/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/images/gatsby-blog-ci.png"
        type=""
        alt="Gatsby Blog CI"
        
      /></p>
<details
  class="notice info"
  open="true"
>
    <summary class="notice-title">Gitlab CI</summary>
  
  You can, of course, change the <code>deploy:site</code> job to suit how you want to deploy your site, i.e. Gitlab pages, Github Pages, Vercel etc.
</details>

<h2 id="netlify">Netlify</h2>
<p>If you don&rsquo;t want to use Gitlab CI to build and publish your Gatsby blog and want to force a rebuild on Netlify.
Then you can do the following; Every time we push a new commit to the master branch on the article repo. We can
use a webhook to trigger a rebuild of our site on Netlify. To do this select your website on the Netlify GUI.
Then <code>Settings</code> &gt; <code>Build &amp; deploy</code> &gt; <code>Build hooks</code>. Add a new build hook. Then copy the <code>cURL</code> command,
so your article repo <code>.gitlab-ci.yml</code> now looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">rebuild:portfolio-site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</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">curlimages/curl</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">    </span>- <span class="l">curl -X POST -d {} https://api.netlify.com/build_hooks/5f5e9c4f495aebe573c39aef</span></span></span></code></pre></div>
<p>You will want to turn <code>5f5e9c4f495aebe573c39aef</code> into a CI/CD variable, else anyone can force a rebuild of your site.</p>
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden">
  <iframe src="https://yewtu.be/embed/7KRihyulbTQ"
    style="position:absolute;top:0;left:0;width:100%;height:100%;border:0" allowfullscreen></iframe>
</div>

<p>That&rsquo;s it, we learnt how we can manage our markdown articles in a separate repo to our Gatsby blog! We went over how we
can also automate the rebuild of our site using Gitlab CI and Netlify.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-09-18-how-to-source-your-gatsby-posts-from-another-repo/source_code">Example source code</a></li>
<li>Example <a href="https://gitlab.com/hmajid2301/portfolio-site/-/tree/7258fe7ca1366024f17da5952077cdc00f00a3a8">Gatsby blog</a> repo</li>
<li>Example <a href="https://gitlab.com/hmajid2301/articles">Articles</a> repo</li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to auto create MRs in Gitlab</title>
      <link>https://haseebmajid.dev/posts/2020-08-31-how-to-auto-create-mrs-in-gitlab/</link>
      <pubDate>Mon, 31 Aug 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-08-31-how-to-auto-create-mrs-in-gitlab/</guid>
      <description>&lt;p&gt;In this article, we will go over how we can use the &lt;code&gt;gitlab-auto-mr&lt;/code&gt; CLI script I wrote to help automate your Gitlab
workflow. This is a very simple script you can use with Gitlab which will auto-create merge requests (MRs) every time you
create a new branch on a project in Gitlab.&lt;/p&gt;
&lt;h2 id=&#34;optional-git-feature-branches&#34;&gt;(Optional) Git Feature Branches&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Feel free to skip this section if you are already familar with feature branch, skip to the &lt;code&gt;Gitlab Auto MR&lt;/code&gt; section&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will go over how we can use the <code>gitlab-auto-mr</code> CLI script I wrote to help automate your Gitlab
workflow. This is a very simple script you can use with Gitlab which will auto-create merge requests (MRs) every time you
create a new branch on a project in Gitlab.</p>
<h2 id="optional-git-feature-branches">(Optional) Git Feature Branches</h2>
<blockquote>
<p>Feel free to skip this section if you are already familar with feature branch, skip to the <code>Gitlab Auto MR</code> section</p>
</blockquote>
<p>Before I introduce what the script does and how we use it, let&rsquo;s go over why you might need to use it.
Say you&rsquo;re working on a project with multiple other people and you want to make sure you keep your master/production
branch clean. One way to do that is everyone uses &ldquo;feature&rdquo; branches. So for every feature being added to the project
you create a new short-lived branch off of the master branch. Then typically one developer will work on a feature
and make a merge request when ready to get the branch merged into the main master branch, to integrate their work
with everyone else&rsquo;s.</p>
<p><img
    loading="lazy"
    src="https://docs.gitlab.com/ee/ci/introduction/img/gitlab_workflow_example_11_9.png"
    alt="Gitlab Workflow"
    
  /></p>
<p>This means your changes can get reviewed before they merged into the master branch. This helps keep the master branch
&ldquo;cleaner&rdquo;, it should have fewer bugs etc. One good way to visualise this is in the diagram above. Let&rsquo;s say we want
to add reaction buttons to a blog. We would create a new branch called something like <code>feature/add-reaction-buttons</code>.
Then we would commit our changes on the branch.</p>
<p>Usually, this is coupled with a CI pipeline which will auto-run against our code. It may run jobs like unit tests, linting
and static code analysis. This acts as a kind of first step review, we need to make sure it&rsquo;s passing (and is green) before
people even start to review our code, as seen in the above diagram. Once the CI pipeline is working the merge request can
be reviewed. After the merge request has been approved it can be merged into the master branch and we will add the new
feature to our codebase, which will eventually get deployed to production. We can also then delete our old branch.
You can read more about <a href="https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow">feature branches here</a>.</p>
<blockquote>
<p>Note there are many other git flows. This can project can be used in conjunction with all which will use some form of feature branching.</p>
</blockquote>
<h3 id="cicd">CI/CD</h3>
<blockquote>
<p>Feel free to skip this section if you are already familiar with CI/CD, Git and Gitlab CI.</p>
</blockquote>
<p>Continuous Integration (CI) is typically defined as making sure all code being integrated into codebase works.
It usually involves running a set of jobs referred to as a CI pipeline. Some jobs we may run include linting our
code and running unit tests. This is usually done automatically using a tool such as Travis, Circle or even Gitlab.</p>
<p>One use case for this is when others are adding new features to our codebase and we want to check it
still works. We can create a CI pipeline that will run unit tests against the new code automatically when a pull request
(GitHub) or merge request (Gitlab) is opened. This saves us a lot of time, rather than having to copy the new
features/code and then run the tests ourselves on our machine.</p>
<p>Continuous Delivery (CD) is typically an extension of CI to make sure that you can release new changes quickly.
This means automating your release process, such that you can deploy your application at any point of time just
by clicking on a button.</p>
<p>Continuous Deployment takes CD one step further by requiring no human intervention in deploying our application.
You can read more about <a href="https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment">this here</a></p>
<h2 id="gitlab-auto-mr">Gitlab Auto MR</h2>
<p>I created a simple CLI script, that I run during on Gitlab CI, which will auto-create merge requests every time you create a new branch.
So you could have something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">create:merge-request</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">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_mr -t master -c WIP -d .gitlab/merge_request_templates/merge_request.md -r -s --use-issue-name</span><span class="w">
</span></span></span></code></pre></div><p>To use this tool you will need to create a personal access token so that the tool can access the GitLab API on your behalf.
You can find out how to do that <a href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">here</a>. In the
the example above I have set the private token in my CI/CD variables as <code>GITLAB_PRIVATE_TOKEN</code> but you can also pass in the
<code>--private-token</code> argument instead.</p>
<p><img
        loading="lazy"
        src="/posts/2020-08-31-how-to-auto-create-mrs-in-gitlab/images/ci-vars.gif"
        type=""
        alt="CI/CD Variables in Gitlab CI"
        
      /></p>
<p>If you use <code>registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr</code> this Docker image, the cli tool already
comes preinstalled with all of the dependencies as well. Else you can also install it manually using
<code>pip install gitlab-auto-mr</code>.</p>
<p>This particular job will only be run on a branch that is not called a master. This is because of the <code>except</code> clause
we defined above. Hence it should only run when we create a new feature branch. It will create a new merge request
if one does not already exist between this new branch and the master branch. The target branch it will create the
merge request to is set by the <code>-t</code> option, in this example <code>-t master</code>. For example, source branch -&gt; target branch.</p>
<p>Next, we append all of our new merge requests with WIP, set by the <code>-c WIP</code> argument, where WIP typically means
<code>Work in progress</code> so other devs know not to review our MR as it&rsquo;s not ready. Next, the tool also allows you to
specify a merge request template to use by passing the <code>-d</code> and giving it the path to a file. In this example
<code>.gitlab/merge_request_templates/merge_request.md</code> looks like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="gh"># Description
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">&lt;!-- please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. --&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Type
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Bug Fix
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Improvement
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> New Feature
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Fixes #<span class="cm">&lt;!-- Issue Number --&gt;</span>
</span></span></code></pre></div><p>The other arguments do the following:</p>
<ul>
<li><code>-r</code>: Will remove the source branch (our feature branch) after the MR has been approved</li>
<li><code>-s</code>: Will squash our commits into a single commit, so each feature branch will appear as a single commit onto the master branch</li>
<li><code>--use-issue-name</code>: If set and you do something like <code>feature/#6</code> it will search for the issue with id <code>6</code> and pull information from there like labels and milestones etc. It will then assign those to this MR, an example of this can be seen with <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/merge_requests/196">MR here</a> where the issue <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/issues/211"><code>#211</code> is here</a></li>
</ul>
<p>You can get a full list of options available using this tool
<a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr">here at its project page</a>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>This project was originally inspired by <a href="https://gitlab.com/tmaier/gitlab-auto-merge-request">this other project</a> and this <a href="https://rpadovani.com/open-mr-gitlab-ci">post</a></li>
<li><a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr">Project Page</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/blob/84d48e80d77a04870b748d2ac62e2cb698f17db8/.gitlab-ci.yml">An example project</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/merge_requests/176">Example MRs created with this tool</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/pipelines/120105476">Example pipeline running this tool</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use Gitlab CI, Pytest and docker-compose together</title>
      <link>https://haseebmajid.dev/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/</link>
      <pubDate>Mon, 22 Jun 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/</guid>
      <description>&lt;p&gt;On a recent project, I was working on, I wanted to test my web service using docker-compose where I can run and kill
Docker containers used by the application and see how my web application reacts to that. In this article, we will
go over how you start docker containers using docker-compose from within Gitlab CI.&lt;/p&gt;
&lt;p&gt;&lt;img
        loading=&#34;lazy&#34;
        src=&#34;../../posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/images/main.png&#34;
        type=&#34;&#34;
        alt=&#34;main&#34;
        
      /&gt;&lt;/p&gt;
&lt;p&gt;The diagram above is a visualisation of what we are trying to achieve. We want to spawn Docker containers using docker-compose
from within our job. The spawning and destruction of these Docker containers will be done via our Python code. We can achieve
this by using dind (Docker in Docker). I have written a previous article on this topic which you can read more about
&lt;a href=&#34;../../posts/2020-05-01-how-to-use-dind-with-gitlab-ci/&#34;&gt;here&lt;/a&gt;. This article assumes you already somewhat familiar
with Docker, docker-compose and Pytest.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>On a recent project, I was working on, I wanted to test my web service using docker-compose where I can run and kill
Docker containers used by the application and see how my web application reacts to that. In this article, we will
go over how you start docker containers using docker-compose from within Gitlab CI.</p>
<p><img
        loading="lazy"
        src="/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/images/main.png"
        type=""
        alt="main"
        
      /></p>
<p>The diagram above is a visualisation of what we are trying to achieve. We want to spawn Docker containers using docker-compose
from within our job. The spawning and destruction of these Docker containers will be done via our Python code. We can achieve
this by using dind (Docker in Docker). I have written a previous article on this topic which you can read more about
<a href="/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/">here</a>. This article assumes you already somewhat familiar
with Docker, docker-compose and Pytest.</p>
<p>This compose file will be used to start our Docker containers.</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="s2">&#34;3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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">service1</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">container1</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">docker</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="p">[</span><span class="s2">&#34;tail&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-f&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/dev/null&#34;</span><span class="p">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">service2</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">container_name</span><span class="p">:</span><span class="w"> </span><span class="l">container2</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">docker</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="p">[</span><span class="s2">&#34;tail&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;-f&#34;</span><span class="p">,</span><span class="w"> </span><span class="s2">&#34;/dev/null&#34;</span><span class="p">]</span><span class="w">
</span></span></span></code></pre></div><h2 id="gitlab-ci">Gitlab CI</h2>
<p>Next, we have our <code>.gitlab-ci.yml</code> file, this file is used to tell Gitlab CI what our CI jobs should do.
In this example, we have one job called <code>test:integration</code> which will run our integration tests. But before we do that
we need a way to access the Docker daemon from within Gitlab CI, this can be done by using the <code>docker:dind</code> service.</p>
<p>The docker:dind image automatically using its entry point starts a docker daemon. We need to use this daemon to
start/stop our Docker images within CI. The docker:dind (dind = Docker in Docker) image is almost identical to
the docker image. The difference being the dind image starts a Docker daemon. In this example, the job will
use the docker image as the client and connect to the daemon running in this container.</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">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">tests:integration</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</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">hmajid2301/dind-docker-compose</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pip install pytest docker lovely-pytest-docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pytest -s tests/test_integration.py</span><span class="w">
</span></span></span></code></pre></div><p>The job itself is very simple, it uses a container which already comes with <code>docker</code> and <code>docker-compose</code>. Next we
install the dependencies we need for our tests. Then it runs our tests.</p>
<h2 id="tests">Tests</h2>
<p>Now onto our actual tests file. It looks more complicated than it is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">docker</span> <span class="k">as</span> <span class="nn">docker_py</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">docker_client</span> <span class="o">=</span> <span class="n">docker_py</span><span class="o">.</span><span class="n">from_env</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">docker_compose</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">docker</span><span class="p">(</span><span class="n">docker_services</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">global</span> <span class="n">docker_compose</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span> <span class="o">=</span> <span class="n">docker_services</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">setup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">kill_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">remove</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">container</span> <span class="ow">in</span> <span class="n">containers</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">container</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">container_name</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">container</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_container</span><span class="p">(</span><span class="n">service_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">service_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_two_containers</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_kill_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">kill_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="ow">not</span> <span class="n">container1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_start_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_container</span><span class="p">(</span><span class="s2">&#34;service1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">container11</span>
</span></span></code></pre></div><p>The first part is the setup, we will use the python Docker library,<code>docker</code>. Which allow us to use Python code to
control our Docker daemon. The first <code>@pytest-fixture</code> called <code>docker</code> allows us to use the
<code>lovely-pytest-docker</code> library. To give us a <code>docker_compose</code> object which will allow us to again use Python code to
control our <code>docker-compose.yml</code> file (start-up/stop containers). The library also has some very nice features such
as waiting for containers or executing commands within containers. You can find the available functions
<a href="https://github.com/lovelysystems/lovely-pytest-docker/blob/master/src/lovely/pytest/docker/compose.py">here</a>.
Now we can access the <code>docker_compose</code> object by using our <code>docker_compose</code> global variable.</p>
<p>The reason we have both a library for Docker and docker-compose is because at the moment there is no way to use
<code>lovely-pytest-docker</code> (as far as I&rsquo;m aware) to stop a single container. So we need to use the standard <code>docker</code>
library to do that. We also use the standard <code>docker</code> library to find out if a container is running.</p>
<p>Next, we have the <code>setup()</code> fixture which we auto use, this means the fixture is run before our test, normally a fixture
would only be called once it has been referred to within another function. In this function, we start both of our containers
in our <code>docker-compose</code> file. This is the same as running <code>docker-compose up --build -d</code>. Next we <code>yield</code>, how exactly the
<code>yield</code> command works I won&rsquo;t go over in this article, all you have to know is that everything after the yield will only
be run after all of our tests. In this case we teardown our containers (stop them). This is the same as running <code>docker-compose down</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">docker</span> <span class="k">as</span> <span class="nn">docker_py</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pytest</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">docker_client</span> <span class="o">=</span> <span class="n">docker_py</span><span class="o">.</span><span class="n">from_env</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">docker_compose</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">docker</span><span class="p">(</span><span class="n">docker_services</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">global</span> <span class="n">docker_compose</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span> <span class="o">=</span> <span class="n">docker_services</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@pytest.fixture</span><span class="p">(</span><span class="n">scope</span><span class="o">=</span><span class="s2">&#34;session&#34;</span><span class="p">,</span> <span class="n">autouse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">setup</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">yield</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">shutdown</span><span class="p">()</span>
</span></span></code></pre></div><p>The next part of our file contains some helper functions I&rsquo;ve written. These functions can be used by multiple tests.
This helps our tests file stay more DRY (do not repeat yourself). You may well want to make these part of a
(helper) class that you expose as a fixture. If you wanted to structure these properly so they can be accessed by
more than one file. Also whilst we are on this topic, we may want to move our fixture to <code>conftest.py</code>, again to allow
other files to use the same fixture we have defined here. But to keep this example simpler we will leave it here.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">kill_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">kill</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container</span><span class="o">.</span><span class="n">remove</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_container</span><span class="p">(</span><span class="n">container_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">container</span> <span class="ow">in</span> <span class="n">containers</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="n">container</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="n">container_name</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">container</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">start_container</span><span class="p">(</span><span class="n">service_name</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">docker_compose</span><span class="o">.</span><span class="n">start</span><span class="p">(</span><span class="n">service_name</span><span class="p">)</span>
</span></span></code></pre></div><p>Finally, onto our actual tests, in reality, these tests are very boring and not super useful but they should give you an
idea what you can do. Such as killing the database container and then check how your Python application responds. Then
you can start the database container and again check how your Python application responds.
You can read more about how you can test Python Flask applications with pytest
<a href="https://medium.com/@hmajid2301/testing-mocking-a-connexion-flask-application-with-pytest-bacfd07099eb">here</a>.</p>
<p>So what do our tests do? Well, the first one checks the number of containers running is equal to 2.
The next one kills <code>container1</code> and checks that only one container is running and it&rsquo;s not <code>container1</code>.
Our final test starts <code>container1</code> and checks that it is running and the number of containers running
is back to 2. After this final test has completed then the <code>setup</code> fixture will run its <code>docker_compose.shutdown()</code>
command.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_two_containers</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_kill_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">kill_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="ow">not</span> <span class="n">container1</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">test_start_container1</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="n">start_container</span><span class="p">(</span><span class="s2">&#34;service&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">containers</span> <span class="o">=</span> <span class="n">docker_client</span><span class="o">.</span><span class="n">containers</span><span class="o">.</span><span class="n">list</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="n">container1</span> <span class="o">=</span> <span class="n">get_container</span><span class="p">(</span><span class="s2">&#34;container1&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="nb">len</span><span class="p">(</span><span class="n">containers</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span>
</span></span><span class="line"><span class="cl">    <span class="k">assert</span> <span class="n">container1</span>
</span></span></code></pre></div><p>That&rsquo;s it we&rsquo;ve managed to start/stop Docker containers from within Gitlab CI, using DinD. In a future article I will
explain how you could run your tests within a Docker container you&rsquo;ve started. Say you had three containers <code>nginx</code>, <code>flask</code>
and <code>postgres</code> and you wanted to run your tests within the <code>flask</code> container. But for now that&rsquo;s it, thanks for reading!</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-06-22-how-to-use-gitlab-ci-pytest-and-docker-compose-together/source_code">Example source code</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to use DinD with Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/</link>
      <pubDate>Fri, 01 May 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-05-01-how-to-use-dind-with-gitlab-ci/</guid>
      <description>&lt;p&gt;Like most developers, we want to be able to automate as many and as much of processes as possible. Pushing Docker
images to a registry is a task that can easily be automated. In this article, we will cover how you can use
Gitlab CI to build and publish your Docker images, to the Gitlab registry. However, you can also very easily
edit this to push your images to DockerHub as well.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Like most developers, we want to be able to automate as many and as much of processes as possible. Pushing Docker
images to a registry is a task that can easily be automated. In this article, we will cover how you can use
Gitlab CI to build and publish your Docker images, to the Gitlab registry. However, you can also very easily
edit this to push your images to DockerHub as well.</p>
<p>A quick aside on terminology related to Docker:</p>
<ul>
<li>container: An instance of an image is called a container (<code>docker run</code>)</li>
<li>image: A set of immutable layers (<code>docker build</code>)</li>
<li>hub: The official registry where you can get more Docker images from (<code>docker pull</code>)</li>
</ul>
<h2 id="example">Example</h2>
<p>Here is an example <code>.gitlab-ci.yml</code> file which can be used to build and push your Docker images to the Gitlab registry.</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">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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="l">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</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">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><h2 id="explained">Explained</h2>
<p>The code above may be a bit confusing, it might be a lot to take in. So now we will break it down line by line.</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">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span></code></pre></div><p>In our first couple of lines, we define some variables which will be used by all our jobs (the variables are global).
We define a variable <code>DOCKER_DRIVER: overlay2</code>, this helps speed our Docker containers a bit because by default it
uses <code>vfs</code> which is slower
<a href="https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#using-the-overlayfs-driver">learn more here</a>.</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">random-job</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo &#34;HELLO&#34;</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Note we could just as easily define <code>variables</code> just within our job as well like you see in the example above.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><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="l">docker:dind</span><span class="w">
</span></span></span></code></pre></div><p>The next couple of lines define a service. A service is a Docker image which links during our job(s). Again in this
example, it is defined globally and will link to all of our jobs. We could very easily define it within our job just
like in the <code>variables</code> example. The <a href="https://github.com/docker-library/docker/blob/157869f94ea90e2acb4d0f77045d99079ead821c/18.02/dind/dockerd-entrypoint.sh"><code>docker:dind</code></a>
image automatically using its <code>entrypoint</code> starts a docker daemon. We need to use this daemon to build/push our
Docker images within CI.</p>
<p>The <code>docker:dind</code> (dind = Docker in Docker) image is almost identical to the <code>docker</code> image. The difference being the dind image
starts a Docker daemon. In this example, the job will use the <code>docker</code> image as the client and connect to the daemon
running in this container.</p>
<p>We could also just use the <code>dind</code> image in our job and simply start <code>dockerd</code> (&amp; = in the background) in the first line.
The <code>dockerd</code> command starts the Docker daemon as a client, so we can then communicate with the other Docker daemon.
It would achieve the same outcome. I think the service approach is a bit cleaner but as already stated either approach
would work.</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">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</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">docker:dind</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">dockerd &amp;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><blockquote>
<p>Info: One common use case of Gitlab CI services is to spin up databases like MySQL. We can then connect to it within our job, run our tests. It can simplify our jobs by quite a bit.</p>
</blockquote>
<blockquote>
<p>Note: There are several other ways we could also build/push our images. This is the <a href="https://gitlab.com/gitlab-examples/docker/blob/master/.gitlab-ci.yml">recommended approach</a>.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span></code></pre></div><p>Next, we define our stages and give them names. Each job must have a valid stage attached to it. Stages are used to
determine when a job will be run in our CI pipeline. If two jobs have the same stage, then they will run in parallel.
The stages defined earlier will run first so order does matter. However in this example, we only have one stage and
one job so this isn&rsquo;t super important, more just something to keep in mind.</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">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span></code></pre></div><p>Now we define our job, where <code>publish-docker</code> is the name of our job on <code>Gitlab CI</code> pipeline. We then define
what <code>stage</code> the job should run in, in this case, this job will run during the <code>publish</code> stage.</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">publish-docker</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><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">image</span><span class="p">:</span><span class="w"> </span><span class="l">docker</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="l">...</span><span class="w">
</span></span></span></code></pre></div><p>Then we define what Docker image to use in this job. In this job, we will use the <code>docker</code> image. This
image has all the commands we need to <code>build</code> and <code>push</code> our Docker images. It will act as the client making
requests to the <code>dind</code> daemon.</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">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>Finally, we get to the real meat and potatoes of the CI file. The bit of code that builds and pushes are Docker
images to the registry:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">export VERSION_TAG=v1.2.3</span><span class="w">
</span></span></span></code></pre></div><p>It is often a good idea to tag our images, in this case, I&rsquo;m using a release name. You could get this from say your
<code>setup.py</code> or <code>package.json</code> file as well. In my Python projects I usually use this command
<code>export VERSION_TAG=$(cat setup.py | grep version | head -1 | awk -F= '{ print $2 }' | sed 's/[&quot;,]//g' | tr -d &quot;'&quot;)</code>,
to parse my <code>setup.py</code> for the version number. But this can be whatever you want it to be. Here we have just kept it
static to make things simpler but in reality, you&rsquo;ll probably want to retrieve it programmatically (the version number).</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker login ${CI_REGISTRY} -u gitlab-ci-token -p ${CI_BUILD_TOKEN}</span><span class="w">
</span></span></span></code></pre></div><p>Then we log in to our Gitlab registry, the environment variables <code>$CI_REGISTRY</code> and <code>CI_BUILD_TOKEN</code> are predefined
Gitlab variables that are injected into our environment. You can read more about them
<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">here</a>. Since we are pushing to our Gitlab registry
we can just use the credentials defined within environment i.e. <code>username=gitlab-ci-token</code> and password a throwaway
token.</p>
<blockquote>
<p>Note: You can only do this on protected branches/tags.</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker build -t ${CI_REGISTRY_IMAGE}:latest -t ${CI_REGISTRY_IMAGE}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${CI_REGISTRY_IMAGE}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${CI_REGISTRY_IMAGE}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>Finally, we run our normal commands to build and push our images. The place where you can find your images will depend
on the project name and your username but it should follow this format</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">registry.gitlab.com/&lt;username&gt;/&lt;project_name&gt;/&lt;tag&gt;
</span></span></code></pre></div><h3 id="optional-push-to-dockerhub">(Optional) Push to DockerHub</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl">- <span class="l">docker login -u hmajid2301 -p ${DOCKER_PASSWORD}</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">export IMAGE_NAME=&#34;hmajid2301/example_project&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker build -t ${IMAGE_NAME}:latest -t ${IMAGE_NAME}:${VERSION_TAG}  .</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${IMAGE_NAME}:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="l">docker push ${IMAGE_NAME}:${VERSION_TAG}</span><span class="w">
</span></span></span></code></pre></div><p>We can also push our images to DockerHub, with the code shown above. We need to first login to DockerHub. Then change
the name of our image <code>&lt;username&gt;/&lt;project_name&gt;</code>.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li>A good <a href="https://stackoverflow.com/questions/47280922/role-of-docker-in-docker-dind-service-in-gitlab-ci">Stackoverflow Post</a></li>
<li><a href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html">Gitlab CI Docs</a></li>
<li><a href="https://gitlab.com/gitlab-examples/docker/blob/master/.gitlab-ci.yml">Gitlab Example</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>How to Deploy Docz on Gitlab Pages</title>
      <link>https://haseebmajid.dev/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/</link>
      <pubDate>Sat, 28 Mar 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/</guid>
      <description>&lt;p&gt;In this article I will show you how you can deploy a Docz website on Gitlab pages, using &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;.
Most of this article should be applicable to Github pages as well.&lt;/p&gt;
&lt;h2 id=&#34;docz&#34;&gt;Docz&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.Docz.site/&#34;&gt;Docz&lt;/a&gt; is a tool powered by Gatsby, it aims to make it easier to document your project.
It uses a language called &lt;code&gt;mdx&lt;/code&gt; which is like normal markdown with some extra features, i.e. &lt;code&gt;md + jsx&lt;/code&gt;. The main
advantage of using Docz is you can render components &amp;ldquo;live&amp;rdquo;, if you put them with the &lt;code&gt;&amp;lt;playground&amp;gt;&lt;/code&gt; tags. A basic
example may look like this:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show you how you can deploy a Docz website on Gitlab pages, using <code>.gitlab-ci.yml</code>.
Most of this article should be applicable to Github pages as well.</p>
<h2 id="docz">Docz</h2>
<p><a href="https://www.Docz.site/">Docz</a> is a tool powered by Gatsby, it aims to make it easier to document your project.
It uses a language called <code>mdx</code> which is like normal markdown with some extra features, i.e. <code>md + jsx</code>. The main
advantage of using Docz is you can render components &ldquo;live&rdquo;, if you put them with the <code>&lt;playground&gt;</code> tags. A basic
example may look like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Button</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">route</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="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">import { Playground, Props } from &#39;Docz&#39;
</span></span><span class="line"><span class="cl">import { Button } from &#39;./&#39;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Button
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;Props of={Button} /&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Basic usage
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">&lt;Playground&gt;
</span></span><span class="line"><span class="cl">  &lt;Button&gt;Click me&lt;/Button&gt;
</span></span><span class="line"><span class="cl">  &lt;Button kind=&#34;secondary&#34;&gt;Click me&lt;/Button&gt;
</span></span><span class="line"><span class="cl">&lt;/Playground&gt;
</span></span></code></pre></div><p>I chose to use Docz because it was simple to set up and looks very nice. I was already writing my documentation
in markdown so it seemed like a perfect fit, even though in my use case I didn&rsquo;t use the <code>playground</code> to render
components.</p>
<h2 id="example">Example</h2>
<p>You can find an example project using <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/tree/release/1.0.2/">Docz here</a>.
This is one of my projects where I deployed the documentation using Gitlab Pages. You can
find <a href="https://stegappasaurus.haseebmajid.dev/">it here</a>.</p>
<h2 id="getting-started">Getting Started</h2>
<p>Ok, now let&rsquo;s get into how we can add Docz to an existing project. We also need to have <code>react-dom</code> and <code>react</code>
installed.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn add Docz
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># or</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">npm install Docz
</span></span></code></pre></div><p>So our <code>package.json</code> looks like:</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;name&#34;</span><span class="p">:</span> <span class="s2">&#34;example_app&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;docs-dev&#34;</span><span class="p">:</span> <span class="s2">&#34;docz dev&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;docs-build&#34;</span><span class="p">:</span> <span class="s2">&#34;docz build&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dependencies&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;Docz&#34;</span><span class="p">:</span> <span class="s2">&#34;2.2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react&#34;</span><span class="p">:</span> <span class="s2">&#34;16.9.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;react-dom&#34;</span><span class="p">:</span> <span class="s2">&#34;16.8.0&#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>We then need to create our <code>Doczrc.js</code> configuration file, like so:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">src</span><span class="o">:</span> <span class="s2">&#34;docs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">description</span><span class="o">:</span> <span class="s2">&#34;Example Documentation&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">menu</span><span class="o">:</span> <span class="p">[</span><span class="s2">&#34;Introduction&#34;</span><span class="p">,</span> <span class="s2">&#34;Contributing&#34;</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">  <span class="nx">themeConfig</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">initialColorMode</span><span class="o">:</span> <span class="s2">&#34;dark&#34;</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></code></pre></div><p>We will keep all our <code>.mdx</code> files in a folder called <code>docs</code> hence <code>src: 'docs'</code>. Each file will be shown as a page on
our website. The <code>description</code> option will be the name of our website in an open browser tab. The <code>menu</code> option is
the order in which our pages will show up in the sidebar (on the left-hand side by default). The names used in this
menu option must match the names used in the frontmatter of that file should match the front matter in <code>mdx</code> pages.
Finally, I want to use the <code>dark</code> mode by default which is the final option.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Button</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">route</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="nn">---</span><span class="w">
</span></span></span></code></pre></div><p>We can view our current <code>docz</code> website by running <code>docs-dev</code> and then go to <code>localhost:3000</code> on our dev machine.</p>
<h3 id="adding-pages">Adding Pages</h3>
<p>Ok, now let&rsquo;s add our actual &ldquo;pages&rdquo; to our Docz website. First create a new folder called <code>docs</code> in your project root.
Then we will create our first page called <code>Introduction.mdx</code> (this name doesn&rsquo;t matter so much), where the page&rsquo;s contents
look something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Introduction</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">route</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="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Example
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Welcome to this example app, in this app we will show examples.
</span></span></code></pre></div><blockquote>
<p>Note: The front matter <code>name</code> must match the name we defined in the <code>Doczrc.js</code> menu option.</p>
</blockquote>
<p>The route defines the path the user will see, i.e. in the <code>stegappasaurus</code> example this page will be shown on
<code>https://stegappasaurus.haseebmajid.dev/</code>.</p>
<p>Next let&rsquo;s create a second page called <code>Contributing.mdx</code>, which looks like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-md" data-lang="md"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">Contributing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">route</span><span class="p">:</span><span class="w"> </span><span class="l">/contributing</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh"># Contributing
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Three main ways to contribute to this project are;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Adding a new feature:** Adding a new feature to the project, such as allow encoding of audio files alongside images
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Improving a feature:** Extend/improve an existing feature, such as a small UI change
</span></span><span class="line"><span class="cl"><span class="k">-</span> **Fix an issue:** We have a list of [<span class="nt">issues</span>](<span class="na">https://gitlab.com/hmajid2301/stegappasaurus/issues</span>), or you can fix your issue.
</span></span></code></pre></div><p>This page can be found on <code>/contributing</code> i.e. <code>https://stegappasaurus.haseebmajid.dev/contributing</code>. The page may look
something like the image below. The titles are shown as sub-menus.</p>
<p><img
        loading="lazy"
        src="/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/images/docs.png"
        type=""
        alt="Example App"
        
      /></p>
<h2 id="gitlab-pages">Gitlab Pages</h2>
<p>Now that we have our Docz website and it is working locally, how can we deploy on Gitlab pages for all the world to see?
Well first we need to add a job titled <code>pages</code> to our <code>.gitlab-ci.yml</code> file, then we need to store all of our
static assets in a folder called <code>public</code> and make that an <code>artifact</code> of this job. This will tell Gitlab CI, that we
want to publish this &ldquo;website&rdquo; to Gitlab Pages. Here is an example of what it may look like:</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">pages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn docs-build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mv .Docz/dist/* public/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">public</span><span class="w">
</span></span></span></code></pre></div><p>This will publish our &ldquo;website&rdquo; to https://<username>.gitlab.io/&lt;project_name&gt;. So, for example, my username is
<code>hmajid2301</code> and my project name is <code>stegappasaurus</code> hence the website URL is <a href="https://hmajid2301.gitlab.io/stegappasaurus">https://hmajid2301.gitlab.io/stegappasaurus</a>.</p>
<h3 id="custom-domain-optional">Custom Domain (Optional)</h3>
<p>If you have your own custom domain, you can &ldquo;host&rdquo; the page under two URLs. In my case, I own <code>haseebmajid.dev</code> and I
wanted to host it under a subdomain within that domain, like so <code>https://stegappasaurus.haseebmajid.dev/</code>. Gitlab
makes this surprisingly easy to do:</p>
<blockquote>
<p>Note: In this example, I am assuming we are using a subdomain and not the root domain i.e. example_app.haseebmajid.dev not haseebmajid.dev.</p>
</blockquote>
<ul>
<li>First go to your project on Gitlab</li>
<li>Next Settings (Left sidebar) &gt; Pages &gt; New Domain (Top Righthand Corner)</li>
<li>Enter the domain name you&rsquo;d like to use, i.e. example_app.haseebmajid.dev</li>
<li>Press <code>Create New Domain</code></li>
<li>Copy the <code>TXT</code> record to verify ownership.</li>
<li>Next go your domain provider, in my case it&rsquo;s <a href="https://domains.google.com">Google</a></li>
<li>Go to your DNS settings for that domain</li>
</ul>
<p>We will now create two DNS records a TXT record which verifies ownership of the domain. Go to settings for &ldquo;pages&rdquo;
which can be found <code>https://gitlab.com/&lt;username&gt;/&lt;project_name&gt;/pages</code>. Then copy the data next to
<code>Verification status</code> into your DNS settings.</p>
<p>Next we will create a <code>CNAME</code> record, a CNAME is used to point one domain record to another, i.e. a user comes to
<code>stegappasaurus.haseebmajid.dev CNAME -&gt; hmajid2301.gitlab.io -&gt; Resolve IP Address</code>. Then the browser will take the
user to the correct IP address. You can copy the CNAME data next to the <code>DNS</code> field.</p>
<p>After we&rsquo;ve added the details it will take a few minutes for verification and for <code>Let's encrypt</code> to create a certificate
for our website. Then you should be able to view your Docz website using both domains listed under the page settings
of your project, i.e the <code>gitlab.io</code> url and your custom domain.</p>
<p>You can find more information
<a href="https://docs.gitlab.com/ee/user/project/pages/custom_domains_ssl_tls_certification/#3-set-up-dns-records-for-pages">here</a>,
with regards to Gitlab pages and custom domains.</p>
<blockquote>
<p>Note: <code>.dev</code> domains always needs to be HTTPS encrypted (need a certificate).</p>
</blockquote>
<blockquote>
<p>Note: For Google Domains this data must be added in the DNS &gt; Custom resource records.</p>
</blockquote>
<p><img
        loading="lazy"
        src="/posts/2020-03-28-how-to-deploy-docz-on-gitlab-pages/images/pages.png"
        type=""
        alt="pages"
        
      /></p>
<p>That&rsquo;s it. We have deployed a <code>docz</code> website using Gitlab CI onto Gitlab Pages and even added our custom domain to it.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/-/tree/release/1.0.2">Example Project</a></li>
<li><a href="https://docs.gitlab.com/ee/user/project/pages/custom_domains_ssl_tls_certification/#3-set-up-dns-records-for-pages">Gitlab Tutorial</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>An Example Gitlab CI file for React Native Apps</title>
      <link>https://haseebmajid.dev/posts/2020-02-23-an-example-gitlab-ci-file-for-react-native-apps/</link>
      <pubDate>Sun, 23 Feb 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-02-23-an-example-gitlab-ci-file-for-react-native-apps/</guid>
      <description>&lt;p&gt;A bit of backstory when I first started developing React Native apps, I found there weren&amp;rsquo;t
any good example of Gitlab CI files. So in this article, I will show you an example &lt;code&gt;.gitlab-ci.yml&lt;/code&gt;
file you can use with your React Native app. You can of course tweak and makes changes as required by your
project.&lt;/p&gt;
&lt;h2 id=&#34;cicd&#34;&gt;CI/CD&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Before we dive straight into the CI file itself, let&amp;rsquo;s do a quicker refresher on some basic concepts. Feel free to skip this section if you are already familiar with CI/CD, Git and Gitlab CI.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>A bit of backstory when I first started developing React Native apps, I found there weren&rsquo;t
any good example of Gitlab CI files. So in this article, I will show you an example <code>.gitlab-ci.yml</code>
file you can use with your React Native app. You can of course tweak and makes changes as required by your
project.</p>
<h2 id="cicd">CI/CD</h2>
<blockquote>
<p>Before we dive straight into the CI file itself, let&rsquo;s do a quicker refresher on some basic concepts. Feel free to skip this section if you are already familiar with CI/CD, Git and Gitlab CI.</p>
</blockquote>
<p>Continuous Integration (CI), is typically defined as making sure all code being integrated into a codebase works.
It usually involves running a set of jobs referred to as a CI pipeline. Some jobs we may run include linting our
code and running unit tests. This is usually done automatically using a tool such as Travis, Circle or even Gitlab.</p>
<p>One particularly useful use case for this is when others are adding new features to our codebase and we want to check it
still works. We can create a CI pipeline that will run unit tests against the new code automatically when a pull request
(GitHub) or merge request (Gitlab) is opened. This saves us a lot of time, rather than having to copy the new
features/code and then run the tests our selves on our machine.</p>
<p>Continuous Delivery (CD), is typically an extension of CI to make sure that you can release new changes quickly.
This means automating your release process, such that you can deploy your application at any point of time just
by clicking on a button.</p>
<p>Continuous Deployment takes CD one step further by requiring no human intervention in deploying our application.
You can read more about <a href="https://www.atlassian.com/continuous-delivery/principles/continuous-integration-vs-delivery-vs-deployment">this here</a>.</p>
<h3 id="git">Git</h3>
<p><a href="https://guides.github.com/introduction/git-handbook/">Git</a> is a version control system (VCS), it is heavily tied in with CI.
In git, we can make &ldquo;commits&rdquo; which are snapshots of our project at its current state. We can later revert back to older commits and
compare files between commits (and much more). Usually every commit we push to Gitlab triggers a CI pipeline run against that current commit.
Git also has this concept of branches, where usually the <code>master</code> branch contains our production-ready code and the other branches have
new features being worked on. When our feature branches are ready they are merged into the master branch. Usually, the CI pipeline needs
to be successfully running (green ticks) before this can happen, however.</p>
<h3 id="gitlab-ci">Gitlab CI</h3>
<p><a href="https://docs.gitlab.com/ee/ci/">Gitlab CI</a>, is defined as a YAML file. In the file, we define &ldquo;jobs&rdquo; which can do various
different task. You can read more <a href="https://docs.gitlab.com/ee/user/project/pages/getting_started_part_four.html">here</a>.
Full <a href="https://docs.gitlab.com/ee/ci/yaml/README.html">reference docs here</a>, which details all the different parameters we
can use. To use Gitlab CI within our projects is very straight forward, create a new file <code>.gitlab-ci.yml</code> in our project
root and then define our jobs (we will see this a bit later in the article).</p>
<h3 id="example">Example</h3>
<p><img
    loading="lazy"
    src="https://docs.gitlab.com/ee/ci/introduction/img/gitlab_workflow_example_11_9.png"
    alt="https://docs.gitlab.com/ee/ci/introduction/"
    
  /></p>
<p>The image above shows an example of a workflow we may use. So we create a new branch for our feature called <code>feature/add-x</code>.
We then create our commits (with our new code) and push them to Gitlab. Open a merge request, this triggers the CI
pipeline (from the <code>.gitlab-ci.yml</code>) file. In this example, the pipeline fails, perhaps because a unit test failed. This
causes the whole pipeline to fail.</p>
<p>We then fix our code so the unit tests pass and then create more commits and push them. This then triggers the
pipeline to run again, this time it passes. Now our code is ready to be reviewed and merged into the main branch.
After the code review, it will be merged onto the <code>master</code> (main) branch. Then we will trigger the deployment process,
this can also be defined within our CI file.</p>
<h2 id="gitlab-ciyml">.gitlab-ci.yml</h2>
<p>Now onto the real meat and potatoes of this article, our <code>.gitlab-ci.yml</code> file for React Native apps.
Taking a look at an <a href="https://gitlab.com/hmajid2301/stegappasaurus">example application</a>. You can find the <code>.gitlab-ci.yml</code>
and <code>package.json</code> in the appendix below or follow the link above. Now let&rsquo;s take a look the <code>.gitlab-ci.yml</code> file.</p>
<h3 id="setup">setup</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">post</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">cache</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">${CI_COMMIT_REF_SLUG}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">node_modules/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">DOCKER_DRIVER</span><span class="p">:</span><span class="w"> </span><span class="l">overlay2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn generate-dotenv</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">yarn</span><span class="w">
</span></span></span></code></pre></div><p>First, we specify an <code>image</code>, this is the default docker image we will use for our &ldquo;jobs&rdquo;.
Unless a job specifies an image explicitly in its definition it will use this one. In this
example, we will use <code>node 8</code> it already has node, npm and yarn installed. We could probably
upgrade this to <code>node 10</code> or even <code>node 12</code> (long term releases of node).</p>
<p>Next, we define all the <code>stages</code> of our pipeline, any jobs in the same stage will run
in parallel (at the same time). If a job in an earlier stage fails the pipeline won&rsquo;t carry
on to the next and will stop running at the current stage. The stages defined first such
as <code>pre</code> and <code>test</code> run before stages defined later such as <code>publish</code>.
Each job <strong>must</strong> be given a <code>stage</code>.</p>
<p>Next, we define a <code>cache</code>, we will cache the <code>node_modules</code> for
future jobs (in this pipeline). Gitlab CI injects some
<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">predefined environment variables</a>,
one of them being <code>CI_COMMIT_REF_SLUG</code>.</p>
<p>We then define a variable <code>DOCKER_DRIVER: overlay2</code>, this helps speed our
docker containers a bit because by default it uses <code>vfs</code> which is slower
<a href="https://docs.gitlab.com/ce/ci/docker/using_docker_build.html#using-the-overlayfs-driver">learn more here</a>.</p>
<p>Finally, we define <code>before_script</code> which will run before every job unless we specify a
<code>before_script</code> within the jobs themselves. In this example, we install our node_modules
using <code>yarn</code> and create a <code>.env</code> file, we need the <code>.env</code> file for a few our the jobs.
The <code>.env</code> file is used by React Native to set configuration within the app.</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;name&#34;</span><span class="p">:</span> <span class="s2">&#34;stegappasaurus&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;generate-dotenv&#34;</span><span class="p">:</span> <span class="s2">&#34;sh util/generate-dotenv.sh &gt; .env&#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>Where <code>BUGSNAG_API_KEY</code> and <code>CAT_API_KEY</code> are environment variables which are injected by
Gitlab <a href="https://docs.gitlab.com/ee/ci/variables/">more information here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">cat  <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">BUGSNAG_API_KEY=${BUGSNAG_API_KEY}
</span></span></span><span class="line"><span class="cl"><span class="s">CAT_API_KEY=${CAT_API_KEY}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><p>Where the generated <code>.env</code> file will look like.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nv">BUGSNAG_API_KEY</span><span class="o">=</span><span class="m">1232541</span>
</span></span><span class="line"><span class="cl"><span class="nv">CAT_API_KEY</span><span class="o">=</span>abxc-71379991
</span></span></code></pre></div><h3 id="jobs">jobs</h3>
<p><strong>Note:</strong> For the example application I am showing it has two branches <code>production</code> (main) and <code>master</code>.</p>
<h4 id="pre">pre</h4>
<p>Now, let&rsquo;s take a look at our jobs in the CI file. The first job is used to close
issues automatically on Gitlab if there is an issue number in the git commit.
It uses the following tool
<a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-close-issue">gitlab-auto-close-issue</a>.
Which provides a docker image which contains the script to auto-close your issues. It will also
remove labels from the issue if you want such as &ldquo;Doing&rdquo;.
This job is only run on the master branch of our project.</p>
<p>Since we don&rsquo;t need to install any dependencies to run the job <code>before_script: []</code> is an empty
list, therefore the default <code>before_script</code> defined above won&rsquo;t run in this job. Also since
we define a docker image within the job we don&rsquo;t use the default docker image <code>node:8</code>.</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">close:issue</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">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-close-issue</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">apk add --no-cache --upgrade grep</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">ISSUE=$(echo $CI_COMMIT_MESSAGE | grep -oP &#34;(?&lt;=Fixes \#)[0-9]+&#34; || echo &#39;1&#39;)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_close_issue --issue $ISSUE --remove-label &#34;Doing&#34; --remove-label &#34;To Do&#34;</span><span class="w">
</span></span></span></code></pre></div><p>The next job automatically creates a merge request (MR) if the commits are not being pushed to <code>master</code> or <code>production</code> branches.
It will create an MR as WIP with a template we defined in the <code>.gitlab</code> folder. We also set the option <code>--use-issue-name</code>
where if we have a branch called say <code>feature/#211</code> where <code>#211</code> is an issue number (for that project). It will take
certain bits of information from that issue and set it on the MR such as <code>labels</code>.
More information about the tool <a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr">gitlab-auto-mr</a>.</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">create:merge-request</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">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-mr</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">pre</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">production</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">tags</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_mr -t master -c WIP -d .gitlab/merge_request_templates/merge_request.md -r -s --use-issue-name</span><span class="w">
</span></span></span></code></pre></div><p>Where the template could look something like this.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl"><span class="gh"># Description
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">&lt;!-- please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. --&gt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gu">## Type
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Bug Fix
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> Improvement
</span></span><span class="line"><span class="cl"><span class="k">- [ ]</span> New Feature
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Fixes #<span class="cm">&lt;!-- Issue Number --&gt;</span>
</span></span></code></pre></div><h3 id="test">test</h3>
<p>This job called <code>lint</code> only runs on MRs not on the master branch i.e. it won&rsquo;t run if create an MR from master to production.
Hence the <code>except</code> clause. Finally we run the <code>lint</code> command which is defined in our <code>package.json</code> file as <code>eslint src/**/*.{ts,tx,tsx}</code>.
This will run eslint against all of the code within our <code>src</code> folder.</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">lint</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn lint</span><span class="w">
</span></span></span></code></pre></div><p>Then <code>lint:code-formatter</code> checks our code against <code>prettier</code> and see&rsquo;s if it&rsquo;s compliant with the code formatter.</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">lint:code-formatter</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn code-formatter-check</span><span class="w">
</span></span></span></code></pre></div><p>Then we check all of our TS is valid, by running <code>tsc --project . --noEmit --pretty --skipLibCheck</code>.
To make sure there aren&rsquo;t any type mismatches.</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">lint:types</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn types-check</span><span class="w">
</span></span></span></code></pre></div><p>We run our unit tests using <code>jest</code> (our test runner). We also use the <code>--silent</code> flag to
hide various warnings raised by components we are testing. Like all the other jobs in this <code>stage</code> we
only run this job in an MR.</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">tests:unit</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">merge_requests</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_REF_NAME =~ /^master/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn tests --silent</span><span class="w">
</span></span></span></code></pre></div><p>Finally, almost the same as the job above, except it only runs on the <code>master</code> branch it gets the
code coverage from unit tests and stores the result using <code>coverage</code> (with some Regex). Where the <code>coverage</code> script is
defined as <code>jest --coverage</code> in <code>package.json</code>. <a href="https://docs.gitlab.com/ee/user/project/pipelines/settings.html#test-coverage-parsing">More information here</a>.
The code coverage can be shown on a badge, such as <a href="https://gitlab.com/hmajid2301/stegappasaurus/badges/master/coverage.svg">here</a>.</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">tests:coverage</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">test</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn coverage --silent</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">coverage</span><span class="p">:</span><span class="w"> </span><span class="l">/All\sfiles.*?\s+(\d+.\d+)/</span><span class="w">
</span></span></span></code></pre></div><h3 id="publish">publish</h3>
<p>Then on to our next stage. The job below actually publishes our app to the Play Store. It will only run when we&rsquo;ve
tagged one of our commits for release i.e. <code>release/1.0.0</code>. This will only be done on the <code>production</code> branch.
We are also using another docker image which has Android and various our dependencies need for our React Native app.</p>
<p>I won&rsquo;t do a massive deep dive into this job because I&rsquo;ve already written an article about it
<a href="https://dev.to/hmajid2301/auto-publish-react-native-app-to-android-play-store-using-gitlab-ci-44mc">here</a>.
But essentially what happens is we have various variables defined in our project in Gitlab such as our Keystore stored
in base64 and the Keystore setting such as the username and password.
To use the tool to auto-publish our app I need to have a <code>play-store.json</code> file and because my app uses react-native-firebase<code>I need a</code>google-services.json` file.</p>
<p>I then generate a <code>licenses.json</code> file using the following command <code>npm-license-crawler -onlyDirectDependencies --omitVersion -json src/data/licenses.json</code>,
there is a license view within my application which lists all of the main dependencies so I can properly credit those libraries this task generates that file.</p>
<p>I then generate a <code>gradle.propeties</code> file using <code>sh util/generate-gradle-properties.sh &gt; android/gradle.properties</code>.
Very similar to the <code>.env</code> the script we looked at above. Where the file looks something like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">cat  <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">android.useAndroidX=true
</span></span></span><span class="line"><span class="cl"><span class="s">android.enableJetifier=true
</span></span></span><span class="line"><span class="cl"><span class="s">org.gradle.jvmargs=-Xms1g
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_FILE=stegappasaurus.keystore
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_PASSWORD=${ANDROID_KEYSTORE_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_ALIAS=${ANDROID_KEYSTORE_ALIAS}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_PASSWORD=${ANDROID_KEYSTORE_KEY_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><p>This means we can reference the variables for the keystore within our <code>build.gradle</code> files without needing to hardcode
the values and once again this file is generated from CI variables stored on the project itself. For example the
<code>app/build.gradle</code> I have the following defined.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">android</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">signingConfigs</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">release</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="o">(</span><span class="n">project</span><span class="o">.</span><span class="na">hasProperty</span><span class="o">(</span><span class="s2">&#34;MYAPP_RELEASE_STORE_FILE&#34;</span><span class="o">))</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">storeFile</span> <span class="nf">file</span><span class="o">(</span><span class="n">MYAPP_RELEASE_STORE_FILE</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">storePassword</span> <span class="n">MYAPP_RELEASE_STORE_PASSWORD</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyAlias</span> <span class="n">MYAPP_RELEASE_KEY_ALIAS</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyPassword</span> <span class="n">MYAPP_RELEASE_KEY_PASSWORD</span>
</span></span><span class="line"><span class="cl">            <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>We then publish the application using <code>publish-package</code> script which runs <code>yarn run bundle &amp;&amp; bash util/publish-package.sh</code>.
Where <code>publish-package.sh</code> looks like</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="nv">$CI_COMMIT_TAG</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="o">[[</span> <span class="nv">$CI_COMMIT_TAG</span> <span class="o">==</span> *<span class="s2">&#34;alpha&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Alpha&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track alpha
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">[[</span> <span class="nv">$CI_COMMIT_TAG</span> <span class="o">==</span> *<span class="s2">&#34;beta&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Beta&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track beta
</span></span><span class="line"><span class="cl"><span class="k">elif</span> <span class="o">[[</span> <span class="nv">$CI_COMMIT_TAG</span> <span class="o">==</span> *<span class="s2">&#34;release&#34;</span>* <span class="o">]]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Production&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track production
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Internal&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track internal
</span></span><span class="line"><span class="cl"><span class="k">fi</span>
</span></span></code></pre></div><p>If the git tag is <code>release/1.0.0</code> then we will publish this directly onto the production track. It also
check if the tag contains <code>alpha</code> or <code>beta</code> if so then we publish it to different tracks.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;Publishing Package: Production&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> android <span class="o">&amp;&amp;</span> ./gradlew publish --track production
</span></span></code></pre></div><p>Finally we make the <code>assets</code> and <code>build</code> folders available as
<a href="https://docs.gitlab.com/ee/user/project/pipelines/job_artifacts.html">artifacts</a> for jobs in future stages.</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">publish:android:package</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</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">reactnativecommunity/react-native-android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cd android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">base64 -d $ANDROID_KEYSTORE &gt; app/stegappasaurus.keystore</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VERSION=$(cat app.json | jq -r .version)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cat $PLAY_STORE_JSON &gt; app/play-store.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cat $FIREBASE_GOOGLE_SERVICES_JSON &gt; app/google-services.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn generate-licenses</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn generate-gradle-properties</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn publish-package --no-daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/build/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/src/main/assets/</span><span class="w">
</span></span></span></code></pre></div><h3 id="post">post</h3>
<p>Onto our final stage, the first job creates a Gitlab release. This job is again only run on release tags, but only
for &ldquo;final&rdquo; release hence the <code>except</code> clause. It won&rsquo;t run if the git tag contains <code>beta</code> or <code>alpha</code> in its name.
The <a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-release">gitlab-auto-release</a> tool much like the other
tools above is used to automate this part of the Gitlab workflow.</p>
<p>The script also can use <code>CHANGELOG.md</code>, if it follows
<a href="https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-release">keepachangelog</a> format. It takes the changelog from that
file and copies into our release. Only for the matching version name i.e. <code>release/1.0.0</code>, would look for <code>1.0.0</code> in our changelog file.
You can find an example release created by this script <a href="https://gitlab.com/hmajid2301/stegappasaurus/-/tags/release%2F1.0.1">here</a>.
Also if you specify a job name after the <code>--artifacts</code> argument it will link that jobs artifacts in this release (if it was run in the
same pipeline as this job). In this example, we want to include our Android app build (APK/AAB).</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">create:gitlab:release</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">registry.gitlab.com/gitlab-automation-toolkit/gitlab-auto-release</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">post</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">except</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_TAG =~ /beta/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">$CI_COMMIT_TAG =~ /alpha/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w"> </span><span class="p">[]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gitlab_auto_release -c CHANGELOG.md -d &#34;This was auto-generated by the gitlab-auto-release tool, https://gitlab.com/gitlab-automation-toolkit/gitlab-auto-release.&#34; --artifacts &#34;publish:android:package&#34;</span><span class="w">
</span></span></span></code></pre></div><p>Our final job in this stage again only runs on release tags. It publishes our source maps to <a href="https://www.bugsnag.com/">Bugsnag</a>.
Which is a bug tracking tool. When our app is published to the Play store the JavaScript is minified and so Bugsnag cannot
give us a proper stack trace without the source maps. We must &ldquo;tag&rdquo; each upload with a version, hence we look in <code>app.json</code> file
for the current app version. This job requires artifacts from the previous android publishing job <code>publish:android:package</code>,
hence we mark it a dependency in <code>dependencies</code>. We need access to the bundle generated in the <code>assets</code> folder from the
previous job. Rather than repeat the same &ldquo;actions&rdquo; here to generate the files we need.
To speed up our CI we will just &ldquo;copy&rdquo; the files into the job by using artifacts.</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">publish:bugsnag:soucemaps</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">post</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">apt update &amp;&amp; apt install -y jq</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">export VERSION=$(cat app.json | jq -r .version)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">curl https://upload.bugsnag.com/react-native-source-map -F apiKey=${BUGSNAG_API_KEY} -F appVersion=${VERSION} -F dev=false -F platform=android -F sourceMap=@android/app/src/main/assets/index.map -F bundle=@android/app/src/main/assets/index.bundle -F projectRoot=`pwd`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn run bugsnag-sourcemaps upload --api-key=${BUGSNAG_API_KEY} --app-version=${VERSION} --minifiedFile=android/app/build/generated/assets/react/release/index.android.bundle --source-map=android/app/build/generated/sourcemaps/react/release/index.android.bundle.map --minified-url=index.android.bundle --upload-sources</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">dependencies</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">publish:android:package</span><span class="w">
</span></span></span></code></pre></div><h3 id="other">other</h3>
<p>Finally, we have a Gitlab defined job called pages, where using Gitlab pages we will publish documentation
for this application. It will publish a static website present in the <code>public</code>. The documentation is built
using <a href="docz.site">docz</a>. By default, you can access pages at <a href="https://hmajid2301.gitlab.io/stegappasaurus">https://hmajid2301.gitlab.io/stegappasaurus</a>,
i.e. username.gitlab.io/project_name but I have a google domain and using a <code>CNAME</code> you can also view the
website on <code>https://stegappasaurus.haseebmajid.dev/</code>.</p>
<p>Since this is a special job this job is run at the very end of our pipeline, also this job on runs on the
<code>master</code> branch.</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">pages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">master</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">before_script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn docs-build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mv .docz/dist/* public/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">public</span><span class="w">
</span></span></span></code></pre></div><p>Finally it&rsquo;s done! That&rsquo;s it! That is one example of a <code>.gitlab-ci.yml</code> file you can use to for
your React Native projects.</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus">Example Project</a></li>
<li><a href="https://about.gitlab.com/images/ci/ci-cd-test-deploy-illustration_2x.png">Cover image</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using React Native with Firebase Cloud Functions and Gitlab CI</title>
      <link>https://haseebmajid.dev/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/</link>
      <pubDate>Sat, 22 Feb 2020 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/</guid>
      <description>&lt;p&gt;In this article, we will talk about how you can use React Native with &lt;a href=&#34;https://firebase.google.com/docs/functions&#34;&gt;Firebase Cloud Functions&lt;/a&gt;.
We will also go over how we can automate the process of updating the cloud functions using &lt;a href=&#34;https://docs.gitlab.com/ee/ci/&#34;&gt;Gitlab CI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://firebase.google.com/&#34;&gt;Firebase&lt;/a&gt; is a cloud-based platform developed by Google to aid in the development of Web and Mobile applications. It is tightly
coupled with the &lt;a href=&#34;https://cloud.google.com/&#34;&gt;Google Cloud Platform (GCP)&lt;/a&gt;, so much so that there are certain actions you can
only do using the GCP GUI, such as increasing the RAM of your cloud function &amp;ldquo;containers&amp;rdquo;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, we will talk about how you can use React Native with <a href="https://firebase.google.com/docs/functions">Firebase Cloud Functions</a>.
We will also go over how we can automate the process of updating the cloud functions using <a href="https://docs.gitlab.com/ee/ci/">Gitlab CI</a>.</p>
<p><a href="https://firebase.google.com/">Firebase</a> is a cloud-based platform developed by Google to aid in the development of Web and Mobile applications. It is tightly
coupled with the <a href="https://cloud.google.com/">Google Cloud Platform (GCP)</a>, so much so that there are certain actions you can
only do using the GCP GUI, such as increasing the RAM of your cloud function &ldquo;containers&rdquo;.</p>
<p><strong>Note</strong>: We will be using Typescript in this article</p>
<h2 id="firebase-cloud-functions">Firebase Cloud Functions</h2>
<p>Firebase Cloud Functions can be referred to as serverless or as Functions-as-a-service (FaaS).
This means we simply deploy our code as a function, the tool (Firebase) installs our dependencies
and set up the environment. Essentially all we manage is the &ldquo;code&rdquo; and let the platform manage the
actual server/environment.</p>
<p><strong>Note</strong>: You can deploy cloud functions in Python and Golang, however, you must do this through the
GCP GUI. The functions will show up on your Firebase GUI after you&rsquo;ve created them.
You can view your Firebase project within GCP hence you can make changes to it such
as increasing the RAM (from 512MB to 2GB) from within GCP.</p>
<p>Let&rsquo;s now take a look at a simple app we will deploy to Firebase Cloud Functions.</p>
<h3 id="structure">Structure</h3>
<p>Our project structure will look something like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── firebase.json
</span></span><span class="line"><span class="cl">├── .firebaserc
</span></span><span class="line"><span class="cl">├── functions
</span></span><span class="line"><span class="cl">│   ├── index.ts
</span></span><span class="line"><span class="cl">│   ├── middleware
</span></span><span class="line"><span class="cl">│   ├── node_modules
</span></span><span class="line"><span class="cl">│   ├── package.json
</span></span><span class="line"><span class="cl">│   ├── tsconfig.json
</span></span><span class="line"><span class="cl">│   └── yarn.lock
</span></span><span class="line"><span class="cl">├── .gitignore
</span></span><span class="line"><span class="cl">└── .gitlab-ci.yml
</span></span></code></pre></div><p>This setup will look very similar to the tutorial <a href="https://firebase.google.com/docs/functions/get-started">available here</a>.</p>
<p><img
        loading="lazy"
        src="/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/images/firebase.gif"
        type=""
        alt="Firebase Pizza"
        
      /></p>
<h3 id="firebase-misc-files">Firebase Misc Files</h3>
<p>This file contains some configuration options but for most projects, it will just contain the project name
(the one we want to publish our changes to on Firebase, as we could be working on multiple projects).</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;projects&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;default&#34;</span><span class="p">:</span> <span class="s2">&#34;ExampleProject&#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>This file is important as it defines the actions that will happen before we deploy a new version
of the cloud functions. In this case, we run <code>yarn run build</code>, within the <code>functions</code> folder.
It compiles our TypeScript (TS) into regular JavaScript (JS) so that it can be run
as a cloud function. You could do various other actions such as lint your code etc.</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;functions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;predeploy&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="s2">&#34;yarn --cwd \&#34;$RESOURCE_DIR\&#34; run build&#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><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h3 id="gitlab-ci">Gitlab CI</h3>
<p>Now you&rsquo;re probably wondering how do we get our Cloud Functions from our dev machine (computer) to the Firebase servers.
We run the <code>deploy</code> script command. Now we could do this every time we make a change, however, I prefer to automate this process.</p>
<p>We will use Gitlab CI to automatically publish changes to Firebase.
First, we will need a deploy token as we cannot enter our username and password within GitLab
CI to do this run <code>yarn firebase login:ci</code>. Then log in to your Firebase account after you&rsquo;ve done this you will get a deploy token (shown in the terminal), then;</p>
<ul>
<li>Open your Gitlab project in a web browser</li>
<li>Go to Settings (left-hand sidebar) &gt; CI/CD</li>
<li>Variables -&gt; Expand</li>
<li>Add a new variable, with Type: Variable, Key: FIREBASE_DEPLOY_TOKEN, Value: <code>your deploy token here</code>, and toggle protected and masked as true (blue).</li>
</ul>
<p>This now means you can access the token within the Gitlab CI as an environment variable,
and it will allow us to authenticate with Firebase and push changes to Firebase.</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">image</span><span class="p">:</span><span class="w"> </span><span class="l">node:8</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">publish:firebase:functions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">only</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">/^release/.*$/</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">cd functions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">yarn run deploy -m &#34;Pipeline $CI_PIPELINE_ID, build $CI_BUILD_ID&#34; --non-interactive --token $FIREBASE_DEPLOY_TOKEN</span><span class="w">
</span></span></span></code></pre></div><p>The CI file we&rsquo;ve defined means every time we commit onto the master branch it will trigger
a deployment of our code to Firebase Cloud Functions. We add a message so we know which
pipeline triggered the build <code>-m</code>. Gitlab provides some predefined<br>
<a href="https://docs.gitlab.com/ee/ci/variables/predefined_variables.html">environment variables</a>.
Two of those being the ones within our message.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">yarn run deploy -m <span class="s2">&#34;Pipeline </span><span class="nv">$CI_PIPELINE_ID</span><span class="s2">, build </span><span class="nv">$CI_BUILD_ID</span><span class="s2">&#34;</span> --non-interactive --token <span class="nv">$FIREBASE_DEPLOY_TOKEN</span>
</span></span></code></pre></div><p>When we trigger the deploy script it will look within our <code>firebase.json</code> file and then
run the <code>predeploy</code> commands, which will transpile our code from TS -&gt; JS.</p>
<h3 id="functions">Functions</h3>
<p>This folder contains our (Express) web service, i.e. it has our actual code.</p>
<p>The <code>package.json</code> file is used to install all of our dependencies inside the serverless environment.
It also defines the <code>build</code> script that will be used in the pre-deploy process before the code
is deployed to Firebase.</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="c1">// ...
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;main&#34;</span><span class="p">:</span> <span class="s2">&#34;lib/index.js&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;build&#34;</span><span class="p">:</span> <span class="s2">&#34;tsc -p . --skipLibCheck&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;deploy&#34;</span><span class="p">:</span> <span class="s2">&#34;firebase deploy --only functions&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;engines&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;node&#34;</span><span class="p">:</span> <span class="s2">&#34;8&#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>When we run the <code>build</code> script we create a <code>lib</code> folder which contains the compiled (JS). Hence the main file
is <code>lib/index.js</code>. The lib folder is created because we specify the <code>outDir</code> to be <code>lib</code> in the <code>tsconfig.json</code>.
The Firebase Cloud Functions by default uses NodeJS (as stated above this can be changed in the GCP GUI) to run
our Firebase Cloud Functions, hence our code needs to be compiled to JS from TS before we deploy it.</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;compilerOptions&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">    <span class="nt">&#34;outDir&#34;</span><span class="p">:</span> <span class="s2">&#34;lib&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</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 let&rsquo;s take a look at the &ldquo;business&rdquo; logic of the application.</p>
<p>This file contains all the core logic for our web service. Here we define
two endpoints called <code>hello</code> and <code>bye</code>. As stated earlier this will be the entry point
into our application. This is the file that will set up and start are Express server/web service within the
Firebase Cloud environment.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">express</span> <span class="kr">from</span> <span class="s2">&#34;express&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">initializeApp</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;firebase-admin&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">https</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;firebase-functions&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">ValidateToken</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;./middleware&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">initializeApp</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">ValidateToken</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/hello&#34;</span><span class="p">,</span> <span class="nx">hello</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/bye&#34;</span><span class="p">,</span> <span class="nx">bye</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">hello</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">hello</span><span class="o">:</span> <span class="sb">`Hello </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</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">function</span> <span class="nx">bye</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">bye</span><span class="o">:</span> <span class="sb">`Bye </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</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="kr">export</span> <span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">https</span><span class="p">.</span><span class="nx">onRequest</span><span class="p">(</span><span class="nx">app</span><span class="p">);</span>
</span></span></code></pre></div><p>Breaking down the file first, we set up our web service. We tell it to use the JSON
middleware alongside our custom <code>ValidateToken</code>. These will run before the request is passed
to our two endpoints helping to reduce boilerplate code, as common functionality between
endpoints can be split out into middleware functions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-js" data-lang="js"><span class="line"><span class="cl"><span class="nx">initializeApp</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">ValidateToken</span><span class="p">);</span>
</span></span></code></pre></div><p>Then we define our endpoints in this case two very simple endpoints <code>/hello</code> and <code>/bye</code>,
that receive a field called <code>name</code> in the request body, we return a <code>200</code> status
code alongside a message (returned as JSON).</p>
<p>We split out <code>hello</code> and <code>bye</code> into separate functions as it&rsquo;s a bit easier to read,
we could also split this out into separate files if the logic gets more complicated,
but in this example, it&rsquo;s simple enough to leave it all in this single file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/hello&#34;</span><span class="p">,</span> <span class="nx">hello</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/bye&#34;</span><span class="p">,</span> <span class="nx">bye</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">function</span> <span class="nx">hello</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">hello</span><span class="o">:</span> <span class="sb">`Hello </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</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">function</span> <span class="nx">bye</span><span class="p">(</span><span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span> <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">body</span><span class="p">.</span><span class="nx">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">200</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">bye</span><span class="o">:</span> <span class="sb">`Bye </span><span class="si">${</span><span class="nx">name</span><span class="si">}</span><span class="sb">`</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="kr">export</span> <span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">https</span><span class="p">.</span><span class="nx">onRequest</span><span class="p">(</span><span class="nx">app</span><span class="p">);</span>
</span></span></code></pre></div><h4 id="middleware-optional">middleware (optional)</h4>
<p>The middleware folder stores all of our server middleware, these are functions that are usually called before
every request. Hence we don&rsquo;t have to explicilty call them on all of our endpoints. <code>Express</code> handles this for
us and automatically runs the middleware before the endpoint function is called.</p>
<p>We are checking the <code>Authorization</code> token sent with the request is validate, by default
our Firebase Cloud Function endpoints are accessible by anyone. We can restrict
who has access to them by requiring the client to send a token. As you can see below we do this using Firebase&rsquo;s
own auth component.</p>
<p><strong>Note</strong>: Don&rsquo;t worry, your users don&rsquo;t need to sign up for you to &ldquo;authenticate/authorisation&rdquo; them.</p>
<h5 id="validatetokents">ValidateToken.ts</h5>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="o">*</span> <span class="kr">as</span> <span class="nx">express</span> <span class="kr">from</span> <span class="s2">&#34;express&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">auth</span> <span class="p">}</span> <span class="kr">from</span> <span class="s2">&#34;firebase-admin&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">ValidateToken</span> <span class="o">=</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">request</span>: <span class="kt">express.Request</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span>: <span class="kt">express.Response</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">next</span>: <span class="kt">express.NextFunction</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kd">let</span> <span class="nx">token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">token</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</span> <span class="p">});</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="nx">auth</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">verifyIdToken</span><span class="p">(</span><span class="nx">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nx">next</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 class="k">catch</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</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></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">ValidateToken</span><span class="p">;</span>
</span></span></code></pre></div><p>Breaking down the file, first we check if the request header contains the <code>Authorization</code> parameter
and that parameter has a form similar to
<a href="https://swagger.io/docs/specification/authentication/bearer-authentication/"><code>Bearer $TOKEN</code></a>.
If not, we return a <code>403</code> <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">HTTP error</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">  <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">token</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">authorization</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="s2">&#34;Bearer &#34;</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</span> <span class="p">});</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></code></pre></div><p>Then we use Firebase admin to verify if the token is valid. If so, we pass the request on with the <code>next()</code> function.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="nx">auth</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nx">verifyIdToken</span><span class="p">(</span><span class="nx">token</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">.</span><span class="nx">then</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nx">next</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 class="k">catch</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">response</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">403</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span> <span class="nx">code</span><span class="o">:</span> <span class="s2">&#34;unauthorized&#34;</span> <span class="p">});</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span></code></pre></div><p>Finally we have an <code>index.ts</code> to make for cleaner import/export.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">ValidateToken</span> <span class="kr">from</span> <span class="s2">&#34;./ValidateToken&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="p">{</span> <span class="nx">ValidateToken</span> <span class="p">};</span>
</span></span></code></pre></div><h2 id="react-native">React Native</h2>
<p>Next let&rsquo;s take a look at our React Native logic and how we interact with the Firebase Cloud Functions.
I created a new app using the following command:</p>
<p><code>react-native init MyAwesomeProject --template typescript</code></p>
<h3 id="structure-1">Structure</h3>
<p>This is the project structure of our React Native app:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">└── ExampleApp
</span></span><span class="line"><span class="cl">    ├── android
</span></span><span class="line"><span class="cl">    ├── app.json
</span></span><span class="line"><span class="cl">    ├── App.tsx
</span></span><span class="line"><span class="cl">    ├── babel.config.js
</span></span><span class="line"><span class="cl">    ├── .buckconfig
</span></span><span class="line"><span class="cl">    ├── .eslintrc.js
</span></span><span class="line"><span class="cl">    ├── .flowconfig
</span></span><span class="line"><span class="cl">    ├── .gitattributes
</span></span><span class="line"><span class="cl">    ├── .gitignore
</span></span><span class="line"><span class="cl">    ├── index.js
</span></span><span class="line"><span class="cl">    ├── ios
</span></span><span class="line"><span class="cl">    ├── LICENSE
</span></span><span class="line"><span class="cl">    ├── metro.config.js
</span></span><span class="line"><span class="cl">    ├── node_modules
</span></span><span class="line"><span class="cl">    ├── package.json
</span></span><span class="line"><span class="cl">    ├── .prettierrc.js
</span></span><span class="line"><span class="cl">    ├── README.md
</span></span><span class="line"><span class="cl">    ├── template.config.js
</span></span><span class="line"><span class="cl">    ├── __tests__
</span></span><span class="line"><span class="cl">    ├── tsconfig.json
</span></span><span class="line"><span class="cl">    ├── .watchmanconfig
</span></span><span class="line"><span class="cl">    └── yarn.lock
</span></span></code></pre></div><p>This file contains most of our logic:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span><span class="nx">ApiResponse</span><span class="p">,</span> <span class="nx">create</span><span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;apisauce&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="nx">React</span> <span class="kr">from</span> <span class="s1">&#39;react&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span><span class="nx">Button</span><span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;react-native&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span><span class="nx">firebase</span><span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;@react-native-firebase/auth&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Make Request&#34;</span> <span class="na">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">makeRequest</span><span class="p">()}&gt;&lt;/</span><span class="nt">Button</span><span class="p">&gt;</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="kr">async</span> <span class="kd">function</span> <span class="nx">makeRequest() {</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">userCredentials</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">firebase</span><span class="p">.</span><span class="nx">auth</span><span class="p">().</span><span class="nx">signInAnonymously</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">userCredentials</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">getIdToken</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">create</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">baseURL</span><span class="o">:</span> <span class="s1">&#39;https://us-central1-exampleapp.cloudfunctions.net&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">headers</span><span class="o">:</span> <span class="p">{</span><span class="nx">Authorization</span><span class="o">:</span> <span class="sb">`Bearer </span><span class="si">${</span><span class="nx">token</span><span class="si">}</span><span class="sb">`</span><span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">timeout</span>: <span class="kt">10000</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="k">try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kr">const</span> <span class="nx">response</span>: <span class="kt">ApiResponse</span><span class="o">&lt;</span><span class="p">{</span><span class="nx">hello</span>: <span class="kt">string</span><span class="p">}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s1">&#39;/hello&#39;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">name</span><span class="o">:</span> <span class="s1">&#39;Haseeb&#39;</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="kr">const</span> <span class="p">{</span><span class="nx">data</span><span class="p">,</span> <span class="nx">ok</span><span class="p">,</span> <span class="nx">status</span><span class="p">}</span> <span class="o">=</span> <span class="nx">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">&#39;Success&#39;</span><span class="p">,</span> <span class="nx">status</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;error&#39;</span><span class="p">,</span> <span class="nx">status</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 class="k">catch</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s1">&#39;Error thrown&#39;</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></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">App</span><span class="p">;</span>
</span></span></code></pre></div><p>The main page will have a single button which when pressed will make a request to our Firebase Cloud Functions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">App</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="p">&lt;</span><span class="nt">Button</span> <span class="na">title</span><span class="o">=</span><span class="s">&#34;Make Request&#34;</span> <span class="na">onPress</span><span class="o">=</span><span class="p">{()</span> <span class="o">=&gt;</span> <span class="nx">makeRequest</span><span class="p">()}&gt;&lt;/</span><span class="nt">Button</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>Then in the <code>makeRequest()</code> function we use
<a href="https://invertase.io/oss/react-native-firebase/">react-native-firebase</a> for the authentication (optional)
if you set up the authentication middleware in the firebase functions. You can use the
<a href="https://invertase.io/oss/react-native-firebase/quick-start/existing-project">following tutorial</a> to get
started with the library.
The following allows any user of our app to get a token we can send with our HTTP request.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">userCredentials</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">firebase</span><span class="p">.</span><span class="nx">auth</span><span class="p">().</span><span class="nx">signInAnonymously</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">userCredentials</span><span class="p">.</span><span class="nx">user</span><span class="p">.</span><span class="nx">getIdToken</span><span class="p">();</span>
</span></span></code></pre></div><p>We use <code>apisauce</code> to make HTTP requests, but first we must &ldquo;create&rdquo; an API object. Here is where we pass our auth token.</p>
<p><strong>NOTE</strong>: Remember to replace <code>baseURL</code> with your URL.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">api</span> <span class="o">=</span> <span class="nx">create</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">baseURL</span><span class="o">:</span> <span class="s2">&#34;https://us-central1-exampleapp.cloudfunctions.net&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nx">headers</span><span class="o">:</span> <span class="p">{</span> <span class="nx">Authorization</span><span class="o">:</span> <span class="sb">`Bearer </span><span class="si">${</span><span class="nx">token</span><span class="si">}</span><span class="sb">`</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nx">timeout</span>: <span class="kt">10000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></div><p>Then we specify the <code>/hello</code> endpoint. The response contains a few parameters, if <code>ok</code> is set to <code>true</code>
then the request was successful (<code>2xx</code> HTTP code).</p>
<p>We then log the response from the server. In reality you will want to do something more
useful than that but this is just a simple example.
All of this code is all surrounded by a try catch so if a reject promise is returned, it will be
captured by the <code>catch</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">const</span> <span class="nx">response</span>: <span class="kt">ApiResponse</span><span class="o">&lt;</span><span class="p">{</span> <span class="nx">hello</span>: <span class="kt">string</span> <span class="p">}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="s2">&#34;/hello&#34;</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">name</span><span class="o">:</span> <span class="s2">&#34;Haseeb&#34;</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="kr">const</span> <span class="p">{</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">ok</span><span class="p">,</span> <span class="nx">status</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">response</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="nx">ok</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">&#34;Success&#34;</span><span class="p">,</span> <span class="nx">status</span><span class="p">,</span> <span class="nx">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="s2">&#34;error&#34;</span><span class="p">,</span> <span class="nx">status</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><strong>Note</strong>: Sometimes your Cloud Functions may run a bit slower the first time you call them (in a while).
You need to keep your functions &ldquo;warm&rdquo; as they say, as long as you&rsquo;re running the functions the container
they are running in stays alive, after a period of time it is destroyed and needs to be recreated, hence
after a long period of time since the function was called it may well be a few seconds slower.</p>
<p>That&rsquo;s it! Ee succesfully set up a React Native application to use Cloud Functions we deployed on
Firebase (with authentication).</p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/blog/-/tree/main/content/posts/2020-02-22-using-react-native-with-firebase-cloud-functions-and-gitlab-ci/source_code">Example source code</a></li>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus-api">Example Firebase project</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Auto Publish React Native App to Android Play Store using GitLab CI</title>
      <link>https://haseebmajid.dev/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/</link>
      <pubDate>Wed, 09 Oct 2019 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/</guid>
      <description>&lt;p&gt;In this article, I will show you how can automate the publishing of your AAB/APK to the &lt;code&gt;Google Play Console&lt;/code&gt;.
We will be using the &lt;a href=&#34;https://github.com/Triple-T/gradle-play-publisher&#34;&gt;Gradle Play Publisher&lt;/a&gt; (GPP) plugin to do
automate this process for us. Using this plugin we cannot only automate the publishing and release of our app,
we can also update the release notes, store listing (including photos) all from GitLab CI.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In this article I will assume that you are using Linux and React Native version &amp;gt;= 0.60.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article, I will show you how can automate the publishing of your AAB/APK to the <code>Google Play Console</code>.
We will be using the <a href="https://github.com/Triple-T/gradle-play-publisher">Gradle Play Publisher</a> (GPP) plugin to do
automate this process for us. Using this plugin we cannot only automate the publishing and release of our app,
we can also update the release notes, store listing (including photos) all from GitLab CI.</p>
<p><strong>Note:</strong> In this article I will assume that you are using Linux and React Native version &gt;= 0.60.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://play.google.com/apps/publish/signup/#EnterDetailsPlace">Google Developers Account</a></li>
<li>A working React Native Android project.</li>
</ul>
<h2 id="google-play-console">Google Play Console</h2>
<p>First, we need to create a service account, this account will be used to make changes to our app
automatically, such as publish it or change the store listing.</p>
<ul>
<li>Go to the <a href="https://play.google.com/apps/publish">Google Play Console</a></li>
<li>Go to &ldquo;Settings&rdquo;</li>
<li>Go to &ldquo;Developer Account&rdquo;</li>
<li>Select &ldquo;API access&rdquo;</li>
<li>Click the &ldquo;Create Service Account&rdquo; button</li>
<li>Click the &ldquo;Google API Console&rdquo; button, this will take you to the <code>Google Cloud Platform</code></li>
<li>Click &ldquo;Create Service Account&rdquo;, give your service account a <code>name</code> and a <code>description</code></li>
<li>Select the <code>Role</code> as <code>Owner</code>, select the &ldquo;Continue&rdquo; button and then finally select &ldquo;Done&rdquo;</li>
<li>After creating your service account you should have a file automatically downloading <code>api&lt;...&gt;.json</code>, if not click on &ldquo;Actions (3 Dots) &gt; Create Key&rdquo;, as shown in <code>Image 1</code></li>
<li>Now go back to &ldquo;Play Console&rdquo;</li>
<li>Go to &ldquo;Service Accounts&rdquo; (on the current page), and select &ldquo;Grant Access&rdquo;</li>
<li>Next set the permission as shown in <code>Image 2</code></li>
</ul>
<p>If you would like, you can set the permissions either globally so you can use this account for all your apps,
or you can specify an app so that you will create a new account each time you want to auto-publish a new app.
Each approach has its own advantages, the first approach is more convenient. However, the second approach is
more safe, for example, if your credentials are leaked, only one of your apps is affected.</p>
<p><img
        loading="lazy"
        src="/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/images/json-key.png"
        type=""
        alt="Image 1: Permissions"
        
      /></p>
<p><img
        loading="lazy"
        src="/posts/2019-10-09-auto-publish-react-native-app-to-android-play-store-using-gitlab-ci/images/permissions.png"
        type=""
        alt="Image 2: Permissions"
        
      /></p>
<h2 id="gitlab">GitLab</h2>
<p>Now we need the JSON file we just downloaded to be accessible by our GitLab CI jobs, where the JSON file should
look something 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;type&#34;</span><span class="p">:</span> <span class="s2">&#34;service_account&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;project_id&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;private_key_id&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;private_key&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_email&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_id&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;auth_uri&#34;</span><span class="p">:</span> <span class="s2">&#34;https://accounts.google.com/o/oauth2/auth&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;token_uri&#34;</span><span class="p">:</span> <span class="s2">&#34;https://oauth2.googleapis.com/token&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;auth_provider_x509_cert_url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://www.googleapis.com/oauth2/v1/certs&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;client_x509_cert_url&#34;</span><span class="p">:</span> <span class="s2">&#34;xxx&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Now, let&rsquo;s move the relevant keystore information to GitLab CI variables, so we can access them during our CI jobs.
First, go to your GitLab project;</p>
<ul>
<li>Settings &gt; CI/CD &gt; Variables</li>
<li>Add Type: File, Key: PLAY_STORE_JSON, Value: (the contents of your JSON file)</li>
</ul>
<p><strong>Note:</strong> When I&rsquo;m testing locally sometimes I store this JSON file locally, so I include it in my <code>.gitignore</code> (I call mine <code>play-store.json</code>) file
so it doesn&rsquo;t accidentally get published online.</p>
<h2 id="buildgradle">build.gradle</h2>
<p>We need to edit the <code>android/build.gradle</code> file first. The Maven URL is a place where we can download
the GPP plugin. The dependencies list the plugins we want to download and their versions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">buildscript</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">repositories</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="o">...</span>
</span></span><span class="line"><span class="cl">        <span class="n">maven</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">url</span> <span class="s2">&#34;https://plugins.gradle.org/m2/&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">dependencies</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">classpath</span> <span class="s2">&#34;com.android.tools.build:gradle:3.5.0&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="n">classpath</span> <span class="s2">&#34;com.github.triplet.gradle:play-publisher:2.4.1&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><h3 id="gradle-wrapperproperties">gradle-wrapper.properties</h3>
<p>To use GPP version <code>2.4.1</code> we need to use gradle version &gt;= 5.6.1. To do this we open
<code>android/gradle/wrapper/gradle-wrapper.properties</code> and edit the line <code>distributionUrl</code> so that the gradle version match
the required version for example <code>distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip</code>.</p>
<h2 id="appbuildgradle">app/build.gradle</h2>
<p>We also need to edit our <code>android/app/build.gradle</code>.
Add the <code>apply plugin: &quot;com.github.triplet.play&quot;</code> to the top of your file after <code>apply plugin: &quot;com.android.application&quot;</code>.
Then at the bottom of the file add the code below. This is what the GPP plugin uses to determine how to publish your app.
In this case, it will</p>
<ul>
<li>defaultToAppBundles: It will generate an AAB (Android App Bundle) instead of APK</li>
<li>track: Which track we should deploy the new AAB to, you can chose for example production or beta, in this case it&rsquo;s the internal track</li>
<li>serviceAccountCredentials: The location of the Play Store JSON we stored in our CI/CD variables in this example it will be kept in <code>android/app/play-store.json</code></li>
<li>resolutionStrategy: Automatically updates the app&rsquo;s version code to matches the next available number required so it&rsquo;s published</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">play</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">defaultToAppBundles</span> <span class="o">=</span> <span class="kc">true</span>
</span></span><span class="line"><span class="cl">    <span class="n">track</span> <span class="o">=</span> <span class="s2">&#34;internal&#34;</span> <span class="c1">// beta, alpha, production.
</span></span></span><span class="line"><span class="cl">    <span class="n">serviceAccountCredentials</span> <span class="o">=</span> <span class="n">file</span><span class="o">(</span><span class="s2">&#34;./play-store.json&#34;</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">resolutionStrategy</span> <span class="o">=</span> <span class="s2">&#34;auto&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">outputProcessor</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">versionNameOverride</span> <span class="o">=</span> <span class="s2">&#34;$versionNameOverride.$versionCode&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>There are numerous more options you can enable/use, such as generating a draft which you can manually publish yourself from the Play store console.
You can find more details <a href="https://github.com/Triple-T/gradle-play-publisher">here</a>, in the GPPs very well documented README.</p>
<h2 id="meta-data">Meta Data</h2>
<p>You can also use the GPP to publish meta-data about our app such as the listing, photos etc. To do this you can either
run <code>cd android &amp;&amp; ./gradlew bootstrap</code> which will initialise the directory layout you need and also pull the
information from the play store if you already have it. If you do not want to run this command, you can create a
structure similar to the one shown below in your <code>android/app/src/main/play/</code> folder. You can find more information
about meta-data <a href="https://github.com/Triple-T/gradle-play-publisher#managing-play-store-metadata">here</a>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">├── contact-email.txt
</span></span><span class="line"><span class="cl">├── contact-website.txt
</span></span><span class="line"><span class="cl">├── default-language.txt
</span></span><span class="line"><span class="cl">├── listings
</span></span><span class="line"><span class="cl">│   └── en-GB
</span></span><span class="line"><span class="cl">│       ├── full-description.txt
</span></span><span class="line"><span class="cl">│       ├── graphics
</span></span><span class="line"><span class="cl">│       │   ├── feature-graphic
</span></span><span class="line"><span class="cl">│       │   │   └── 1.png
</span></span><span class="line"><span class="cl">│       │   ├── icon
</span></span><span class="line"><span class="cl">│       │   │   └── 1.png
</span></span><span class="line"><span class="cl">│       │   └── phone-screenshots
</span></span><span class="line"><span class="cl">│       │       ├── 1.png
</span></span><span class="line"><span class="cl">│       │       ├── 2.png
</span></span><span class="line"><span class="cl">│       │       ├── 3.png
</span></span><span class="line"><span class="cl">│       │       ├── 4.png
</span></span><span class="line"><span class="cl">│       │       ├── 5.png
</span></span><span class="line"><span class="cl">│       │       └── 6.png
</span></span><span class="line"><span class="cl">│       ├── short-description.txt
</span></span><span class="line"><span class="cl">│       ├── title.txt
</span></span><span class="line"><span class="cl">│       └── video-url.txt
</span></span><span class="line"><span class="cl">├── release-names
</span></span><span class="line"><span class="cl">│   └── default.txt
</span></span><span class="line"><span class="cl">└── release-notes
</span></span><span class="line"><span class="cl">    └── en-GB
</span></span><span class="line"><span class="cl">        └── default.txt
</span></span></code></pre></div><h2 id="packagejson">package.json</h2>
<p>Add the following script to your <code>package.json</code>, this will be used within our <code>.gitlab-ci.yml</code>. This is so that we can
simply use yarn run bundle, for example instead of having to write the whole command, in our GitLab CI. Also, the other
advantage is that the command is used multiple times in our GitLab CI jobs, we only have to edit in a single location. It also
saves us having to type out the same (very long) command again.</p>
<ul>
<li>bundle: Bundles all of our react native code into a single file</li>
<li>publish-package: Will build our AAB and also publish it along with all the meta-data we include</li>
</ul>
<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="c1">// ...
</span></span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bundle&#34;</span><span class="p">:</span> <span class="s2">&#34;react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.bundle --sourcemap-output android/app/src/main/assets/index.map --assets-dest android/app/src/main/res&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;publish-package&#34;</span><span class="p">:</span> <span class="s2">&#34;cd android &amp;&amp; ./gradlew publishRelease&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="gitlab-ciyml">.gitlab-ci.yml</h2>
<p>Now finally we can add our job to our GitLab CI file.</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">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">publish</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">publish:android:package</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">publish</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">reactnativecommunity/react-native-android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">mv $PLAY_STORE_JSON android/app/play-store.json</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn bundle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn publish-package --no-daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/build/outputs/</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s break this file down line by line;
We need a Docker image which contains all the prerequisites for building our APK. A good one
to use is the <a href="https://hub.docker.com/r/reactnativecommunity/react-native-android">reactnativecommunity/react-native-android</a>.
It has everything we need for React Native/Android (Java, Android SDK etc).</p>
<p>Depending on your exact project, you may need to increase the inoitfy file watcher limit. You can do this by using
<code>echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</code>. Essentially file watchers are used
to monitor changes in the file system. You can find more information about
Linux&rsquo;s inotify <a href="https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers">here</a>.</p>
<p><code>yarn bundle</code>: We create a bundle this is where all of our (JavaScript) React Native file are bundled into a single Javascript
file.</p>
<p><code>yarn android-publish --no-daemon</code>: This builds our AAB locally and then publish it to the <code>Android Play Store</code>, to the internal testing track in this case.
It will also publish all of our meta-data for us such as the photos, store listing, contact information and release notes (etc).
Since this is a CI job we don&rsquo;t need to start a daemon, to speed up future builds, hence the <code>--no-daemon</code> argument.</p>
<p>Finally we create artifacts <code>./android/app/build/outputs/</code> so that we can download the AAB after the job has been completed. I also like to include
my artifacts (AAB) with releases of my apps within GitLab. It makes it easier to track what version of the app was released when. But if you don&rsquo;t
want the AAB/APK you can simply remove the artifacts part from the job.</p>
<p>That&rsquo;s it, we&rsquo;re done :).</p>
<p><img
    loading="lazy"
    src="https://giphy.com/gifs/13Csc1WMn9sXSM/html5"
    alt="More automation is good right?"
    
  /></p>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/tree/cde1afd6fbb9d882bccb9e05693824587ce1b77e">Example project</a></li>
<li><a href="https://github.com/Triple-T/gradle-play-publisher">Gradle Play Publisher</a></li>
</ul>
]]></content:encoded>
    </item>
    
    <item>
      <title>Using Gitlab CI to Publish an Android React Native App</title>
      <link>https://haseebmajid.dev/posts/2019-09-23-using-gitlab-ci-to-publish-an-android-react-native-app/</link>
      <pubDate>Mon, 23 Sep 2019 00:00:00 +0000</pubDate>
      <author>Me</author>
      <guid>https://haseebmajid.dev/posts/2019-09-23-using-gitlab-ci-to-publish-an-android-react-native-app/</guid>
      <description>&lt;p&gt;In this article I will show how you can use the GitLab CI with React Native to create a binary which can be published to the
Google Play Store.&lt;/p&gt;
&lt;h2 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://play.google.com/apps/publish/signup/#EnterDetailsPlace&#34;&gt;Google Developers Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;A working React Native Android project&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;keystore&#34;&gt;Keystore&lt;/h2&gt;
&lt;p&gt;First, we have to generate a keystore which we will use to sign our APK. To do this run the commands below,
follow all the instructions and keep the file safe.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>In this article I will show how you can use the GitLab CI with React Native to create a binary which can be published to the
Google Play Store.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://play.google.com/apps/publish/signup/#EnterDetailsPlace">Google Developers Account</a></li>
<li>A working React Native Android project</li>
</ul>
<hr>
<h2 id="keystore">Keystore</h2>
<p>First, we have to generate a keystore which we will use to sign our APK. To do this run the commands below,
follow all the instructions and keep the file safe.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Used to generate our keystore</span>
</span></span><span class="line"><span class="cl">keytool -genkeypair -v -keystore my-key.keystore -alias my-key-alias -keyalg RSA -keysize <span class="m">2048</span> -validity <span class="m">10000</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Used to encode our keystore in base64</span>
</span></span><span class="line"><span class="cl">base64 my-key.keystore &gt; base64-keystore.txt
</span></span></code></pre></div><h3 id="app-signing">App signing</h3>
<p>We no longer need to upload a certificate manually, as long as you &ldquo;Opt In&rdquo;, to let Google Sign your applications for you.
It will keep track of the key you used to upload your very first APK file, then it will expect you to sign all new releases
of that application using that same key, so don&rsquo;t lose your keystore. To allow Google to sign our app for us do the following;</p>
<ul>
<li>Login to <a href="https://play.google.com/apps/publish">Google Play Console</a></li>
<li>Select your application from the list</li>
<li>Select &ldquo;App Releases&rdquo;</li>
<li>Select a track, for example &ldquo;Internal test&rdquo; &gt; &ldquo;Manage&rdquo;</li>
<li>Select &ldquo;Create Release&rdquo;</li>
<li>Then where it says <code>Let Google manage and protect your app signing key (recommended)</code>, Select &ldquo;Continue&rdquo;</li>
</ul>
<p>The signing process works as follows;</p>
<ul>
<li>You digitally sign each release using your upload key (upload key being the keystore we just generated) before publishing it on the Play Console.</li>
<li>Google Play uses the upload certificate to verify your identity and then re-signs your release using the app signing key for distribution.</li>
</ul>
<h3 id="gitlab">GitLab</h3>
<p>Now, let&rsquo;s move on to the relevant keystore information to GitLab CI variables, so we can access them during our CI jobs.
First, go to your GitLab project;</p>
<ul>
<li>Settings (side menu) &gt; CI/CD &gt; Variables</li>
<li>Add Type: Variable, key: <code>ANDROID_KEYSTORE_ALIAS</code> , value: <code>my-key-alias</code></li>
<li>Add Type: Variable, key: <code>ANDROID_KEYSTORE_PASSWORD</code>, value: (whatever password you used)</li>
<li>Add Type: Variable, key: <code>ANDROID_KEYSTORE_KEY_PASSWORD</code>, value: (whatever password you used, by default it&rsquo;s the same as the <code>ANDROID_KEYSTORE_PASSWORD</code>)</li>
<li>Add Type: <code>File</code>, key: <code>ANDROID_KEYSTORE</code>, value: (copy the contents of <code>base64-keystore.txt</code>)</li>
</ul>
<p>Now we have all our keystore values/files on GitLab CI.</p>
<p><strong>Note</strong> Check the project permissions so only the relevant users can see/edit these values.
You should keep the keystore file/passwords private, make sure only the relevant users can access them.</p>
<hr>
<h2 id="appbuildgradle">app/build.gradle</h2>
<p>Next open the <code>android/app/build.gradle</code>, then add the following to the <code>android{}</code> section.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-groovy" data-lang="groovy"><span class="line"><span class="cl"><span class="n">android</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">signingConfigs</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">release</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="o">(</span><span class="n">project</span><span class="o">.</span><span class="na">hasProperty</span><span class="o">(</span><span class="s1">&#39;MYAPP_RELEASE_STORE_FILE&#39;</span><span class="o">))</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">storeFile</span> <span class="nf">file</span><span class="o">(</span><span class="n">MYAPP_RELEASE_STORE_FILE</span><span class="o">)</span>
</span></span><span class="line"><span class="cl">                <span class="n">storePassword</span> <span class="n">MYAPP_RELEASE_STORE_PASSWORD</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyAlias</span> <span class="n">MYAPP_RELEASE_KEY_ALIAS</span>
</span></span><span class="line"><span class="cl">                <span class="n">keyPassword</span> <span class="n">MYAPP_RELEASE_KEY_PASSWORD</span>
</span></span><span class="line"><span class="cl">            <span class="o">}</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">buildTypes</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">release</span> <span class="o">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">minifyEnabled</span> <span class="n">enableProguardInReleaseBuilds</span>
</span></span><span class="line"><span class="cl">            <span class="n">proguardFiles</span> <span class="nf">getDefaultProguardFile</span><span class="o">(</span><span class="s2">&#34;proguard-android.txt&#34;</span><span class="o">),</span> <span class="s2">&#34;proguard-rules.pro&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="n">signingConfig</span> <span class="n">signingConfigs</span><span class="o">.</span><span class="na">release</span>
</span></span><span class="line"><span class="cl">        <span class="o">}</span>
</span></span><span class="line"><span class="cl">    <span class="o">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span>
</span></span></code></pre></div><p>All this does is generate a signed APK so we can upload it to the Android Play Store. It will use the
values from the keystore we just generated, to sign our application. We will create a <code>gradle.propeties</code>
file so we don&rsquo;t have to store our keystore values in plain-text within the <code>app/build.gradle</code>
(The <code>gradle.properties</code> file will be generated during our CI job, using the values we stored earlier on GitLab)</p>
<p><strong>Note</strong>: I have added my <code>gradle.properties</code> file to my <code>.gitignore</code> file so it doesn&rsquo;t accidentally get committed
when I am testing out the build process locally. I recommend you do the same.</p>
<hr>
<h2 id="packagejson">package.json</h2>
<p>Add the following three scripts to your <code>package.json</code> file. This is so that we can simply use <code>yarn run bundle</code>
for example instead of having to write out the whole command, in our GitLab CI. Also, the other advantage is
if the command is used multiple times in our GitLab CI jobs, we only have to edit in a single place.</p>
<ul>
<li>build-package: Builds our APK file</li>
<li>bundle: Bundles all of our react native code into a single file</li>
<li>generate-gradle-properties: Creates a gradle.properties file for us in the <code>android folder</code>.</li>
</ul>
<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;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;android-package&#34;</span><span class="p">:</span> <span class="s2">&#34;cd android &amp;&amp; ./gradlew assembleRelease&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bundle&#34;</span><span class="p">:</span> <span class="s2">&#34;react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.bundle --sourcemap-output android/app/src/main/assets/index.map --assets-dest android/app/src/main/res&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;generate-gradle-properties&#34;</span><span class="p">:</span> <span class="s2">&#34;sh generate-gradle-properties.sh &gt; android/gradle.properties&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="err">...</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="err">...</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Where the <code>generate-gradle-propeties.sh</code> file looks something like, the code below.
The file is essentially a template file, where the <code>${variable}</code> are determined by
the environment variables set (the values we set earlier on GitLab). So, in this case,
the GitLab CI will pass in our keystore variables as environment variables and this
file will simply fill them in and will create our <code>gradle.properties</code> file.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="cp">#!/usr/bin/env bash
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">cat  <span class="s">&lt;&lt; EOF
</span></span></span><span class="line"><span class="cl"><span class="s">android.useAndroidX=true
</span></span></span><span class="line"><span class="cl"><span class="s">android.enableJetifier=true
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_FILE=my-key.keystore
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_STORE_PASSWORD=${ANDROID_KEYSTORE_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_ALIAS=${ANDROID_KEYSTORE_ALIAS}
</span></span></span><span class="line"><span class="cl"><span class="s">MYAPP_RELEASE_KEY_PASSWORD=${ANDROID_KEYSTORE_KEY_PASSWORD}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span></code></pre></div><hr>
<h2 id="gitlab-ciyml">.gitlab-ci.yml</h2>
<p>Add the following job to your <code>.gitlab-ci.yml</code> file, this job will create our APK.</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">stages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">build</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">build:android:package</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">stage</span><span class="p">:</span><span class="w"> </span><span class="l">build</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">reactnativecommunity/react-native-android</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">script</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn install</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">base64 -d $ANDROID_KEYSTORE &gt; android/app/my-key.keystore</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn generate-gradle-properties</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn bundle</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">yarn android-package --no-daemon</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">artifacts</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">paths</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span>- <span class="l">./android/app/build/outputs/</span><span class="w">
</span></span></span></code></pre></div><p>Let&rsquo;s break this job down line by line;
First we need a Docker image which contains all the prerequisites for building our APK. I think the
<a href="https://hub.docker.com/r/reactnativecommunity/react-native-android">reactnativecommunity/react-native-android</a>
has everything we need for our React Native/Android build (Java, Android SDK etc).</p>
<p>Depending on your exact project, you may need to increase the <code>inoitfy file watcher</code> limit, you can do this using
<code>echo fs.inotify.max_user_watches=524288 | tee -a /etc/sysctl.conf &amp;&amp; sysctl -p</code>. Essentially, file watchers are used
to monitor changes in the file system. You can find more information about
Linux&rsquo;s inotify <a href="https://github.com/guard/listen/wiki/Increasing-the-amount-of-inotify-watchers">here</a>.</p>
<p>We then install our project dependencies using <code>yarn install</code>.</p>
<p>We then decode the keystore file <code>base64 -d $ANDROID_KEYSTORE &gt; android/app/my-key.keystore</code> the file need to be saved in
<code>android/app</code> folder so it can be used during the building of the APK.</p>
<p>We then generate our <code>gradle.properties</code> file using <code>yarn generate-gradle-properties</code>, where we store variables required during the
build process such as the keystore passsword.</p>
<p>Then we run <code>yarn bundle</code>, which creates the bundle, where all of our (JavaScript) React Native file are bundled into a single Javascript
file.</p>
<p>Finally we run the command that will actually build our APK <code>yarn android-build-apk --no-daemon</code>. Since this is a <code>CI</code> job we
don&rsquo;t need to start a daemon, to speed up future builds hence the <code>--no-daemon</code> argument.</p>
<p>Also we make some build artifacts available so everything with in this folder, <code>./android/app/build/outputs/</code> can be accessed/downloaded
the APK after the job has completed, so we can then upload our APK manually.</p>
<h3 id="aab">AAB</h3>
<p>An AAB is the <a href="https://developer.android.com/guide/app-bundle">Android App Bundle</a>, which is now the recommended
way to upload our app to the Play Store. It has a few advantages over the APK, the main one being it usually
makes your app slimmer and takes fewer bytes on your users device&rsquo;s. Luckily for us the change in code required to
create an AAB instead of an AAB is very small.</p>
<p>All we have to do is open our <code>package.json</code> and edit the <code>android-package</code> script so that it contains the following
<code>&quot;android-package&quot;: &quot;cd android &amp;&amp; ./gradlew bundleRelease&quot;</code>. So essentially we change the Gradle target from
<code>assembleRelease</code> to <code>bundleRelease</code> and that it.</p>
<p><img
        loading="lazy"
        src="/posts/2019-09-23-using-gitlab-ci-to-publish-an-android-react-native-app/images/gitlab.gif"
        type=""
        alt="Example GitLab CI job"
        
      /></p>
<hr>
<h2 id="appendix">Appendix</h2>
<ul>
<li><a href="https://gitlab.com/hmajid2301/stegappasaurus/blob/4eb78b18e5677b9f6956750d50ea50725c4d099f">Example project</a></li>
</ul>
]]></content:encoded>
    </item>
    
  </channel>
</rss>
