<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Patrick Blesi</title>
    <description>The latest articles on Forem by Patrick Blesi (@pblesi).</description>
    <link>https://forem.com/pblesi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F211694%2Fbc2c65b6-123b-4f70-85b2-7eb16a8cccc0.jpeg</url>
      <title>Forem: Patrick Blesi</title>
      <link>https://forem.com/pblesi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/pblesi"/>
    <language>en</language>
    <item>
      <title>SSL Cert Rotation with Runbook</title>
      <dc:creator>Patrick Blesi</dc:creator>
      <pubDate>Thu, 07 Nov 2019 19:34:53 +0000</pubDate>
      <link>https://forem.com/pblesi/ssl-cert-rotation-with-runbook-2bjc</link>
      <guid>https://forem.com/pblesi/ssl-cert-rotation-with-runbook-2bjc</guid>
      <description>&lt;p&gt;SSL cert rotation is a problem that plagues nearly every web developer. Some are fortunate to work with infrastructure where an automated solution is available. This eliminates the need for tedious SSL cert management. Others are fortunate to have a team dedicated to managing infrastructure needs, so they can toss this issue to another engineer.&lt;/p&gt;

&lt;p&gt;Unfortunately, I do not fall into either of these two camps when it comes to SSL certificate management. I needed to develop my own certificate management solution to ensure new SSL certs got properly installed on my servers. This post covers how to use Braintree's &lt;a href="https://github.com/braintree/runbook"&gt;Runbook&lt;/a&gt; to automate the rotation of SSL certificates.&lt;/p&gt;

&lt;p&gt;My certificate infrastructure is based on &lt;a href="https://jamielinux.com/docs/openssl-certificate-authority/"&gt;Jamie Nguyen's OpenSSL Certificate Authority Tutorial&lt;/a&gt;. I store a root certificate authority and intermediate certificate authority on my local machine. From the intermediate CA, I issue certs for servers within my infrastructure. All servers in my infrastructure trust my intermediate CA, so they accept traffic from valid certs when connecting to other servers.&lt;/p&gt;

&lt;p&gt;SSL certificate rotation is a process that's ripe for automation. It is a tedious, repetitive process. The process is performed infrequently enough that it is easy to forget how to do it. And it is mission critical; failing to properly rotate SSL certificates almost certainly results in an outage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The automation process
&lt;/h2&gt;

&lt;p&gt;Several iterations were required before I boiled the rotation process down to a single command entered on the command line. My first pass at rotating SSL certs involved a lot of trial and error. Ultimately, I nailed down all the steps required to successfully rotate SSL certificates in my infrastructure. I took diligent notes to be well-armed for my next inevitable encounter with SSL cert rotation.&lt;/p&gt;

&lt;p&gt;On my second iteration, with notes in hand, I was able to  successfully rotate certificates with only minor tweaks to my documented process. Nevertheless, having a handful of servers still made this a tedious process. Retyping the several-dozen commmand incantations over and over for each server is not how I like to spend my afternoons. Leveraging &lt;a href="https://github.com/braintree/runbook"&gt;Runbook&lt;/a&gt; allowed me to bring this process down to single command for rotating my SSL certs. Below is the &lt;a href="https://gist.github.com/pblesi/a48e2a2c07cd22f3e0cab49d3888e724"&gt;SSL cert rotation runbook&lt;/a&gt; I developed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The runbook
&lt;/h2&gt;

&lt;p&gt;The SSL Cert rotation process breaks down into three main steps. First, a new cert needs to be generated using the intermediate CA. Second, the new cert needs to be distributed to the target server. And finally, the service must be restarted to pick up the new certificate.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://github.com/braintree/runbook"&gt;Runbook's README.md&lt;/a&gt; for details on setting up and working with Runbook.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;

&lt;p&gt;First, we must collect some input for our parameterized runbook. The two pieces of information we need are the host and service for the SSL certificate we are generating.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env ruby&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"runbook"&lt;/span&gt;

&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&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;"HOST"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# e.x. ldap01.stg&lt;/span&gt;
&lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Error no host specified using HOST env var"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;

&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&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;"SERVICE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# e.x. slapd&lt;/span&gt;
&lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Error no service specified using SERVICE env var"&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;

&lt;span class="n"&gt;local_user&lt;/span&gt; &lt;span class="o"&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;"USER"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;company_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"patricks_pickles"&lt;/span&gt;
&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/root/ca/intermediate"&lt;/span&gt;
&lt;span class="n"&gt;local_git_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_user&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/dev/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;company_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/pp-infrastructure"&lt;/span&gt;
&lt;span class="n"&gt;local_cert_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"modules/ldap/files"&lt;/span&gt;
&lt;span class="n"&gt;local_cert_file&lt;/span&gt; &lt;span class="o"&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;host&lt;/span&gt;&lt;span class="si"&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;service&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.cert.pem"&lt;/span&gt;
&lt;span class="n"&gt;staging_suffix&lt;/span&gt; &lt;span class="o"&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;company_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-staging.com"&lt;/span&gt;
&lt;span class="n"&gt;prod_suffix&lt;/span&gt; &lt;span class="o"&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;company_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We collect this info from the command line using environment variables. The reason we do not use &lt;a href="https://github.com/braintree/runbook#ask"&gt;Runbook's ask statement&lt;/a&gt; to collect this info is because these values are used prominently throughout the runbook. It makes for a cleaner implementation to collect these values on the command line and then parameterize our runbook with them before executing it.&lt;/p&gt;

&lt;p&gt;Additionally, we initialize a number of other local variables that are used throughout our runbook.&lt;/p&gt;

&lt;p&gt;All runbooks are initialized with a title. It is best-practice to provide a description of the runbook as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;runbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Renew SSL Certs"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;DESC&lt;/span&gt;&lt;span class="sh"&gt;
    This Runbook rotates SSL Certs.
&lt;/span&gt;&lt;span class="no"&gt;  DESC&lt;/span&gt;

  &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="ss"&gt;:runbook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:commands&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Setup"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ruby_command&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_sym&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&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;This runbook has a layout, which implies the runbook manages multiple panes using &lt;a href="https://github.com/tmux/tmux"&gt;tmux&lt;/a&gt;. The layout is a stacked layout where the runbook executes in the top pane and commands are sent to the bottom pane. The setup section sets the &lt;code&gt;@env&lt;/code&gt; instance variable at runtime. This value is subsequently available in all other &lt;code&gt;ruby_command&lt;/code&gt; blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificate generation
&lt;/h3&gt;

&lt;p&gt;After initial setup, the next runbook section encompasses all steps required to create the new certificate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;runbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Renew SSL Certs"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Create New Cert"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Backup and update index.txt"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;capture&lt;/span&gt; &lt;span class="sx"&gt;%Q{ls &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;/index.txt.old* | tail -n 1 | sed -E "s/.*([0-9]{2})/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sx"&gt;1/"}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;into: :backup_num&lt;/span&gt;

      &lt;span class="n"&gt;ruby_command&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="vi"&gt;@backup_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backup_num&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"cp &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/index.txt &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/index.txt.old&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@backup_num&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"cp &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/index.txt.attr &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/index.txt.attr.old&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@backup_num&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="sx"&gt;%Q{sed -i "/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt; &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upcase&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;/d" &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;/index.txt}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Generate new cert"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;ruby_command&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:stg&lt;/span&gt;
          &lt;span class="vi"&gt;@expiration_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1035&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:prod&lt;/span&gt;
          &lt;span class="vi"&gt;@expiration_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1095&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Unknown env: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;env&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="n"&gt;tmux_command&lt;/span&gt; &lt;span class="s2"&gt;"sudo openssl ca -config &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/openssl.cnf -extensions server_cert -days &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@expiration_days&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -notext -md sha256 -in &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/csr/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&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;service&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.csr.pem -out &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/certs/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&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="ss"&gt;:commands&lt;/span&gt;
        &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="s2"&gt;"Have you generated the cert?"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"sudo chmod 444 &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/certs/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

      &lt;span class="n"&gt;tmux_command&lt;/span&gt; &lt;span class="s2"&gt;"sudo openssl x509 -noout -text -in &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/certs/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&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="ss"&gt;:commands&lt;/span&gt;
      &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="s2"&gt;"Does the cert look correct?"&lt;/span&gt;

      &lt;span class="n"&gt;tmux_command&lt;/span&gt; &lt;span class="s2"&gt;"sudo openssl verify -CAfile &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/certs/ca-chain.cert.pem &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/certs/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&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="ss"&gt;:commands&lt;/span&gt;
      &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="s2"&gt;"Is the cert valid?"&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;The &lt;code&gt;user&lt;/code&gt; setter designates that all commands in this section exececute as the &lt;code&gt;root&lt;/code&gt; user. Because no host is specified, commands are executed locally. &lt;code&gt;confirm&lt;/code&gt; statements allow us to confirm everything is correct before moving on to the next step.&lt;/p&gt;

&lt;p&gt;Because the case statement references the &lt;code&gt;env&lt;/code&gt; instance variable, it must be wrapped in a &lt;code&gt;ruby_command&lt;/code&gt; block so it is executed at runtime. Because the &lt;code&gt;tmux_command&lt;/code&gt; references &lt;code&gt;@expiration_days&lt;/code&gt;, it also must be defined in the &lt;code&gt;ruby_command&lt;/code&gt; block.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uploading the certificate
&lt;/h3&gt;

&lt;p&gt;In this section we copy the newly-generated certificate to our version-controlled infrastructure management repository, upload the certificate to the server, and install the certificate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;runbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Renew SSL Certs"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Upload Cert"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Copy the cert to pp-infrastructure"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;

      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"cp &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;intermediate_ca_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/certs/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&lt;/span&gt;&lt;span class="si"&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;local_git_dir&lt;/span&gt;&lt;span class="si"&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;local_cert_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"chown &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_user&lt;/span&gt;&lt;span class="si"&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;local_user&lt;/span&gt;&lt;span class="si"&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;local_git_dir&lt;/span&gt;&lt;span class="si"&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;local_cert_path&lt;/span&gt;&lt;span class="si"&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;local_cert_file&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="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Upload the cert"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;

      &lt;span class="n"&gt;upload&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_git_dir&lt;/span&gt;&lt;span class="si"&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;local_cert_path&lt;/span&gt;&lt;span class="si"&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;local_cert_file&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="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"/home/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_user&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="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Install the cert"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;

      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"mv ~&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_user&lt;/span&gt;&lt;span class="si"&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;local_cert_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; /etc/ssl"&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"chown root:ssl-cert /etc/ssl/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"chmod 444 /etc/ssl/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"cp /etc/ssl/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; /etc/ssl/certs"&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;The &lt;code&gt;server&lt;/code&gt; setter uses entries in my &lt;code&gt;~/.ssh/config&lt;/code&gt; file to resolve the long-form host name, port, and user to log in as for a given host string passed to ssh.&lt;/p&gt;

&lt;h3&gt;
  
  
  Restarting the service
&lt;/h3&gt;

&lt;p&gt;Lastly, we restart the service, verify our new certificate is valid, and commit our certificate to version control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;runbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Renew SSL Certs"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="c1"&gt;#...&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Upload Cert"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Restart the service"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;
      &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;

      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"service &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; restart"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Validate the service cert is valid"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;ruby_command&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:stg&lt;/span&gt;
          &lt;span class="vi"&gt;@suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;staging_suffix&lt;/span&gt;
        &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="ss"&gt;:prod&lt;/span&gt;
          &lt;span class="vi"&gt;@suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prod_suffix&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
          &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="s2"&gt;"Unknown env: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;env&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="n"&gt;tmux_command&lt;/span&gt; &lt;span class="s2"&gt;"ssh &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;host&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="ss"&gt;:commands&lt;/span&gt;
        &lt;span class="n"&gt;tmux_command&lt;/span&gt; &lt;span class="s2"&gt;"openssl s_client -connect &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@suffix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:12345 -CApath /etc/ssl/certs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:commands&lt;/span&gt;

        &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="s2"&gt;"Is the cert valid?"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Commit cert changes"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_git_dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"git add &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_path&lt;/span&gt;&lt;span class="si"&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;local_cert_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="sx"&gt;%Q{git commit -m "Update &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;local_cert_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt; certificate"}&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"git push"&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;Including the cert validation step allows us to confirm that the new cert is working as expected so we don't encounter an issue when the old certificate expires.&lt;/p&gt;

&lt;p&gt;With our runbook in hand, rotating SSL certs is as simple as invoking the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ldap01.stg &lt;span class="nv"&gt;SERVICE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;slapd runbook &lt;span class="nb"&gt;exec &lt;/span&gt;runbooks/renew_ssl_certs.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The full runbook for rotating SSL certs is available as a &lt;a href="https://gist.github.com/pblesi/a48e2a2c07cd22f3e0cab49d3888e724"&gt;gist&lt;/a&gt;. This runbook likely won't meet your SSL rotation needs as is. But hopefully it can serve as a basis for automating your SSL cert rotation process, so you are no longer plagued with manually managing SSL certificates.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>devops</category>
      <category>productivity</category>
      <category>sre</category>
    </item>
    <item>
      <title>Runbook: A Ruby DSL for Gradual System Automation</title>
      <dc:creator>Patrick Blesi</dc:creator>
      <pubDate>Wed, 14 Aug 2019 15:25:50 +0000</pubDate>
      <link>https://forem.com/pblesi/runbook-a-ruby-dsl-for-gradual-system-automation-232l</link>
      <guid>https://forem.com/pblesi/runbook-a-ruby-dsl-for-gradual-system-automation-232l</guid>
      <description>&lt;p&gt;At Braintree, we like to write tools to automate our work. Our latest tool is &lt;a href="https://github.com/braintree/runbook"&gt;Runbook&lt;/a&gt;, a Ruby DSL for gradually automating system operations. &lt;/p&gt;

&lt;p&gt;I know what you’re thinking: &lt;em&gt;Why build yet another tool to automate an engineer’s job? We already have bash scripts!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;First, anyone who has tried writing a for-loop in bash will admit it's not intuitive (I have to look it up &lt;em&gt;every&lt;/em&gt; time!). Second, even when scripting out solutions to common maintenance operations, there are often setup, teardown, and verification steps that are required to ensure the operation ran successfully. How many times have you run into issues forgetting to execute a setup or cleanup step that’s required for your maintenance script? How many times have you forgotten to verify that an operation has succeeded?&lt;/p&gt;

&lt;p&gt;We can often mitigate these kinds of issues with good documentation. The problem with software documentation, as we know, is that it can become outdated over time if the maintainers neglect to update it. &lt;/p&gt;

&lt;p&gt;How often have you scripted a maintenance operation only to have it become outdated and break six months later? Inevitably, you break out the editor and perform script surgery in an effort to recover from the failed state. &lt;/p&gt;

&lt;p&gt;Runbook addresses these types of issues by providing a framework that tightly couples the documentation and code for an operation. It also allows you to progressively automate your operations, finding the right balance between full automation and human involvement.&lt;/p&gt;

&lt;p&gt;The philosophy of Runbook is heavily aligned with Dan Slimmon's &lt;a href="https://blog.danslimmon.com/2019/07/15/do-nothing-scripting-the-key-to-gradual-automation/"&gt;Do-nothing scripting&lt;/a&gt; and Atul Gawande's &lt;a href="http://atulgawande.com/book/the-checklist-manifesto/"&gt;The Checklist Manifesto&lt;/a&gt;. It is designed to minimize &lt;a href="https://landing.google.com/sre/sre-book/chapters/eliminating-toil/"&gt;Toil&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Runbook is not intended to replace more special-purpose automation solutions such as configuration management solutions (Puppet, Chef, Ansible, Salt), deployment solutions (Capistrano, Kubernetes, Docker Swarm), monitoring solutions (Nagios, Datadog), or local command execution (Rake tasks, Make). Instead Runbook is best used as a glue when needing to accomplish a task that cuts across these domains.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple runbook
&lt;/h2&gt;

&lt;p&gt;A runbook outlines a list of steps required to perform an operation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# restart_nginx.rb&lt;/span&gt;

&lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Restart Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;DESC&lt;/span&gt;&lt;span class="sh"&gt;
This is a simple runbook to restart nginx
&lt;/span&gt;&lt;span class="no"&gt;  DESC&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Restart Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Stop Nginx"&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Wait for requests to drain"&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Start Nginx"&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;It can be compiled and used to generate a Markdown checklist or be interactively executed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Restart Nginx&lt;/span&gt;

This is a simple runbook to restart nginx

&lt;span class="gu"&gt;## 1. Restart Nginx&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; [] Stop Nginx
&lt;span class="p"&gt;
2.&lt;/span&gt; [] Wait for requests to drain
&lt;span class="p"&gt;
3.&lt;/span&gt; [] Start Nginx
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Yct4f-pM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kfzjpyzqz1n0o3yw6nhe.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Yct4f-pM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/kfzjpyzqz1n0o3yw6nhe.gif" alt="An example of using Runbook as an interactive checklist"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding automation
&lt;/h2&gt;

&lt;p&gt;Moving past this initial outline, one can start to build automation into their runbook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# restart_nginx.rb&lt;/span&gt;

&lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Restart Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;DESC&lt;/span&gt;&lt;span class="sh"&gt;
This is a simple runbook to restart nginx and
verify it starts successfully
&lt;/span&gt;&lt;span class="no"&gt;  DESC&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Restart Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="s2"&gt;"app01.prod"&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="s2"&gt;"root"&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Stop Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="s2"&gt;"Stopping Nginx..."&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"service nginx stop"&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="sx"&gt;%q{service nginx status | grep "not running"}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Start Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;note&lt;/span&gt; &lt;span class="s2"&gt;"Starting Nginx..."&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"service nginx start"&lt;/span&gt;
      &lt;span class="n"&gt;assert&lt;/span&gt; &lt;span class="sx"&gt;%q{service nginx status | grep "is running"}&lt;/span&gt;
      &lt;span class="n"&gt;confirm&lt;/span&gt; &lt;span class="s2"&gt;"Nginx is taking traffic?"&lt;/span&gt;
      &lt;span class="n"&gt;notice&lt;/span&gt; &lt;span class="s2"&gt;"Make sure to report why you restarted nginx"&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;Notice that this runbook includes the step &lt;code&gt;confirm "Nginx is taking traffic?"&lt;/code&gt;. You can easily put off scripting steps that are more difficult to automate by delegating that step to the person executing the runbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;Some of Runbook's features include:&lt;/p&gt;

&lt;h3&gt;
  
  
  SSH integration
&lt;/h3&gt;

&lt;p&gt;Runbook integrates with SSH using &lt;a href="https://github.com/capistrano/sshkit"&gt;SSHKit&lt;/a&gt; to provide support for executing commands on remote servers, downloading and uploading files, and capturing output from remotely executed commands. You can control the parallelization strategy for execution, executing in parallel, serially, or in groups.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Restart Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Restart Services"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;servers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;"app&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rjust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.prod"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;parallelization&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;strategy: :groups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;wait: &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="s2"&gt;"Restart services"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"service nginx restart"&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;The above example executes &lt;code&gt;service nginx restart&lt;/code&gt; across &lt;code&gt;app{01..50}.prod&lt;/code&gt; on five servers at a time, waiting 2 seconds between each execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic control flow
&lt;/h3&gt;

&lt;p&gt;We designed Runbook's &lt;a href="https://en.wikipedia.org/wiki/Control_flow"&gt;control flow&lt;/a&gt; to be dynamic; at any point you can skip steps, jump to any step (even a previous step), or exit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--48BgFydA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/43a8v42b4t0cry552puh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--48BgFydA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/43a8v42b4t0cry552puh.png" alt="A menu displaying Runbook's dynamic control flow options"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Runbook saves its state between each step of the runbook, and it can restart from where it left off if an error occurs while executing the runbook. In fact, you can resume a stopped runbook at any point in its execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Noop and auto modes
&lt;/h3&gt;

&lt;p&gt;Runbook provides both a noop and an auto mode. Noop mode allows you to verify the operations your runbook will run before you execute it. Auto mode will execute your runbook, requiring no human interaction. Any prompts you have added to your runbook will use the provided default values, or the execution will immediately fail if prompts exist without defaults.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--US4-jy2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/41j4u0eq0kayrdusss1l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--US4-jy2Q--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/41j4u0eq0kayrdusss1l.png" alt="An example of Runbook running in noop mode"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Execution lifecycle hooks
&lt;/h3&gt;

&lt;p&gt;Runbook provides support for before, around, and after execution hooks. You can alter and augment your runbook behavior by hooking into the execution of entities and statements in your runbook. Hooks can be used to provide a rich set of behavior such as timing the execution of steps of a runbook or the runbook as a whole, tracking the frequency of execution of a runbook, and notifying Slack when a runbook has completed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Runs&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;SSHKit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_hook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;:notify_slack_of_execution_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:around&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Entities&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;
  &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
  &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:noop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Runbook &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: took &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; seconds to execute!"&lt;/span&gt;
    &lt;span class="n"&gt;notify_slack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&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;h3&gt;
  
  
  First-class tmux support
&lt;/h3&gt;

&lt;p&gt;At Braintree we live on a steady diet of vim and &lt;a href="https://github.com/tmux/tmux"&gt;tmux&lt;/a&gt;. Consequently, Runbook provides first-class support for executing commands within a tmux. When specifying your runbook, you can define a tmux layout. This flexible and intuitive interface allows you to send commands to panes by name. &lt;/p&gt;

&lt;p&gt;Executing commands in separate panes is ideal for monitoring, commands that require user interaction, or commands that are prone to failure. You can then interact with the command directly, troubleshooting and resolving issues before continuing the runbook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Restart Nginx"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;layout&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;
    &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="ss"&gt;name: :top_left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;runbook_pane: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="ss"&gt;:top_right&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="ss"&gt;:middle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;name: :bottom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;directory: &lt;/span&gt;&lt;span class="s2"&gt;"/var/log"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;command: &lt;/span&gt;&lt;span class="s2"&gt;"tail -Fn 100 nginx.log"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;]]&lt;/span&gt;

  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Setup monitoring"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;tmux_command&lt;/span&gt; &lt;span class="s2"&gt;"watch 'service nginx status'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:top_right&lt;/span&gt;
      &lt;span class="n"&gt;tmux_command&lt;/span&gt; &lt;span class="s2"&gt;"vim /etc/nginx/nginx.conf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:middle&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;Runbooks remember their tmux layouts between executions. If a runbook stops unexpectedly, it will connect to the existing tmux layout when resumed as long as the tmux panes have not been altered. Additionally, runbooks offer to automatically close their tmux panes when the runbook finishes executing. &lt;/p&gt;

&lt;h3&gt;
  
  
  Ruby commands
&lt;/h3&gt;

&lt;p&gt;Runbook provides a &lt;code&gt;ruby_command&lt;/code&gt; statement to dynamically define runbook statements and their arguments. You can, for example, hit a JSON endpoint to retrieve a list of servers and then execute a command on each of those servers. Because you are working in Ruby, you have access to all the parsing and processing capabilities it provides.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'json'&lt;/span&gt;

&lt;span class="no"&gt;Runbook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;book&lt;/span&gt; &lt;span class="s2"&gt;"Restart Old Services"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="s2"&gt;"Restart week-old services"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;  
    &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="s2"&gt;"monitor01.prod"&lt;/span&gt;

      &lt;span class="n"&gt;capture&lt;/span&gt; &lt;span class="s2"&gt;"curl localhost:9200/host_ages.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="ss"&gt;into: :host_ages&lt;/span&gt;

      &lt;span class="n"&gt;ruby_command&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;rb_cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;one_week_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;week&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ago&lt;/span&gt;
        &lt;span class="n"&gt;old_hosts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host_ages&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"started"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;one_week_ago&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;old_host_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;old_hosts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;old_host_names&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="s2"&gt;"shutdown -r now"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ssh_config: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;servers: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="ss"&gt;user: &lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;}&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;h3&gt;
  
  
  Generators
&lt;/h3&gt;

&lt;p&gt;Runbook provides generators, similar to Rails, for generating runbooks, runbook extensions, and runbook-focused projects. You can even define your own generators for including team-specific customizations in your generated runbooks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--j6MEFcHQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/z1mo2z2tmhoao8g2tsch.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--j6MEFcHQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/z1mo2z2tmhoao8g2tsch.png" alt="Runbook generator options"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adaptability
&lt;/h3&gt;

&lt;p&gt;Runbook is designed to seamlessly integrate into existing infrastructure. It can be used as a Ruby library, a command line tool, or to create self-executable runbooks. Runbook adheres to universal interfaces such as the command line and ssh. Runbooks can be invoked via cron jobs and integrated into docker containers.&lt;/p&gt;

&lt;p&gt;Further, Runbook is extensible so you can augment the DSL with your own statements and functionality. The below example aliases &lt;code&gt;section&lt;/code&gt; to &lt;code&gt;s&lt;/code&gt; in the &lt;code&gt;Book&lt;/code&gt; DSL.&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;MyRunbook::Extensions&lt;/span&gt;
  &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Aliases&lt;/span&gt;
    &lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;DSL&lt;/span&gt;
      &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;s&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&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;section&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&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="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="no"&gt;Runbook&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Entities&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Book&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DSL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Aliases&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DSL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This flexibility allows you to adapt Runbook to meet any use case you encounter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check it out
&lt;/h2&gt;

&lt;p&gt;At Braintree, we use Runbook for automating our app deployment preflight checklists, on-call playbooks, system maintenance operations, SDK deployments, and more. We've found it to be instrumental in streamlining production operations, reducing human error, and increasing overall quality of life. &lt;/p&gt;

&lt;p&gt;Check out &lt;a href="https://github.com/braintree/runbook"&gt;Runbook on Github&lt;/a&gt; for more information on how you can use Runbook to streamline production operations and increase developer happiness!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was originally published on &lt;a href="https://medium.com/braintree-product-technology/https-medium-com-braintree-product-technology-runbook-be6f072cfc0d"&gt;medium&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>devops</category>
      <category>productivity</category>
      <category>sre</category>
    </item>
  </channel>
</rss>
