<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pantographe</title>
    <description>The latest articles on DEV Community by Pantographe (@pantographe).</description>
    <link>https://dev.to/pantographe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F939%2Fbc7a5dd7-5c39-428c-ae22-0c5b49509078.png</url>
      <title>DEV Community: Pantographe</title>
      <link>https://dev.to/pantographe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pantographe"/>
    <language>en</language>
    <item>
      <title>How we improved our deployment tool</title>
      <dc:creator>Nicolas Brousse</dc:creator>
      <pubDate>Fri, 21 Feb 2020 15:27:22 +0000</pubDate>
      <link>https://dev.to/pantographe/how-we-improved-our-deployment-tool-5bkg</link>
      <guid>https://dev.to/pantographe/how-we-improved-our-deployment-tool-5bkg</guid>
      <description>&lt;p&gt;&lt;em&gt;Original post: &lt;a href="https://pantographe.studio/en/blog/how-we-improved-our-deployment-tool"&gt;https://pantographe.studio/en/blog/how-we-improved-our-deployment-tool&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Today we are going to talk about our deployment process. Around a year ago, we start working on a script that helps us to manage our deployments.&lt;br&gt;
The idea is to have a unique script that manages our deployments.&lt;br&gt;
At start, the script was a quiet simple shell script that ran needed commands to make new deployments. We quickly added some actions like communicating informations about new release to &lt;a href="https://sentry.io"&gt;Sentry&lt;/a&gt;, running migrations if asked, then, at end, notifying us on our Mattermost chat when deployment is complete.&lt;/p&gt;

&lt;p&gt;But last summer we decided to completely rebuild it, by coding it in Ruby.&lt;br&gt;
The script is now split into different parts that we call layers, and we create a classic event manager to use them.&lt;br&gt;
One of the main ideas by moving to a Ruby script, is to have something more maintainable.&lt;/p&gt;

&lt;p&gt;At Pantographe we currently use &lt;em&gt;Heroku&lt;/em&gt; and &lt;em&gt;Dokku&lt;/em&gt; as hosting providers for our clients applications. So our deployer only manages this two providers, for now.&lt;/p&gt;

&lt;p&gt;Our tool main needs are the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having more consistent notifications (notifying at start and when a command failed with informations)&lt;/li&gt;
&lt;li&gt;Being able to put our &lt;a href="https://github.com/mperham/sidekiq/wiki/Deployment#heroku"&gt;&lt;code&gt;sidekiq&lt;/code&gt; workers in quiet mode at beginning of deployment&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Having a script structure that is easier to maintain&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  From Shell script to Ruby script
&lt;/h3&gt;

&lt;p&gt;So, &lt;strong&gt;why that choice?&lt;/strong&gt; I'm pretty sure that the Ruby choice is not the best one. There is better languages or tools to manage this kind of needs. But since we do Ruby all days at Pantographe, for Rails application, it's easier for our team to add new features.&lt;br&gt;
It will also allow us to add a test coverage on our script, which is always good.&lt;/p&gt;
&lt;h3&gt;
  
  
  The new tool
&lt;/h3&gt;

&lt;p&gt;Our new tool needs some features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;Logger&lt;/code&gt; object to print steps into &lt;code&gt;STDOUT&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;An event manager to easily execute commands or actions in specific order inside separate classes (layers)&lt;/li&gt;
&lt;li&gt;Separating files (layers) to store code related to Dokku, Heroku, notifiers, thirds parties, ...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having simpler files, make our code a bit easier to read and maintain.&lt;/p&gt;

&lt;p&gt;We have some events that can be handled in layers. Here are the different events names:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;configure&lt;/code&gt;: used to configure deployment (ex: setting git remote)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pre_deploy&lt;/code&gt;: to do some action just before deploy starts (ex: notifying team that a new deployment is starting)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy&lt;/code&gt;: code to run to deploy application depending on the provider (ex: &lt;code&gt;git push&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;post_deploy&lt;/code&gt;: whatever script that needs to be run after deployment has been done (ex: finalize sentry release)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy_succeeded&lt;/code&gt;: used to notify on successful deployment&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy_skipped&lt;/code&gt;: used when application is already deployed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy_failed&lt;/code&gt;: used to notify when deployment has failed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, we have 4 kinds of layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Providers&lt;/code&gt;: contains code relative to the provider like &lt;code&gt;dokku&lt;/code&gt; and &lt;code&gt;heroku&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Applications&lt;/code&gt;: contains code relative to the application to deploy (ex: &lt;code&gt;Rails&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ThirdParties&lt;/code&gt;: contains code relative to some third party services like Sentry.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Notifiers&lt;/code&gt;: contains code that will trigger notification (like on our Mattermost chat)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since &lt;code&gt;dokku&lt;/code&gt; and &lt;code&gt;heroku&lt;/code&gt; both use &lt;code&gt;git&lt;/code&gt; the same way to deploy an application, we moved &lt;code&gt;git&lt;/code&gt; related code inside a concern. We also did the same for SSH related code.&lt;/p&gt;

&lt;p&gt;Here an example of layer with our &lt;code&gt;dokku&lt;/code&gt; provider layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Deployer&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Layers&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Providers&lt;/span&gt;
      &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dokku&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Base&lt;/span&gt;
        &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Git&lt;/span&gt; &lt;span class="c1"&gt;# Git concern.&lt;/span&gt;
        &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;SSH&lt;/span&gt; &lt;span class="c1"&gt;# SSH concern.&lt;/span&gt;

        &lt;span class="n"&gt;required_env&lt;/span&gt; &lt;span class="ss"&gt;:DOKKU_HOST&lt;/span&gt; &lt;span class="c1"&gt;# Set DOKKU_HOST has a required env var.&lt;/span&gt;

        &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="ss"&gt;:configure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;priority: &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;check_env_vars!&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="ss"&gt;:configure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;priority: &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
          &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indent&lt;/span&gt; &lt;span class="s2"&gt;"Server is using &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;dokku_version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# Those two methods are accessible from other layers. It helps to&lt;/span&gt;
        &lt;span class="c1"&gt;# apply migration or restart worker process for example.&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;run_ssh&lt;/span&gt; &lt;span class="ss"&gt;:run&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;run_ssh&lt;/span&gt; &lt;span class="s2"&gt;"ps:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="kp"&gt;private&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_env_vars!&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;server_uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;

          &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;EnvVarError&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DOKKU_HOST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"must be user@git.host"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# This is called inside SSH concern.&lt;/span&gt;
        &lt;span class="c1"&gt;# By default it calls a simple `whoami`.&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_ssh_connection!&lt;/span&gt;
          &lt;span class="n"&gt;run_ssh&lt;/span&gt; &lt;span class="ss"&gt;:version&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="c1"&gt;# Overwrites default SSH concern method.&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;server_uri&lt;/span&gt;
          &lt;span class="vi"&gt;@server_uri&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="k"&gt;begin&lt;/span&gt;
            &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ssh://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"DOKKU_HOST"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
            &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dokku"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;nil?&lt;/span&gt;
            &lt;span class="n"&gt;uri&lt;/span&gt;
          &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dokku_version&lt;/span&gt;
          &lt;span class="n"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:ssh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;server_host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; version"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;All layers look more or less the same way.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitLab CI
&lt;/h3&gt;

&lt;p&gt;Having a deployment tool is a great thing. But this script main goal is to deploy automatically our Rails apps with our GitLab CI.&lt;br&gt;
Here is a configuration example, in which, we select the provider by choosing the correct image and we set required env vars for provider, application, third party and notifier layers we want to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;deploy:staging:&lt;/span&gt;
  &lt;span class="s"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;registry.domain.tld/devops/ci-deploy/dokku:latest&lt;/span&gt;
  &lt;span class="s"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="s"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CI_DEPLOY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;span class="err"&gt;  &lt;/span&gt;&lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;APP_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-project"&lt;/span&gt;               &lt;span class="c1"&gt;# Required var by Deployer&lt;/span&gt;
    &lt;span class="na"&gt;DOKKU_HOST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dokku@dokku.domain.tld"&lt;/span&gt; &lt;span class="c1"&gt;# Required by Dokku layer&lt;/span&gt;
    &lt;span class="na"&gt;AUTO_MIGRATE_ON_DEPLOY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;       &lt;span class="c1"&gt;# Optionnal in Rails layer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then in GitLab project CI settings, we could configure the following variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt;: The ssh private key that must be used for deployment with dokku&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MATTERMOST_WEBHOOK_URL&lt;/code&gt;: Mattermost webhook&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MATTERMOST_CHANNEL&lt;/code&gt;: Mattermost channel to notify&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SENTRY_AUTH_TOKEN&lt;/code&gt;: Sentry auth token. Used with &lt;code&gt;sentry-cli&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SENTRY_URL&lt;/code&gt;: Sentry instance url&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SENTRY_ORG&lt;/code&gt;: Sentry organization&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SENTRY_PROJECT&lt;/code&gt;: Sentry project&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Now we have a clean tool that every developers of our team could easily upgrade. Using a layers structure and an event manager give us the availability to easily add new providers and connect new third parties without breaking the script.&lt;/p&gt;

&lt;p&gt;Our tool is now running for months in production without issues, it's a possibility that we push it soon as open source.&lt;br&gt;
Let us know if it's something that you may be interested in 🙂&lt;br&gt;
I guess it could be also easy to make it usable with the recent feature GitHub Actions.&lt;/p&gt;

</description>
      <category>deployment</category>
      <category>ruby</category>
      <category>dokku</category>
      <category>heroku</category>
    </item>
  </channel>
</rss>
