<?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: Ken Moini</title>
    <description>The latest articles on DEV Community by Ken Moini (@kenmoini).</description>
    <link>https://dev.to/kenmoini</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%2F315850%2Fb804ce4d-4dfb-4b9a-bf6c-adc8918b06bf.png</url>
      <title>DEV Community: Ken Moini</title>
      <link>https://dev.to/kenmoini</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kenmoini"/>
    <language>en</language>
    <item>
      <title>Up and running with Patternfly 4</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Sun, 14 Jun 2020 22:26:53 +0000</pubDate>
      <link>https://dev.to/kenmoini/up-and-running-with-patternfly-4-4akj</link>
      <guid>https://dev.to/kenmoini/up-and-running-with-patternfly-4-4akj</guid>
      <description>&lt;p&gt;If you've used any Red Hat technologies in the last few years, you may have noticed a common theme.  This standardized design framework is called &lt;strong&gt;&lt;a href="https://www.patternfly.org/v4/"&gt;Patternfly&lt;/a&gt;&lt;/strong&gt; and is based on the &lt;strong&gt;Bootstrap&lt;/strong&gt; framework.  Patternfly is one of the over 1 million Open-Source projects that are backed by Red Hat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_L5dgAZ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pel89yz11253wyxceqws.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_L5dgAZ5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/pel89yz11253wyxceqws.jpg" alt="Patternfly 4 Page Example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've used Patternfly 3 before, which was basically a Bootstrap 3 theme.  Migrating to Patternfly 4 had a few changes that broke my previous workflow and assets.  It's nothing really difficult to address, though the documentation is sometimes difficult to navigate at first - it makes sense once you start to use Patternfly and understand how it was redesigned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cliff Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Patternfly 4 now has two variants: a core HTML/CSS only version, and one based on React.  I have been working on updating a PF3 site to PF4, so I used the core HTML/CSS only version - also because I don't care for Facebook and their technologies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Note that the core HTML/CSS version does NOT come with the Javascript needed to make all those assets and components work!  Accordions, dropdowns, modals - they don't work!  PF4 is BYOJS or Bring-Your-Own-JavaScript.  This was confusing at first while I wondered where the hell the JS files were...&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Development instructions tend to be unclear, with the "missing" JS this spent many cycles.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thankfully for you, I've got the cheat codes and notes so you don't have to spend as many cycles getting started with Patternfly 4!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building with Patternfly 4
&lt;/h2&gt;

&lt;p&gt;In this guide we'll be going over how to use the core HTML/CSS version of Patternfly 4 in a new web project and how to bring in the needed JavaScript and glue to make it all work!&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Create new environment
&lt;/h3&gt;

&lt;p&gt;First off, let's start with a blank canvas - assuming you already have Git, NodeJS, and NPM installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-awesome-site &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-awesome-site
git init
npm init
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After that, feel free to modify the &lt;code&gt;package.json&lt;/code&gt; file that is created to match your new project specfications.  I'd also suggest making your first commit after the init as well!&lt;/p&gt;




&lt;h3&gt;
  
  
  2) Pull in needed packages
&lt;/h3&gt;

&lt;p&gt;Now that we have a new environment, let's bring in the Patternfly 4 framework and a few other packages that'll make it all work nicely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @patternfly/patternfly &lt;span class="nt"&gt;--save&lt;/span&gt;
npm i bootstrap &lt;span class="nt"&gt;--save&lt;/span&gt;
npm i jquery &lt;span class="nt"&gt;--save&lt;/span&gt;
npm i jquery-slimscroll &lt;span class="nt"&gt;--save&lt;/span&gt; &lt;span class="c"&gt;#optional&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That'll pull in the Patternfly 4 framework, Bootstrap and its dependencies such as jQuery, and there's an optional SlimScroll package to make things look a bit nicer.&lt;/p&gt;




&lt;h3&gt;
  
  
  3) Using the Patternfly 4 Styles
&lt;/h3&gt;

&lt;p&gt;So at this point we have the assets needed to include Patternfly 4 into our project.  All we need to do is include the CSS files from the &lt;code&gt;node_modules/@patternfly/patternfly/&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/@patternfly/patternfly/patternfly.min.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/@patternfly/patternfly/patternfly-addons.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You'll also see the SCSS files available - you can include the Patternfly package in your own SCSS file, override your desired styles, and compile the CSS assets as part of your own build process if you already have something like Webpack already set up with an SCSS compiler.&lt;/p&gt;

&lt;p&gt;Alternatively, you can also modify the Patternfly package and build a new custom packaged version of Patternfly by running &lt;code&gt;npm run build&lt;/code&gt; in the Patternfly package directory - note that this is generally an anti-pattern, you don't really want to modify the package as much as you'd want to extend it because your changes could easily be overwritten next time you run &lt;code&gt;npm&lt;/code&gt; to manage packages.&lt;/p&gt;




&lt;h3&gt;
  
  
  4) Adding the JavaScript Glue
&lt;/h3&gt;

&lt;p&gt;Patternfly is a design framework - the functionality of the designed components isn't defined (which is weird from a UX perspective...).  This basically means that the controlling functions aren't defined and we need to bring in our own.  Thankfully, Patternfly is built on Bootstrap so many of the same JavaScript bits will work with little modification.&lt;/p&gt;

&lt;p&gt;Next, we'll include the needed JavaScript files from jQuery and Bootstrap - note that we're not including the Bootstrap {S}CSS, just the JavaScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- jQuery --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/jquery/dist/jquery.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Bootstrap --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- SlimScroll --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/jquery-slimscroll/jquery.slimscroll.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;






&lt;h3&gt;
  
  
  5) Real-world Example
&lt;/h3&gt;

&lt;p&gt;Now that we have all our needed components, let's go ahead and build a couple pages with Patternfly 4.&lt;/p&gt;

&lt;p&gt;The demos on the main public Patternfly 4 site have the source minified so it's difficult to build off those examples.  However, the build available at &lt;a href="https://pf4.patternfly.org/"&gt;https://pf4.patternfly.org/&lt;/a&gt; is not minified and perfect for glancing from the source!&lt;/p&gt;

&lt;h4&gt;
  
  
  Page with Side Nav and Cards
&lt;/h4&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en-us"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charSet=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"x-ua-compatible"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"ie=edge"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1, shrink-to-fit=no"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;title&lt;/span&gt; &lt;span class="na"&gt;data-react-helmet=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;HTML - Page default nav&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/@patternfly/patternfly/patternfly.min.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/@patternfly/patternfly/patternfly-addons.css"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;style &lt;/span&gt;&lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/css"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;#mainSideNav&lt;/span&gt;&lt;span class="nc"&gt;.collapse&lt;/span&gt;&lt;span class="nd"&gt;:not&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.show&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;#mainSideNav&lt;/span&gt;&lt;span class="nc"&gt;.collapse.show&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;#mainSideNav&lt;/span&gt;&lt;span class="nc"&gt;.collapsing&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100vh&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.pf-c-dropdown__menu&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.pf-c-dropdown__menu.show&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;inherit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"___gatsby"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"outline:none"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"gatsby-focus-wrapper"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ws-site-root"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"ws-fullscreen-example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"page-default-nav-example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-skip-to-content pf-c-button pf-m-primary"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#main-content-page-default-nav-example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Skip to content&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;header&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-brand"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-brand-toggle"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-button pf-m-plain"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"page-default-nav-example-nav-toggle"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Global navigation"&lt;/span&gt; &lt;span class="na"&gt;aria-expanded=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;data-toggle=&lt;/span&gt;&lt;span class="s"&gt;"collapse"&lt;/span&gt; &lt;span class="na"&gt;data-target=&lt;/span&gt;&lt;span class="s"&gt;"#mainSideNav"&lt;/span&gt; &lt;span class="na"&gt;aria-controls=&lt;/span&gt;&lt;span class="s"&gt;"mainSideNav"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-bars"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-brand-link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-brand"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/assets/images/PF-Masthead-Logo.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"PatternFly logo"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-tools"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-tools-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-button pf-m-plain"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Settings"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-cog"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-button pf-m-plain"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Help"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-icon pf-icon-help"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-tools-group"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-tools-item pf-m-hidden-on-lg"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__toggle pf-m-plain"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"page-default-nav-example-dropdown-kebab-right-aligned-1-button"&lt;/span&gt; &lt;span class="na"&gt;aria-expanded=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Actions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-ellipsis-v"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__menu pf-m-align-right"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"page-default-nav-example-dropdown-kebab-right-aligned-1-button"&lt;/span&gt; &lt;span class="na"&gt;hidden&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__menu-item"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Link&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__menu-item"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Action&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__menu-item pf-m-disabled"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;aria-disabled=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disabled link&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__menu-item"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Disabled action&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-divider"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"separator"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__menu-item"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Separated link&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__toggle pf-m-plain"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"page-default-nav-example-dropdown-button"&lt;/span&gt; &lt;span class="na"&gt;data-toggle=&lt;/span&gt;&lt;span class="s"&gt;"dropdown"&lt;/span&gt; &lt;span class="na"&gt;aria-haspopup=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;aria-expanded=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__toggle-text"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;John Smith&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__toggle-icon"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-caret-down"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-dropdown__menu"&lt;/span&gt; &lt;span class="na"&gt;aria-labelledby=&lt;/span&gt;&lt;span class="s"&gt;"page-default-nav-example-dropdown-button"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;[Panel contents here]&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-avatar"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/assets/images/img_avatar.svg"&lt;/span&gt; &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Avatar image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__sidebar collapse show"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"mainSideNav"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__sidebar-body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"page-default-nav-example-primary-nav"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Global"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;ul&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;System panel&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__link pf-m-current"&lt;/span&gt; &lt;span class="na"&gt;aria-current=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Policy&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Authentication&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Network services&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-nav__link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Server&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;main&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__main"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"main-content-page-default-nav-example"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__main-breadcrumb"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;nav&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"breadcrumb"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;ol&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__list"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Section home&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__item-divider"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-angle-right"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Section title&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__item-divider"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-angle-right"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__link"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Section title&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;li&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__item-divider"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                          &lt;span class="nt"&gt;&amp;lt;i&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"fas fa-angle-right"&lt;/span&gt; &lt;span class="na"&gt;aria-hidden=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/i&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-breadcrumb__link pf-m-current"&lt;/span&gt; &lt;span class="na"&gt;aria-current=&lt;/span&gt;&lt;span class="s"&gt;"page"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Section landing&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/ol&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/nav&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__main-section pf-m-light"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-content"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Main title&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;This is a demo of the Page component.&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;section&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-page__main-section"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-l-gallery pf-m-gutter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-l-gallery__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-card__body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a card&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-l-gallery__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-card__body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a card&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-l-gallery__item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"pf-c-card__body"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;This is a card&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/main&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- jQuery --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/jquery/dist/jquery.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Bootstrap --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- SlimScroll --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"/node_modules/jquery-slimscroll/jquery.slimscroll.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class="c"&gt;&amp;lt;!-- Custom Javascript --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;!--&lt;/span&gt;
      &lt;span class="nx"&gt;jQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;jQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.pf-c-page__sidebar-body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;slimScroll&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;var(--pf-c-page__sidebar--Width)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;//--&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That should get you something that looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W_imXgtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/z4yydubiu2978hn62rgc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W_imXgtK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/z4yydubiu2978hn62rgc.jpg" alt="Patternfly 4 Example"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Dropdown and Side Nav Toggle
&lt;/h3&gt;

&lt;p&gt;There are a few things you should note in the example above:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;#page-default-nav-example-nav-toggle&lt;/code&gt; element has the needed &lt;code&gt;data-&lt;/code&gt; and &lt;code&gt;aria-&lt;/code&gt; attributes defined to control the display toggle of &lt;code&gt;#mainSideNav&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;There is CSS defined to add some extra smooth-ness to the side navigation toggle.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Joshua Smith&lt;/code&gt; dropdown has the needed &lt;code&gt;data-&lt;/code&gt; and &lt;code&gt;aria-&lt;/code&gt; attributes defined to control the display of the dropdown.&lt;/li&gt;
&lt;li&gt;There is some CSS to control when the dropdown is displayed.&lt;/li&gt;
&lt;li&gt;There is additional JavaScript to give the side navigation a nice SlimScroll.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Basically, the Bootstrap component actions can be directly implemented in Patternfly 4 with the use of the &lt;a href="https://getbootstrap.com/docs/4.5/components/dropdowns/#via-data-attributes"&gt;&lt;code&gt;data-&lt;/code&gt; attributes&lt;/a&gt;, or with &lt;a href="https://getbootstrap.com/docs/4.5/components/dropdowns/#via-javascript"&gt;initialization in JavaScript&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;p&gt;From here, you can go about referencing the Patternfly 4 core examples available here: &lt;a href="https://pf4.patternfly.org/"&gt;https://pf4.patternfly.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the HTML set in your document, add the needed glue from the Bootstrap 4 documentation: &lt;a href="https://getbootstrap.com/docs/4.5/components/alerts/"&gt;https://getbootstrap.com/docs/4.5/components/alerts/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you get comfortable with the CSS prefixes and how things are built and set up you can move onto building it as a native Gatsby site or including the SCSS in your own builds to extend the style guidelines in your own ways!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>html</category>
      <category>css</category>
    </item>
    <item>
      <title>Custom Kubernetes Ingress Default Backend and Error Pages</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Mon, 25 May 2020 02:40:27 +0000</pubDate>
      <link>https://dev.to/kenmoini/custom-kubernetes-ingress-default-backend-and-error-pages-3alh</link>
      <guid>https://dev.to/kenmoini/custom-kubernetes-ingress-default-backend-and-error-pages-3alh</guid>
      <description>&lt;p&gt;When you're setting up a Kubernetes cluster, one of the choices you need to make is what Ingress to use - or what combination of Ingresses &lt;em&gt;(Ingressi?)&lt;/em&gt; to use.&lt;/p&gt;

&lt;p&gt;I've used a number of Ingress Controllers in Kubernetes clusters on different clouds, and they all have their place and reason.  The classic choice however, is the now native Ingress option provided by the Kubernetes project, the &lt;a href="https://kubernetes.github.io/ingress-nginx/" rel="noopener noreferrer"&gt;nginx-ingress controller&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wait, what is an Ingress?
&lt;/h2&gt;

&lt;p&gt;In the terms of a Kubernetes cluster, an &lt;strong&gt;Ingress Controller&lt;/strong&gt; is a cluster resource that will route traffic into the services in the cluster, from external requests.  This is how &lt;code&gt;mycutedogs.com&lt;/code&gt; gets translated and routed to the &lt;code&gt;side-sites/mycutedogs-com&lt;/code&gt; namespace/service in the cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blank and Bleak
&lt;/h2&gt;

&lt;p&gt;When you deploy the Nginx Ingress Controller, you'll see some very basic error pages such as this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc3pcnxdb8xm4xilwbv4j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fc3pcnxdb8xm4xilwbv4j.jpg" alt="Boooooring"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But instead, what if you could make it look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F00sk0i354th8vm3do0a9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F00sk0i354th8vm3do0a9.png" alt="Betterrr"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How this all works
&lt;/h2&gt;

&lt;p&gt;So when you deploy the Nginx Ingress Controller, it listens on the edge of your Kubernetes cluster for traffic.  If it does not have a matching service to route to, it'll throw an error.  Those errors are served traditionally from the nginx-ingress-controller container, but you can override where those errors are served from with some modifications to the &lt;strong&gt;Deployment&lt;/strong&gt; and &lt;strong&gt;ConfigMap&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In order to set up your own custom error pages you'll need to fork my repo, build your own copy of the container image with your own error HTML files, and do some modifications to the corresponding Deployment Config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Optional: Modify the error pages
&lt;/h2&gt;

&lt;p&gt;If you'd like to set your own default-backend custom error pages, you'll need to do the following steps - if not and you'd just like to use the more spiffy versions I have available, continue to the &lt;strong&gt;Deployment&lt;/strong&gt; steps&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fork my repo: &lt;a href="https://github.com/kenmoini/custom-nginx-ingress-errors" rel="noopener noreferrer"&gt;https://github.com/kenmoini/custom-nginx-ingress-errors&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Modify the error pages in the &lt;code&gt;www/&lt;/code&gt; directory as you see fit&lt;/li&gt;
&lt;li&gt;Connect to Docker Hub, Quay.io, etc to create your own available image&lt;/li&gt;
&lt;li&gt;Modify the &lt;code&gt;k8s-deployment.yaml&lt;/code&gt; file in the repo to point to your own image.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;These instructions assume that you deployed the Ingress Controller in the default &lt;code&gt;ingress-nginx&lt;/code&gt; namespace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;1)&lt;/em&gt;&lt;/strong&gt; Clone down my repo: &lt;a href="https://github.com/kenmoini/custom-nginx-ingress-errors" rel="noopener noreferrer"&gt;https://github.com/kenmoini/custom-nginx-ingress-errors&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

git clone https://github.com/kenmoini/custom-nginx-ingress-errors
&lt;span class="nb"&gt;cd &lt;/span&gt;custom-nginx-ingress-errors


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;2)&lt;/em&gt;&lt;/strong&gt; Deploy to your Kubernetes cluster - the YAML file already targets the &lt;code&gt;nginx-ingress&lt;/code&gt; namespace:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; k8s-deployment.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;3)&lt;/em&gt;&lt;/strong&gt; Modify the &lt;code&gt;ingress-nginx/ingress-nginx-controller&lt;/code&gt; &lt;strong&gt;Deployment&lt;/strong&gt; and set the value of the &lt;code&gt;--default-backend-service&lt;/code&gt; flag to the namespace/svc-name of the newly created error backend, which should be &lt;code&gt;ingress-nginx/nginx-errors&lt;/code&gt; by default.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;KUBE_EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nano"&lt;/span&gt; &lt;span class="c"&gt;# optional, switch to the Nano editor&lt;/span&gt;
kubectl edit deployment/ingress-nginx-controller &lt;span class="nt"&gt;-n&lt;/span&gt; ingress-nginx

&lt;span class="nt"&gt;---&lt;/span&gt;
apiVersion: apps/v1
kind: Deployment
&lt;span class="o"&gt;[&lt;/span&gt;...]
spec:
  &lt;span class="o"&gt;[&lt;/span&gt;...]
  template:
    spec:
      containers:
      - args:
        - /nginx-ingress-controller
        - &lt;span class="nt"&gt;--configmap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;POD_NAMESPACE&lt;span class="si"&gt;)&lt;/span&gt;/nginx-configuration
        - &lt;span class="nt"&gt;--tcp-services-configmap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;POD_NAMESPACE&lt;span class="si"&gt;)&lt;/span&gt;/tcp-services
        - &lt;span class="nt"&gt;--udp-services-configmap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;POD_NAMESPACE&lt;span class="si"&gt;)&lt;/span&gt;/udp-services
        - &lt;span class="nt"&gt;--publish-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;POD_NAMESPACE&lt;span class="si"&gt;)&lt;/span&gt;/ingress-nginx
        - &lt;span class="nt"&gt;--annotations-prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx.ingress.kubernetes.io
        - &lt;span class="nt"&gt;--default-backend-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;POD_NAMESPACE&lt;span class="si"&gt;)&lt;/span&gt;/nginx-errors
&lt;span class="o"&gt;[&lt;/span&gt;...]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;4)&lt;/em&gt;&lt;/strong&gt; Edit the &lt;code&gt;ingress-nginx/nginx-configuration&lt;/code&gt; &lt;strong&gt;ConfigMap&lt;/strong&gt; and add the key:value pair of &lt;code&gt;"custom-http-errors": "404,500,503"&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

kubectl edit cm/nginx-configuration &lt;span class="nt"&gt;-n&lt;/span&gt; ingress-nginx

&lt;span class="nt"&gt;---&lt;/span&gt;
apiVersion: v1
kind: ConfigMap
metadata:
 &lt;span class="o"&gt;[&lt;/span&gt;...]
data:
  custom-http-errors: &lt;span class="s2"&gt;"404,500,503"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;5)&lt;/em&gt;&lt;/strong&gt; Test your new error pages by navigating to the Ingress Load Balancer:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;kc get services &lt;span class="nt"&gt;-n&lt;/span&gt; ingress-nginx&lt;br&gt;
NAME            TYPE           CLUSTER-IP      EXTERNAL-IP      PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;                      AGE&lt;br&gt;
ingress-nginx   LoadBalancer   10.245.31.157   173.28.135.40   80:32563/TCP,443:30965/TCP   18d&lt;br&gt;
nginx-errors    ClusterIP      10.245.17.217   &amp;lt;none&amp;gt;           80/TCP                       117m&lt;/p&gt;

&lt;p&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-sS&lt;/span&gt; &lt;a href="http://173.28.135.40" rel="noopener noreferrer"&gt;http://173.28.135.40&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;lt;&lt;span class="o"&gt;!&lt;/span&gt;DOCTYPE html&amp;gt;&lt;br&gt;
&amp;lt;html &lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;lt;&lt;span class="nb"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;lt;meta &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"utf-8"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;lt;meta http-equiv&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"X-UA-Compatible"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"IE=edge"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;br&gt;
&amp;lt;meta &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"viewport"&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"width=device-width, initial-scale=1"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;&amp;lt;title&amp;gt;Error 404&amp;lt;/title&amp;gt;&lt;br&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...]&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Bonus: Docker Hub Pre-Push Hook&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;This repo also has an extra file in &lt;code&gt;hooks/pre-hook&lt;/code&gt; which creates another tag with the Git repo commit - it's pretty neat!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Jenkins Configuration as Code with Docker and Kubernetes</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Wed, 06 May 2020 21:08:01 +0000</pubDate>
      <link>https://dev.to/kenmoini/jenkins-configuration-as-code-with-docker-and-kubernetes-290c</link>
      <guid>https://dev.to/kenmoini/jenkins-configuration-as-code-with-docker-and-kubernetes-290c</guid>
      <description>&lt;p&gt;So there are a few tools I use in my CI/CD tool-chain - Ansible, GitLab, and Jenkins.  They all have their strengths, and weaknesses, it's all about using the right tool for the right job.  Sure, you could use a flat-head driver on a Philips but, should you?&lt;/p&gt;

&lt;p&gt;In setting these things up time and time, and time, and time, and...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv4dsige62l91pe0qb3uv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fv4dsige62l91pe0qb3uv.jpg" alt="One eternity later"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;...and time again, I've gotten good at doing it fast, and with as little manual intervention as possible.  Most of these tools have some sort of API or CLI to interface with and automate but Jenkins provides a &lt;em&gt;(now more mature)&lt;/em&gt; Configuration as Code (JCasC, CaC, etc) plugin.  The JCasC plugin allows you to take configuration and store it as YAML, drop it into a directory (or mount into a ConfigMap/Volume in Kubernetes/OpenShift), and Jenkins will pick up that configuration and apply it, so long as the needed plugins are there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jenkins Plugins
&lt;/h2&gt;

&lt;p&gt;So, plugins in Jenkins get a bad rep - as do WordPress plugins, and many other libraries, packages, and plugins created by Free Open-Source Software (FOSS) contributors.  Sometimes a a plugin doesn't get updated, or a dependency has a vulnerability that is made worse by the parent plugin, or the developer moves on and it just stagnates for years.  This is unfortunately the double-edged sword of FOSS, which is why it's often wise to go for supported Open-Source such as that from &lt;a href="https://redhat.com" rel="noopener noreferrer"&gt;Red Hat&lt;/a&gt; and &lt;a href="https://cloudbees.com" rel="noopener noreferrer"&gt;CloudBees&lt;/a&gt;.  In fact, I'll be using &lt;em&gt;CloudBees Jenkins Distribution&lt;/em&gt; which is a much more properly built Jenkins - fo free.&lt;/p&gt;

&lt;p&gt;Anywho, I won't go into the deployment of Jenkins, there are a dozen different ways to do it and to do it properly is a rather large article in and of itself - if there's interest I'll write one though.&lt;/p&gt;

&lt;p&gt;Let's assume that you as well have Jenkins, or CloudBees Jenkins Distribution, installed and accessible - great!  After the Setup Wizard, one of the first things you'll want to do is start stuffing it with plugins - thankfully I have a handy script for you to do it automatically!&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Grab that, drop it into your $JENKINS_HOME (or somewhere wx'able), and pass it a list of plugins you'd like to install, such as:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

/var/jenkins_home/jenkins-plugin-stuffer.sh anchore-container-scanner ansible configuration-as-code docker-commons docker-java-api docker-plugin docker-workflow durable-task generic-webhook-trigger git git-client gitlab-plugin git-server kubernetes kubernetes-cli kubernetes-client-api kubernetes-credentials matrix-auth nexus-jenkins-plugin pipeline-aggregator-view pipeline-build-step pipeline-graph-analysis pipeline-input-step pipeline-milestone-step pipeline-model-api pipeline-model-declarative-agent pipeline-model-definition pipeline-model-extensions pipeline-rest-api pipeline-stage-step pipeline-stage-view pipeline-utility-steps role-strategy sonar workflow-aggregator workflow-api workflow-basic-steps workflow-cps workflow-cps-global-lib workflow-durable-task-step workflow-job workflow-multibranch workflow-scm-step


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Whew - yeah.  Anywho, that's a decent "cloud-native" plugin setup, run that and restart Jenkins, then on to the Configuration as Code ...configuration!&lt;/p&gt;

&lt;h2&gt;
  
  
  Set JCasC Path
&lt;/h2&gt;

&lt;p&gt;Before the Configuration as Code plugin can load the configuration manifests, you need to tell Jenkins where to load it from!  To do so, navigate to the &lt;strong&gt;&lt;em&gt;Manage Jenkins &amp;gt; Configuration as Code&lt;/em&gt;&lt;/strong&gt; section of the Web UI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcyw2g4svsd3xwwolsed0.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fcyw2g4svsd3xwwolsed0.jpg" alt="Manage Jenkins &amp;gt; Configuration as Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add in a directory with where we're going to store the JCasC manifests - I like to do &lt;code&gt;$JENKINS_HOME/jcasc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Click Apply, Save, and now we can start by adding our JCasC manifests!&lt;/p&gt;

&lt;h2&gt;
  
  
  JCasC for Docker
&lt;/h2&gt;

&lt;p&gt;One of the things you may want to do with your Jenkins instance is build/interact with/run container images via Docker.  We've got the plugins set up, now we just need to add the configuration manifest, say a file like &lt;code&gt;$JENKINS_HOME/jcasc/cloud-docker.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;jenkins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clouds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;docker&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;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;docker"&lt;/span&gt;
        &lt;span class="na"&gt;dockerApi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;dockerHost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unix:///var/run/docker.sock"&lt;/span&gt;
        &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;labelString&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;docker-agent"&lt;/span&gt;
            &lt;span class="na"&gt;dockerTemplateBase&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jenkins/slave"&lt;/span&gt;
              &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;
            &lt;span class="na"&gt;remoteFs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/jenkins/agent"&lt;/span&gt;
            &lt;span class="na"&gt;connector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;attach&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;jenkins"&lt;/span&gt;
            &lt;span class="na"&gt;instanceCapStr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10"&lt;/span&gt;
            &lt;span class="na"&gt;retentionStrategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;idleMinutes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is a great starter configuration if you're using the local Docker Unix Socket (default config when installing Docker on Linux/Mac).  Save this file and when Jenkins is restarted the configuration will be loaded allowing you to run Pipelines with the Docker agents.  Read more about that here: &lt;a href="https://www.jenkins.io/doc/book/pipeline/docker/" rel="noopener noreferrer"&gt;https://www.jenkins.io/doc/book/pipeline/docker/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  JCasC for Kubernetes
&lt;/h2&gt;

&lt;p&gt;Maybe instead of using Docker to build applications and run pipelines in Jenkins, you'd like to use Kubernetes and maybe even Kaniko?  No problem, we can do this pretty easily now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;jenkins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;clouds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kubernetes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;connectTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;containerCapStr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10"&lt;/span&gt;
      &lt;span class="na"&gt;credentialsId&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-k8s-token"&lt;/span&gt;
      &lt;span class="na"&gt;serverUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://k8s.api.example.com"&lt;/span&gt;
      &lt;span class="na"&gt;skipTlsVerify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;jenkinsUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://jenkins.example.com/jenkins"&lt;/span&gt;
      &lt;span class="na"&gt;maxRequestsPerHostStr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;32"&lt;/span&gt;
      &lt;span class="na"&gt;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;kubernetes"&lt;/span&gt;
      &lt;span class="na"&gt;readTimeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
      &lt;span class="na"&gt;templates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^${computer.jnlpmac}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;^${computer.name}"&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;registry.access.redhat.com/openshift3/jenkins-slave-maven-rhel7"&lt;/span&gt;
          &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;successThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
          &lt;span class="na"&gt;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;jnlp"&lt;/span&gt;
          &lt;span class="na"&gt;workingDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp"&lt;/span&gt;
        &lt;span class="na"&gt;hostNetwork&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maven"&lt;/span&gt;
        &lt;span class="na"&gt;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;maven"&lt;/span&gt;
        &lt;span class="na"&gt;workspaceVolume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;emptyDirWorkspaceVolume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;yamlMergeStrategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;override"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^${computer.jnlpmac}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;^${computer.name}"&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;registry.access.redhat.com/openshift3/jenkins-agent-nodejs-8-rhel7"&lt;/span&gt;
          &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;successThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
            &lt;span class="na"&gt;timeoutSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
          &lt;span class="na"&gt;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;jnlp"&lt;/span&gt;
          &lt;span class="na"&gt;workingDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp"&lt;/span&gt;
        &lt;span class="na"&gt;hostNetwork&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nodejs"&lt;/span&gt;
        &lt;span class="na"&gt;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;nodejs"&lt;/span&gt;
        &lt;span class="na"&gt;workspaceVolume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;emptyDirWorkspaceVolume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;yamlMergeStrategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;override"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Save that file as &lt;code&gt;$JENKINS_HOME/jcasc/cloud-kubernetes.yml&lt;/code&gt;, restart Jenkins, and enjoy the new ability to deploy pipelines on Kubernetes!  Read more about the Jenkinsfile side of things here: &lt;a href="https://plugins.jenkins.io/kubernetes/" rel="noopener noreferrer"&gt;https://plugins.jenkins.io/kubernetes/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap
&lt;/h2&gt;

&lt;p&gt;That's that!  Within a short time you can deploy Jenkins or CloudBees Jenkins Distribution, add all your plugins with a few commands, set up configuration from code and get to building applications with Jenkins, Docker, and Kubernetes!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deploy Harbor Container Registry - with Replication!</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Tue, 28 Apr 2020 23:42:30 +0000</pubDate>
      <link>https://dev.to/kenmoini/deploy-harbor-container-registry-with-replication-2f5m</link>
      <guid>https://dev.to/kenmoini/deploy-harbor-container-registry-with-replication-2f5m</guid>
      <description>&lt;p&gt;So I have a rather robust DevOps platform at home in my lab - private GitLab, Kubernetes, registry, and so on.  Now that I'm pushing to a public and production Kubernetes cluster in the cloud, I have a very interesting problem - getting my container images from my private lab network, which has an inaccessible subnet and custom TLD, pulled into the public Kubernetes clusters...&lt;/p&gt;

&lt;p&gt;I cooooould create a VPN between my pfSense router and the Kubernetes cluster which would then allow routing to the private container registry.  Problem with that is that I also use a custom Certificate Authority in my lab that works great, but since I use a managed Kubernetes service, I can't easily add my certificates to those nodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;So I guess I have to deploy another Harbor Container Registry in the cloud to mirror my images publicly...&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the VM
&lt;/h2&gt;

&lt;p&gt;I'm working with DigitalOcean's managed Kubernetes service - with their Marketplace of apps and the GitLab integration I use, it's pretty easy to get up and going and use.  That's key because I was getting tired of managing my own clusters and their constantly changing deployments.&lt;/p&gt;

&lt;p&gt;I'm also going to deploy this Harbor Container Registry in DigitalOcean, in the same Region and Availability Zone so that I can use private networking between the Kubernetes cluster and the registry.&lt;/p&gt;

&lt;p&gt;You can quickly deploy the same sorta VM I'm using by clicking the following link which will take you to the DigitalOcean Cloud Panel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.digitalocean.com/droplets/new?i=e07bd5&amp;amp;size=s-2vcpu-4gb&amp;amp;region=nyc1&amp;amp;distro=centos&amp;amp;distroImage=centos-7-x64&amp;amp;options=install_agent"&gt;https://cloud.digitalocean.com/droplets/new?i=e07bd5&amp;amp;size=s-2vcpu-4gb&amp;amp;region=nyc1&amp;amp;distro=centos&amp;amp;distroImage=centos-7-x64&amp;amp;options=install_agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(That's not a referral link, but this is: &lt;a href="https://m.do.co/c/9058ed8261ee"&gt;https://m.do.co/c/9058ed8261ee&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring the VM
&lt;/h2&gt;

&lt;p&gt;So to deploy Harbor, we're going to use Docker and Docker Compose.  This is going to make things very, very easy...connect to that VM you just made and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;epel-release &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
reboot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once we've done a quick system update and reboot to bring in any new kernels, we can continue with confidence.  Speaking of confidence, let's set some security basics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;fail2ban firewalld wget nano &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo cp&lt;/span&gt; /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;fail2ban
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;firewalld
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start fail2ban
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start firewalld
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--permanent&lt;/span&gt; &lt;span class="nt"&gt;--add-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https
&lt;span class="nb"&gt;sudo &lt;/span&gt;firewall-cmd &lt;span class="nt"&gt;--reload&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Next, we can install Docker and Docker Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum-config-manager &lt;span class="nt"&gt;--add-repo&lt;/span&gt; https://download.docker.com/linux/centos/docker-ce.repo
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/docker/compose/releases/download/1.25.5/docker-compose-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-s&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="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/docker-compose
&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/docker-compose

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting Harbor
&lt;/h2&gt;

&lt;p&gt;You can deploy Harbor a number of different ways - today I'll be using Docker Compose to do so.  Some may ask "Why not just deploy on your Kubernetes cluster?" - well, that's because your registry is best suited in a separate environment than a Kubernetes cluster that can be torn down and redeployed at a moment's notice.&lt;/p&gt;

&lt;p&gt;Head on over to &lt;a href="https://goharbor.io/"&gt;https://goharbor.io/&lt;/a&gt; and grab the latest release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://github.com/goharbor/harbor/releases/download/v1.10.2/harbor-online-installer-v1.10.2.tgz
&lt;span class="nb"&gt;tar &lt;/span&gt;xvf harbor-online-installer-v1.10.2.tgz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;harbor /opt
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/harbor

&lt;span class="c"&gt;## Create a directory for the container data and give it the right SELinux contexts&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; /data
&lt;span class="nb"&gt;sudo chcon&lt;/span&gt; &lt;span class="nt"&gt;-Rt&lt;/span&gt; svirt_sandbox_file_t /data
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  SSL Certificates
&lt;/h2&gt;

&lt;p&gt;Now, you could follow the Harbor docs and deploy your own self-signed certificates - I do in my lab.  However, in this instance since we're working in the public cloud, we're going to use Let's Encrypt to generate SSL certificates for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install &lt;/span&gt;certbot &lt;span class="nt"&gt;-y&lt;/span&gt;

certbot certonly &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="nt"&gt;--preferred-challenges&lt;/span&gt; http &lt;span class="nt"&gt;--non-interactive&lt;/span&gt;  &lt;span class="nt"&gt;--staple-ocsp&lt;/span&gt; &lt;span class="nt"&gt;--agree-tos&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; you@example.com &lt;span class="nt"&gt;-d&lt;/span&gt; example.com

&lt;span class="c"&gt;# Setup letsencrypt certificates renewing &lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; /etc/cron.d/letsencrypt
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
30 2 * * 1 root /usr/bin/certbot renew &amp;gt;&amp;gt; /var/log/letsencrypt-renew.log &amp;amp;&amp;amp; cd /etc/letsencrypt/live/example.com &amp;amp;&amp;amp; cp privkey.pem domain.key &amp;amp;&amp;amp; cat cert.pem chain.pem &amp;gt; domain.crt &amp;amp;&amp;amp; chmod 777 domain.*
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# Rename SSL certificates&lt;/span&gt;
&lt;span class="c"&gt;# https://community.letsencrypt.org/t/how-to-get-crt-and-key-files-from-i-just-have-pem-files/7348&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; /etc/letsencrypt/live/example.com &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;cp &lt;/span&gt;privkey.pem domain.key &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;cert.pem chain.pem &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; domain.crt

&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/docker/certs.d/example.com

&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/letsencrypt/live/example.com/chain.pem /etc/docker/certs.d/example.com/ca.crt

&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/letsencrypt/live/example.com/cert.pem /etc/docker/certs.d/example.com/example.com.cert

&lt;span class="nb"&gt;sudo ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; /etc/letsencrypt/live/example.com/privkey.pem /etc/docker/certs.d/example.com/example.com.key

&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy Harbor
&lt;/h2&gt;

&lt;p&gt;Now that we have our SSL certificates in place, let's configure the &lt;code&gt;harbor.yml&lt;/code&gt; file - ensure the following lines are changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;EXAMPLE.com&lt;/span&gt;

&lt;span class="na"&gt;https&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;443&lt;/span&gt;
  &lt;span class="c1"&gt;# The path of cert and key files for nginx&lt;/span&gt;
  &lt;span class="na"&gt;certificate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/example.com/fullchain.pem&lt;/span&gt;
  &lt;span class="na"&gt;private_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With that and whatever else you'd like changed configured, let's deploy the full Harbor suite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; ./prepare
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./install.sh &lt;span class="nt"&gt;--with-notary&lt;/span&gt; &lt;span class="nt"&gt;--with-clair&lt;/span&gt; &lt;span class="nt"&gt;--with-chartmuseum&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With that, you should now be able to navigate to your hostname and access the Web UI - making sure to change the default admin password ASAP, of course.&lt;/p&gt;

&lt;h2&gt;
  
  
  Registry Replication
&lt;/h2&gt;

&lt;p&gt;Now, while you already have a great working registry at hand, what if you're like me and you also need to replicate a private registry into a public one?  Thankfully, replication is a first-class citizen in Harbor.&lt;/p&gt;

&lt;p&gt;Harbor can replicate via a push or a pull action - in this case I'll be pushing from private into the public registry.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Public Harbor - Create a Project &amp;amp; Robot Account
&lt;/h3&gt;

&lt;p&gt;First thing we have to do is navigate to our newly created Public Registry.  We'll create a &lt;strong&gt;Project&lt;/strong&gt; and in that project we'll create a &lt;strong&gt;Robot Account&lt;/strong&gt; - this Robot Account will be the API key we need to interact with the registry.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Projects&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;+New Project&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Fill in the modal to your liking&lt;/li&gt;
&lt;li&gt;Click the link of the new Project you created to enter it&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;Robot Accounts&lt;/strong&gt; tab&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;+ New Robot Account&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;Fill in the modal as you'd like&lt;/li&gt;
&lt;li&gt;Note the &lt;strong&gt;&lt;em&gt;robot$YOUR_NAME&lt;/em&gt;&lt;/strong&gt; - that's your &lt;strong&gt;Access ID&lt;/strong&gt;, the long Token is your &lt;strong&gt;Access Secret&lt;/strong&gt;.  You'll need these two values in a moment to enable the Private registry to push into this Public registry.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Private Harbor - Create a Registry
&lt;/h3&gt;

&lt;p&gt;Now switch over to the Private registry, we'll create the &lt;strong&gt;Registry&lt;/strong&gt; entry and the &lt;strong&gt;Replication&lt;/strong&gt; rule with that entry to push images into that Public registry.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Administration &amp;gt; Registries&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;+ New Endpoint&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;Fill in the modal with the details as you see needed for your Public registry - including the Access ID and Access Secret from the Robot Account in the Public Registry's Project you created earlier - &lt;em&gt;whew&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Test Connection&lt;/strong&gt; and &lt;strong&gt;OK&lt;/strong&gt; if it pans out&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. Private Harbor - Create a Replication Rule
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Administration &amp;gt; Replications&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click the &lt;strong&gt;+ New Replication Rule&lt;/strong&gt; button&lt;/li&gt;
&lt;li&gt;Fill out the modal as you'd like, referencing the newly created remote destination public registry we just defined.
&lt;strong&gt;Note&lt;/strong&gt;: I like to set the &lt;strong&gt;Trigger Mode&lt;/strong&gt; to &lt;strong&gt;&lt;em&gt;Event Based&lt;/em&gt;&lt;/strong&gt; so the images will push automatically.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click the option circle to the left of your Replication rule you just created - click the &lt;strong&gt;Replicate&lt;/strong&gt; button to run a manual replication&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This can take some time depending on your network, how many repositories, how many tags, layers, and so on.  A meager 22 artifacts at 1.7GB took about 16 minutes to push manually - the individual streams will be much faster though.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>linux</category>
    </item>
    <item>
      <title>LDAP on GitLab with Red Hat Identity Management (FreeIPA)</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Tue, 28 Apr 2020 08:16:26 +0000</pubDate>
      <link>https://dev.to/kenmoini/ldap-on-gitlab-with-red-hat-identity-management-freeipa-3f5l</link>
      <guid>https://dev.to/kenmoini/ldap-on-gitlab-with-red-hat-identity-management-freeipa-3f5l</guid>
      <description>&lt;p&gt;I have no idea why, but this weekend I redeployed everything I have in DigitalOcean - not permanently, just to try I guess...&lt;/p&gt;

&lt;p&gt;Well, it did leave me with a lot of documentation at least!  You'd be surprised how much stuff I just do and leave to the pages of history.  This time was different, this time, I typed.&lt;/p&gt;

&lt;p&gt;One of the things that has always gotten me is how to properly integrate LDAP into different things - every application has a different way of interacting and filtering the schema.  GitLab is no exception, but today, I have finally mastered their thought of LDAP integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. LDAP Setup
&lt;/h2&gt;

&lt;p&gt;First off, I'll be using Red Hat Identity Management, or FreeIPA, as the LDAP server.  Honestly it's LDAP with a bunch of other things but either way, there are a couple assumptions being made:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You have a &lt;code&gt;gitlabusers&lt;/code&gt; Group in IDM/IPA&lt;/li&gt;
&lt;li&gt;You have a &lt;code&gt;gitlabadmins&lt;/code&gt; Group in IDM/IPA&lt;/li&gt;
&lt;li&gt;You have a set of users assigned to those groups&lt;/li&gt;
&lt;li&gt;You have a bind user dedicated to GitLab LDAP Binding&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;How do you make a dedicated Bind DN for your LDAP server?  Make a file called &lt;code&gt;gitlabbdn.update&lt;/code&gt; with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dn: uid=gitlabbdn,cn=users,cn=accounts,dc=example,dc=com
add:objectclass:account
add:objectclass:simplesecurityobject
add:uid:gitlabbdn
add:userPassword:s3cr3tP455w0rdHERE
add:passwordExpirationTime:20380119031407Z
add:nsIdleTimeout:0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then on your IPA server, as the admin user, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ipa-ldap-updater gitlab-bind.update
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now you can use the Bind DN of &lt;strong&gt;&lt;em&gt;uid=gitlabbdn,cn=users,cn=accounts,dc=example,dc=com&lt;/em&gt;&lt;/strong&gt; and the &lt;strong&gt;&lt;em&gt;s3cr3tP455w0rdHERE&lt;/em&gt;&lt;/strong&gt; password you set to securely bind to the LDAP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. GitLab Configuration
&lt;/h2&gt;

&lt;p&gt;Next, we'll modify the LDAP section of the &lt;code&gt;/etc/gitlab/gitlab.rb&lt;/code&gt; file to look something like this:&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;### LDAP Settings&lt;/span&gt;
&lt;span class="c1"&gt;###! Docs: https://docs.gitlab.com/omnibus/settings/ldap.html&lt;/span&gt;
&lt;span class="c1"&gt;###! **Be careful not to break the indentation in the ldap_servers block. It is&lt;/span&gt;
&lt;span class="c1"&gt;###!   in yaml format and the spaces must be retained. Using tabs will not work.**&lt;/span&gt;

&lt;span class="n"&gt;gitlab_rails&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ldap_enabled'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;span class="n"&gt;gitlab_rails&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'prevent_ldap_sign_in'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;

&lt;span class="c1"&gt;###! **remember to close this block with 'EOS' below**&lt;/span&gt;
&lt;span class="n"&gt;gitlab_rails&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ldap_servers'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;YAML&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;'EOS'&lt;/span&gt;&lt;span class="sh"&gt;
  main:
    label: 'My LDAP'
    host: 'ipa.example.com'
    port: 389
    uid: 'uid'
    bind_dn: 'uid=gitlabbdn,cn=users,cn=accounts,dc=example,dc=com'
    password: 's3cr3tP455w0rdHERE'
    encryption: 'start_tls'
    verify_certificates: false
    smartcard_auth: false
    active_directory: false
    allow_username_or_email_login: false
    lowercase_usernames: false
    block_auto_created_users: false
    base: 'cn=accounts,dc=example,dc=com'
    user_filter: '(memberof=CN=gitlabusers,CN=groups,CN=accounts,DC=example,DC=com)'
    attributes:
      username: ['uid']
      email: ['mail']
      name: 'displayName'
      first_name: 'givenName'
      last_name: 'sn'
&lt;/span&gt;&lt;span class="no"&gt;EOS&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Outside of replacing the domain/credentials with yours, that should do it.  Any LDAP user in the gitlabusers group will be able to access.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;gitlab-ctl reconfigure&lt;/code&gt; and enjoy the new centralized authentication!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>git</category>
      <category>tutorial</category>
      <category>integration</category>
    </item>
    <item>
      <title>Lightweight DevOps Host with Red Hat Enterprise Linux</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Tue, 31 Mar 2020 03:27:02 +0000</pubDate>
      <link>https://dev.to/kenmoini/lightweight-devops-host-with-red-hat-enterprise-linux-4fec</link>
      <guid>https://dev.to/kenmoini/lightweight-devops-host-with-red-hat-enterprise-linux-4fec</guid>
      <description>&lt;p&gt;I am exceptionally bad at this 100 Days of Code thing.&lt;/p&gt;

&lt;p&gt;Not that I haven't been coding most of these days, but it just hasn't been fun, or sexy, or something to share.  NO ONE WANTS TO READ ABOUT A DMARC MICROSERVICE, TRUST ME I'VE TRIED.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P8TvUIgw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/ZgqJGwh2tLj5C/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P8TvUIgw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://media2.giphy.com/media/ZgqJGwh2tLj5C/giphy.gif" alt="Booooooring"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anywho, here I am today in a new series that should hopefully be good for my own documentation as well.  I'm in the process of deploying a new educational platform and it's all containerized, based on microservices deployed into a Kubernetes cluster, with sprinkles on top, and blah blah.&lt;/p&gt;

&lt;p&gt;Thing is, I'm building this out in my home lab to avoid spending an extra hundred dollars in the cloud.  So if you've got some hardware and time and want to do the same, I'll take you from Zero to Hero with this whole DevOps thing.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Metal
&lt;/h1&gt;

&lt;p&gt;So you can probably run this on anything really, but I'm going to start with a dedicated host and turn it into a container and virtualisation host.  It's a simple Dell R620 with never enough Cores or RAM.  The host sits on my internal network behind a pfSense router.  It's got a static IP of 192.168.42.10 and we'll give it a hostname of &lt;code&gt;thebus.kemo.labs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note that kemo.labs is not a valid TLD&lt;/em&gt; - that's ok.  I have that domain resolved and routed via my pfSense router, you could do the same with DNSMASQ or BIND.&lt;/p&gt;

&lt;h1&gt;
  
  
  RHEL, RHEL, RHEL, we meet again
&lt;/h1&gt;

&lt;p&gt;So this time I decided against going with Proxmox for the container/VM host to get closer to what my intended production environment will be like, which will be a Kubernetes/OpenShift cluster running CentOS/Red Hat Universal Basic Image (UBI) containers.&lt;/p&gt;

&lt;p&gt;So some might want to just grab CentOS, which of course would work, but why not get your own free copy of Red Hat Enterprise Linux?  How does one procure one of these free copies?  Why, wouldn't you like to know...&lt;/p&gt;

&lt;p&gt;Ok, so simply enough, one just needs to register with the &lt;a href="https://developers.redhat.com/"&gt;Red Hat Developers site&lt;/a&gt;, and grab your very own yearly Red Hat Developer Suite subscription.  It expires yearly, but it's still just a free renewal and gives you access to Red Hat Enterprise Linux, the Middleware offerings, and more.&lt;/p&gt;

&lt;p&gt;So grab your subscription, download the ISO (I'll be using RHEL 7), and get it installed - so far that's the easy part.&lt;/p&gt;

&lt;h1&gt;
  
  
  Basic Provisioning
&lt;/h1&gt;

&lt;p&gt;Now that we've got our Red Hat Enterprise Linux (RHEL) host set up, you're staring at the log in terminal, jumped into your privileged user, and are now at a shell.  Let's bless this mess a scootch...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Elevate to root&lt;/span&gt;
&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt;

&lt;span class="c"&gt;# In case you need to still register/attach a subscription&lt;/span&gt;
subscription-manager register
subscription-manager attach &lt;span class="nt"&gt;--auto-attach&lt;/span&gt;

&lt;span class="c"&gt;# Enable repos&lt;/span&gt;
subscription-manager repos &lt;span class="nt"&gt;--enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rhel-7-server-supplementary-rpms &lt;span class="nt"&gt;--enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rhel-7-server-rpms &lt;span class="nt"&gt;--enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rhel-7-server-optional-rpms &lt;span class="nt"&gt;--enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rhel-7-server-ansible-2.9-rpms &lt;span class="nt"&gt;--enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;rhel-7-server-extras-rpms

&lt;span class="c"&gt;# Update system and reboot to pull in new kernels&lt;/span&gt;
yum update &lt;span class="nt"&gt;-y&lt;/span&gt;
systemctl reboot

&lt;span class="c"&gt;# Install needed packages&lt;/span&gt;
yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; wget curl git nano ca-certificates cockpit cockpit-dashboard cockpit-docker cockpit-machines cockpit-packagekit cockpit-shell ansible docker docker-selinux libvirt-client

&lt;span class="c"&gt;# Install Docker Compose&lt;/span&gt;
curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/docker/compose/releases/download/1.25.4/docker-compose-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-s&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="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /usr/local/bin/docker-compose
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/docker-compose

&lt;span class="c"&gt;# Set Firewall Options&lt;/span&gt;
firewall-cmd &lt;span class="nt"&gt;--add-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ssh
firewall-cmd &lt;span class="nt"&gt;--add-service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cockpit

&lt;span class="c"&gt;# Set services&lt;/span&gt;
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;cockpit &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl start cockpit
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;docker &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl start docker
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;So with alllll of that, we've done a few things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Logged in, elevated to root&lt;/li&gt;
&lt;li&gt;Used subscription-manage to register the system and attach your subscription.&lt;/li&gt;
&lt;li&gt;Enabled the needed repositories, updated packages, and rebooted&lt;/li&gt;
&lt;li&gt;Installed a few things:

&lt;ul&gt;
&lt;li&gt;Base system packages such as curl, git, and nano&lt;/li&gt;
&lt;li&gt;Ansible, Docker, and the virsh (via libvirt-client)&lt;/li&gt;
&lt;li&gt;Cockpit and some modules for Cockpit - it's a Web UI that'll make your life sooooo much easier&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Installed Docker Compose&lt;/li&gt;
&lt;li&gt;Set firewall options for SSH access and Cockpit (exposed on port 9090)&lt;/li&gt;
&lt;li&gt;Enabled and started Cockpit and Docker&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And now with that, we have our own RHEL host that is ready to run a bunch of Containers, Virtual Machines, and Web UI with Cockpit to manage it all!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/pjAuKMMxBMyFnW5V1t/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/pjAuKMMxBMyFnW5V1t/giphy.gif" alt="Great success"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Join me in the next part of this series where I show how to install GitLab in a VM and Minio in a Docker container for local S3-compatible storage.&lt;/p&gt;

</description>
      <category>100daysofcode</category>
      <category>devops</category>
      <category>tutorial</category>
      <category>linux</category>
    </item>
    <item>
      <title>Access the OpenShift Internal Registry</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Thu, 13 Feb 2020 17:56:51 +0000</pubDate>
      <link>https://dev.to/kenmoini/access-the-openshift-internal-registry-3kjh</link>
      <guid>https://dev.to/kenmoini/access-the-openshift-internal-registry-3kjh</guid>
      <description>&lt;p&gt;OpenShift has a really handy feature called Image Streams.  It also has a built-in Docker V2 registry which is also very useful.  However, the registry isn't really exposed outside the cluster.  &lt;strong&gt;&lt;em&gt;So how do you do rolling updates of images?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ensure you have the needed RBAC policies
&lt;/h2&gt;

&lt;p&gt;In order to push/pull from the internal OpenShift registry, your OpenShift user needs to have a few permissions.  As either another &lt;em&gt;cluster-admin&lt;/em&gt; user or via the Master OpenShift node in the &lt;strong&gt;system:admin&lt;/strong&gt; user context, apply the following policies to your &lt;strong&gt;intented_user&lt;/strong&gt;:&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;$ &lt;/span&gt;oc adm policy add-role-to-user system:registry &amp;lt;intended_user&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace/project&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;oc adm policy add-role-to-user system:image-builder &amp;lt;intended_user&amp;gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &amp;lt;namespace/project&amp;gt;
&lt;span class="c"&gt;## or for cluster-wide access...&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;oc adm policy add-cluster-role-to-user system:registry &amp;lt;intended_user&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;oc adm policy add-cluster-role-to-user system:image-builder &amp;lt;intended_user&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;With those policies, you can access the internal registry with the &lt;strong&gt;intended_user&lt;/strong&gt; and perform &lt;code&gt;docker push/pull&lt;/code&gt; commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Log into your OpenShift application node
&lt;/h2&gt;

&lt;p&gt;This is really the only trick to it - you don't want to do this on your Master nodes as that's in the &lt;strong&gt;system:admin&lt;/strong&gt; context.  You also can't do this on your local machine since it doesn't have access to the routed &lt;strong&gt;default.svc&lt;/strong&gt; domain inside the cluster.  So scoot on over to one of your infrastructure nodes and run the following:&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;$ &lt;/span&gt;oc login
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker login &lt;span class="nt"&gt;-u&lt;/span&gt; openshift &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;oc &lt;span class="nb"&gt;whoami&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; docker-registry.default.svc:5000
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Pull n Push it Real Good
&lt;/h2&gt;

&lt;p&gt;Now that we're logged into our internal OpenShift registry from an OpenShift application node, we can continue to push a new image to the Image Stream and Registry.  When you have a Deployment Configuration that updates upon Image Change, this action will perform a Rolling Update as well.&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;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker pull yourUser/someContainer:latest
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker tag yourUser/someContainer:latest docker-registry.default.svc:5000/&amp;lt;namespace|project&amp;gt;/&amp;lt;image-stream-name&amp;gt;:&amp;lt;tag&amp;gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;docker push docker-registry.default.svc:5000/&amp;lt;namespace|project&amp;gt;/&amp;lt;image-stream-name&amp;gt;:&amp;lt;tag&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now if your deployment is updated upon an Image Change you should see a rolling update happen very quicky - &lt;em&gt;ultra-quick&lt;/em&gt; if you happen to be on the node that is running that pod since the image layers are already on the system from your &lt;code&gt;docker pull&lt;/code&gt; command!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;If only someone were able to make this into an Ansible playbook that could be fired off as part of a script locally or via Ansible Tower...&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>100daysofcode</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Automate GitLab with Ansible - kinda</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Mon, 10 Feb 2020 21:02:38 +0000</pubDate>
      <link>https://dev.to/kenmoini/automate-gitlab-with-ansible-kinda-b5d</link>
      <guid>https://dev.to/kenmoini/automate-gitlab-with-ansible-kinda-b5d</guid>
      <description>&lt;p&gt;So there I was, automatin' and integratin' again.  What can I say, it does get the best of me at times...&lt;/p&gt;

&lt;p&gt;My task was to provide a way to automate the clean up of GitLab user namespaces, and import in a fresh repo from GitHub for NN student workshop users.  Doesn't sound too hard eh?&lt;/p&gt;

&lt;p&gt;So come to find out the GitLab module in Ansible is SUPER old and not really maintained.  No problem, I'll just ping the GitHub API with Ansible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do that instead of using something like Bash to perform cURL requests?&lt;/strong&gt;  Because the rest of the deployment already uses Ansible and it's much easier to just toss in a few extra tasks instead of bringing along an extra script and dropping into a different execution context.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create the main Ansible Playbook
&lt;/h2&gt;

&lt;p&gt;There's two Ansible files, and you'll see why shortly.  First, let's create the &lt;code&gt;configure_users.yaml&lt;/code&gt; file with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure GitLab Workshop User Instances&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
  &lt;span class="na"&gt;gather_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;student_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;github_personal_access_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;your_github_pat&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;github_repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kenmoini/s2f-tasks-app&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;gitlab_endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://gitlab.example.com&lt;/span&gt;

  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get GitHub Repo Import Repo ID&lt;/span&gt;
    &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.github.com/repos/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;github_repo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
      &lt;span class="na"&gt;validate_certs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
      &lt;span class="na"&gt;status_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;409&lt;/span&gt;
    &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github_repo_info&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure Per User GitLab Environment&lt;/span&gt;
    &lt;span class="na"&gt;include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actual_user_config.yaml&lt;/span&gt;
    &lt;span class="na"&gt;with_sequence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;start=0 end="{{ student_count }}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;A few variables to keep an eye on, you need to pass in your GitHub Personal Access Token in order to import the repo, as well as the actual repo.  We're also provisioning for 50 student user seats.&lt;/li&gt;
&lt;li&gt;GitLab imports GitHub repos by their Repo ID, the numeric value, not the string.  So our first task is to get that Repo ID&lt;/li&gt;
&lt;li&gt;Next, we loop through an external tasks file - Blocks don't support loops.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Per-seat tasks
&lt;/h2&gt;

&lt;p&gt;Let's create that external &lt;code&gt;actual_user_config.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitLab Post | Obtain Access Token&lt;/span&gt;
  &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_endpoint&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/oauth/token"&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;validate_certs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
    &lt;span class="na"&gt;status_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
    &lt;span class="na"&gt;body_format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"grant_type": "password",&lt;/span&gt;
        &lt;span class="s"&gt;"username": "student{{ item }}",&lt;/span&gt;
        &lt;span class="s"&gt;"password": "user_password_here"&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gitlab_access_token&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitLab Get | Get All User projects&lt;/span&gt;
  &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_endpoint&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/api/v4/users/student{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;item&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/projects"&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
    &lt;span class="na"&gt;validate_certs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
    &lt;span class="na"&gt;status_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;409&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer {{ gitlab_access_token.json.access_token }}&lt;/span&gt;
  &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user_projects&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;destroy&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitLab Post | Delete Projects via API&lt;/span&gt;
  &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_endpoint&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/api/v4/projects/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;project.id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DELETE&lt;/span&gt;
    &lt;span class="na"&gt;validate_certs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
    &lt;span class="na"&gt;status_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;202&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;409&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer {{ gitlab_access_token.json.access_token }}&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;user_projects.json&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
  &lt;span class="na"&gt;loop_control&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;loop_var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;project&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;destroy&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitLab Post | Import Project from GitHub&lt;/span&gt;
  &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gitlab_endpoint&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/api/v4/import/github"&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;validate_certs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
    &lt;span class="na"&gt;status_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;409&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;
    &lt;span class="na"&gt;body_format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;json&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer {{ gitlab_access_token.json.access_token }}&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="s"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"personal_access_token": "{{ github_personal_access_token }}",&lt;/span&gt;
        &lt;span class="s"&gt;"repo_id": "{{ github_repo_info.json.id }}",&lt;/span&gt;
        &lt;span class="s"&gt;"target_namespace": "student{{ item }}"&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;import&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;This external set of tasks is executed top-to-bottom per-student.&lt;/li&gt;
&lt;li&gt;First, we get an authentication token to use for the following tasks&lt;/li&gt;
&lt;li&gt;Next, there are tagged tasks to get all the repos a user has access to&lt;/li&gt;
&lt;li&gt;We can then destroy all the repos under the user&lt;/li&gt;
&lt;li&gt;Once it's all cleaned up, we'll import a new repo fresh into each student namespace&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Rolllll it
&lt;/h2&gt;

&lt;p&gt;Now you can simply run one of the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook configure_users.yaml &lt;span class="nt"&gt;--tags&lt;/span&gt; destroy
&lt;span class="c"&gt;# or&lt;/span&gt;
ansible-playbook configure_users.yaml &lt;span class="nt"&gt;--tags&lt;/span&gt; import
&lt;span class="c"&gt;# OR OR&lt;/span&gt;
ansible-playbook configure_users.yaml &lt;span class="nt"&gt;--tags&lt;/span&gt; &lt;span class="s2"&gt;"destroy,import"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Hopefully, this helps in case you're looking to automate GitLab with Ansible.  If there's not a particular Ansible module, or if it's not up to snuff then don't be afraid to use APIs if there's one available!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>tutorial</category>
      <category>100daysofcode</category>
      <category>git</category>
    </item>
    <item>
      <title>How to Diagnose OpenShift API Issues with More Verbose Logging</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Sun, 09 Feb 2020 04:34:40 +0000</pubDate>
      <link>https://dev.to/kenmoini/how-to-diagnose-openshift-api-issues-with-more-verbose-logging-4bcl</link>
      <guid>https://dev.to/kenmoini/how-to-diagnose-openshift-api-issues-with-more-verbose-logging-4bcl</guid>
      <description>&lt;p&gt;So while I was integrating LDAP with OpenShift, I had a funny issue - &lt;em&gt;it didn't work&lt;/em&gt;.  The error message given at the Web UI was also useless, probably for the best, don't want to show how the sausage is made.  On top of that, normal logging such as &lt;code&gt;/var/log/{audit/*,messages,secure}&lt;/code&gt; showed nothing of these authentication failures.&lt;/p&gt;

&lt;p&gt;Come to find out, the normal log-level of OpenShift is pretty low - understandably so, there are a metric TON of events going on in a larger Kubernetes system.  So in order to find logging for LDAP and other authentication events, we have to bump up the log level a bit.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Download the master-loglevel.yml Ansible Playbook
&lt;/h2&gt;

&lt;p&gt;Red Hat has a handy-dandy Ansible playbook that will modify and collect the log levels of your OpenShift masters!&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;$ &lt;/span&gt;wget https://access.redhat.com/sites/default/files/attachments/master-loglevel.yml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Moving on in the execution of this playbook, it's assumed that your host inventory is located at the default &lt;code&gt;/etc/ansible/hosts&lt;/code&gt; location.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Increase the log
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ansible-playbook master-loglevel.yml &lt;span class="nt"&gt;-t&lt;/span&gt; loglevel &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;debug_loglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now that we're logging at a loud enough volume, let's go and do things that break and don't work and stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. ?????
&lt;/h2&gt;

&lt;p&gt;Really, at this point I'm not sure what you'd want to look for, but do the things that don't work.  For me, I attempted logging into the LDAP users which generated an error which is now logged.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. PROFIT!!!!1
&lt;/h2&gt;

&lt;p&gt;Let's pull in those juicy logs:&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;$ &lt;/span&gt;ansible-playbook &lt;span class="nt"&gt;-i&lt;/span&gt; inventory master-loglevel.yml &lt;span class="nt"&gt;-t&lt;/span&gt; collect
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Logs are saved under /tmp/master-logs/ of your ansible host, for each master .tar.gz.&lt;br&gt;
It contains OpenShift Master API, Controllers and ETCD logs.&lt;/p&gt;

&lt;p&gt;Odds are, you want to take a look at those OpenShift Master API logs.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Save dat cheddar (and log space)
&lt;/h2&gt;

&lt;p&gt;Don't forget to turn off the logging to something more sane and less verbose...&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;$ &lt;/span&gt;ansible-playbook master-loglevel.yml &lt;span class="nt"&gt;-t&lt;/span&gt; loglevel &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;debug_loglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Hopefully, this helps you track down some of those more system-level issues that you may face as an SRE!&lt;/p&gt;

</description>
      <category>100daysofcode</category>
      <category>kubernetes</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Keystone, LDAP, and Multiple Identity Providers in OpenShift</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Sat, 08 Feb 2020 05:14:50 +0000</pubDate>
      <link>https://dev.to/kenmoini/keystone-ldap-and-multiple-identity-providers-in-openshift-1d9k</link>
      <guid>https://dev.to/kenmoini/keystone-ldap-and-multiple-identity-providers-in-openshift-1d9k</guid>
      <description>&lt;p&gt;Today was momentous - I moved our OpenShift cluster and workloads off of AWS and onto the OROCK Cloud that runs Red Hat Cloud Suite top to bottom.  Most of the workloads were easy to migrate and while I was shuffling things around I figured I'd deploy Red Hat Identity Management to the OROCK Cloud as well and get it integrated with our new OpenShift cluster.&lt;/p&gt;

&lt;p&gt;A few notes for those who may not know much about the Red Hat ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Red Hat Cloud Suite&lt;/em&gt;&lt;/strong&gt; includes everything you need to run your own private cloud similar to AWS, Azure, GCP, etc&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Red Hat OpenShift Container Platform&lt;/em&gt;&lt;/strong&gt; is the enterprise Kubernetes platform that makes everyone's lives easier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Red Hat Identity Management&lt;/em&gt;&lt;/strong&gt; is the supported FreeIPA offering that wraps up a bunch of services such as DNS, LDAP, PKI, and more.  I use it primarily for an LDAP store.&lt;/li&gt;
&lt;li&gt;LDAP is similar to Active Directory in that it provides a hierarchal tree-based directory and authentication system.&lt;/li&gt;
&lt;li&gt;Keystone is another enterprise authentication mechanism that is part of the OpenStack IaaS offering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anywho, so since this OpenShift cluster is running in an OpenStack private cloud, it uses Keystone to authenticate my user.  I use Red Hat IDM/LDAP for workshop user authentication because it's often easier to integrate into different solutions - and I don't want 100 student user accounts taking up space in Keystone next to my actual cluster-admin user.&lt;/p&gt;

&lt;p&gt;So what I need are multiple authentication methods for OpenShift.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Pull in your IDM/LDAP CA
&lt;/h2&gt;

&lt;p&gt;Odds are you're using a self-signed Certificate Authority certificate which means it's not in the normal keystores.  We need a copy of that on our OpenShift Masters and we can pull it easily:&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;$ &lt;/span&gt;openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; idm.example.com &lt;span class="nt"&gt;-showcerts&lt;/span&gt; 2&amp;gt;/dev/null | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="s1"&gt;'/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'&lt;/span&gt; | &lt;span class="nb"&gt;csplit&lt;/span&gt; &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;outfile - &lt;span class="s2"&gt;"/-----END CERTIFICATE-----/+1"&lt;/span&gt; &lt;span class="s2"&gt;"{*}"&lt;/span&gt; &lt;span class="nt"&gt;--elide-empty-files&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo cp &lt;/span&gt;outfile01 /etc/ssl/certs/idm-ca.pem
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;600 /etc/ssl/certs/idm-ca.pem
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;That will create two files, the last of which will likely be your CA certificate.  Then the following commands copy it into place.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Modify the /etc/origin/master/master-config.yaml file
&lt;/h2&gt;

&lt;p&gt;If you're rolling multi-masters, it's probably easier to modify the Ansible host file and modify the &lt;code&gt;openshift_master_identity_providers&lt;/code&gt; variable and run the deployment playbooks, but for this example we'll modify the &lt;code&gt;/etc/origin/master/master-config.yaml&lt;/code&gt; directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;oauthConfig&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;identityProviders&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keystone&lt;/span&gt;
    &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;mappingMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claim&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
      &lt;span class="na"&gt;domainName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exampleDoamin&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;KeystonePasswordIdentityProvider&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://api.us-east-1.dacloud.com:13000/v3/&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ldap&lt;/span&gt;
    &lt;span class="na"&gt;challenge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;mappingMethod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claim&lt;/span&gt;
    &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
      &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LDAPPasswordIdentityProvider&lt;/span&gt;
      &lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dn&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mail&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;cn&lt;/span&gt;
        &lt;span class="na"&gt;preferredUsername&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;uid&lt;/span&gt;
      &lt;span class="na"&gt;bindDN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uid=binddn,cn=accounts,dc=example,dc=com"&lt;/span&gt;
      &lt;span class="na"&gt;bindPassword&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;superSecretPass"&lt;/span&gt;
      &lt;span class="na"&gt;ca&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/ssl/certs/idm-ca.pem&lt;/span&gt;
      &lt;span class="na"&gt;insecure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ldaps://idm.example.com/cn=accounts,dc=example,dc=com?uid"&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Restart the OpenShift Masters
&lt;/h2&gt;

&lt;p&gt;That configuration manifest now allows the OpenShift cluster to authenticate with either the &lt;strong&gt;&lt;em&gt;keystone&lt;/em&gt;&lt;/strong&gt; method or the &lt;strong&gt;&lt;em&gt;ldap&lt;/em&gt;&lt;/strong&gt; method.  Before it will take, all the masters need to be restarted, so run the following commands on your OpenShift Masters:&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;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/local/bin/master-restart api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo&lt;/span&gt; /usr/local/bin/master-restart controllers
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Once the masters restart, you should be able to log in to OpenShift with either identity providers now!&lt;/p&gt;

</description>
      <category>100daysofcode</category>
      <category>kubernetes</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cache Busting in Docker</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Tue, 28 Jan 2020 12:20:23 +0000</pubDate>
      <link>https://dev.to/kenmoini/cache-busting-in-docker-2ngh</link>
      <guid>https://dev.to/kenmoini/cache-busting-in-docker-2ngh</guid>
      <description>&lt;p&gt;Just putting the finishing touches on a new &lt;strong&gt;&lt;em&gt;Ansible Tower&lt;/em&gt;&lt;/strong&gt; Workshop, testing an additional exercise, building my Docker container locally before I push the changes to the repo to build my production image in Docker Hub.&lt;/p&gt;

&lt;p&gt;So as I'm sitting here in the Nashville Airport waiting for this container to build and a bit of time to pass for us to board, I figured it'd be a good time to do a bit of writing.  &lt;strong&gt;&lt;em&gt;Here's a trick I use that can save you hours in building containers...&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Premise
&lt;/h3&gt;

&lt;p&gt;I start with a very basic Alpine image and add my needed packages - a few from the &lt;strong&gt;&lt;em&gt;apk&lt;/em&gt;&lt;/strong&gt; package manager, but then I compile nginx from source.  This is because it's much easier to set proper operating conditions for the nginx server in a container.  Then, after the nginx server is compiled and ready to roll, I build my application and dump it into &lt;code&gt;/var/www/html&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Now, compiling nginx from source takes time.  I have separate &lt;strong&gt;&lt;em&gt;RUN&lt;/em&gt;&lt;/strong&gt; stanzas in the Dockerfile to benefit from the image layering that container images provide, but Docker isn't always that great at detecting changes.  If I make a few changes to the web application, Docker doesn't always copy over the new files.&lt;/p&gt;

&lt;p&gt;To quickly fix this, one could prune the built images in the local cache - but this means starting from the top, which means recompiling nginx, which takes a good deal of time.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution
&lt;/h3&gt;

&lt;p&gt;How do we tell Docker explicitly where to start from?  Just add an ENV between your static and dynamic actions, then change a small variable in between builds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:8-alpine&lt;/span&gt;

&lt;span class="c"&gt;# Install base packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk &lt;span class="nt"&gt;-U&lt;/span&gt; add wget &lt;span class="nb"&gt;tar gzip &lt;/span&gt;git asciidoctor

&lt;span class="c"&gt;# Install nginx&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nv"&gt;build_pkgs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"build-base linux-headers openssl-dev pcre-dev wget zlib-dev"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nv"&gt;runtime_pkgs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ca-certificates openssl pcre zlib"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk &lt;span class="nt"&gt;--update&lt;/span&gt; add &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;build_pkgs&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;runtime_pkgs&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  wget http://nginx.org/download/nginx-1.15.8.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;tar &lt;/span&gt;xzf nginx-1.15.8.tar.gz &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;cd&lt;/span&gt; /tmp/nginx-1.15.8 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  ./configure &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/nginx &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--sbin-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/sbin/nginx &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--conf-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/nginx/nginx.conf &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--error-log-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/nginx/error.log &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--http-log-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/var/log/nginx/access.log &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--pid-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/run/nginx.pid &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--lock-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/run/nginx.lock &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--http-client-body-temp-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/cache/nginx/client_temp &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--http-proxy-temp-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/cache/nginx/proxy_temp &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--http-fastcgi-temp-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/cache/nginx/fastcgi_temp &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--http-uwsgi-temp-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/cache/nginx/uwsgi_temp &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--http-scgi-temp-path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/cache/nginx/scgi_temp &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_ssl_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_realip_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_addition_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_sub_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_dav_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_flv_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_mp4_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_gunzip_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_gzip_static_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_random_index_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_secure_link_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_stub_status_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_auth_request_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-mail&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-mail_ssl_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-file-aio&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-ipv6&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-threads&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-stream&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-stream_ssl_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_slice_module&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--with-http_v2_module&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  make &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  make &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /dev/stdout /var/log/nginx/access.log &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-sf&lt;/span&gt; /dev/stderr /var/log/nginx/error.log &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  adduser &lt;span class="nt"&gt;-D&lt;/span&gt; nginx &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /tmp/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  apk del &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;build_pkgs&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; /var/cache/apk/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/cache/nginx/scgi_temp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/cache/nginx/uwsgi_temp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/cache/nginx/fastcgi_temp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/cache/nginx/proxy_temp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/cache/nginx/client_temp &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;  &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /tmp/run

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; cacheBUSTER v9001&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; conf/nginx.conf /etc/nginx/nginx.conf&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; webapp/* /var/www/html/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now, simply change that &lt;strong&gt;&lt;em&gt;cacheBuster&lt;/em&gt;&lt;/strong&gt; ENV definition to some other string and enjoy a cached image layer with nginx while being able to pull in fresh application data every time!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>100daysofcode</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Automated DigitalOcean DNS ABCs: APIs, Bash, &amp; cURL</title>
      <dc:creator>Ken Moini</dc:creator>
      <pubDate>Wed, 15 Jan 2020 23:20:44 +0000</pubDate>
      <link>https://dev.to/kenmoini/automated-digitalocean-dns-abcs-apis-bash-curl-12n7</link>
      <guid>https://dev.to/kenmoini/automated-digitalocean-dns-abcs-apis-bash-curl-12n7</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;This script is an extraction of another larger single-node Kubernetes on DigitalOcean deployer that can be read here:&lt;/em&gt;&lt;/strong&gt; &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/kenmoini" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F315850%2Fb804ce4d-4dfb-4b9a-bf6c-adc8918b06bf.png" alt="kenmoini"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/kenmoini/all-in-one-kubernetes-host-on-digitalocean-2o93" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;All-in-one Kubernetes Host on DigitalOcean&lt;/h2&gt;
      &lt;h3&gt;Ken Moini ・ Jan 13 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#kubernetes&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#100daysofcode&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#beginners&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;So while doing the whole &lt;strong&gt;&lt;em&gt;DevOps thing&lt;/em&gt;&lt;/strong&gt; you usually have to build automation to deploy infrastructure and platforms.  It's often not just Virtual Machines that you need to create and automate, but also DNS, networking, and more.  When using &lt;strong&gt;&lt;em&gt;Ansible&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;Terraform&lt;/em&gt;&lt;/strong&gt; with the larger public cloud providers this is no problem, but sometimes the more niche providers such as DigitalOcean and Linode don't have mature modules and providers in those automation platforms.&lt;/p&gt;

&lt;p&gt;Thankfully though, pretty much everyone has mature APIs available for use, including DigitalOcean and Linode!  Yay, my friends are still cool!  So in deploying a system to DigitalOcean with Ansible or Terraform we can create and use a Bash script wrapper that has some functions that call to the DigitalOcean API and process things for us.&lt;/p&gt;

&lt;p&gt;So let's build out a Bash script that will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check to see if a DNS Zone exists for a supplied domain&lt;/li&gt;
&lt;li&gt;Check to see if a specific record exists in that Zone&lt;/li&gt;
&lt;li&gt;Create a record with a specific type and value&lt;/li&gt;
&lt;li&gt;Add addition or delete and overwrite records when directed/forced&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h2&gt;
  
  
  What-what-whaaaaat?
&lt;/h2&gt;

&lt;p&gt;Beefy bits eh?  I like providing the whole source upfront in case you wanna skip the small-talk, then stepping through the functionality to add detail.  Let's review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The commented &lt;code&gt;set -x&lt;/code&gt; makes Bash print the invocation of each line as well, useful for debugging variables as they're expanded and passed around.&lt;/li&gt;
&lt;li&gt;Set a few variables, the &lt;code&gt;DO_PAT&lt;/code&gt; variable is imported when previously exported and set in the shell, otherwise is defined as blank.  This is your DigitalOcean Personal Access Token, &lt;a href="https://www.digitalocean.com/docs/api/create-personal-access-token/" rel="noopener noreferrer"&gt;get one here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Bash does not hoist functions like JavaScript does so functions needed earlier need to be defined before earlier.  The &lt;code&gt;print_help&lt;/code&gt; function is an example of that.&lt;/li&gt;
&lt;li&gt;The if/while/switch-case defines and switches through arguments passed into the script, defines needed variables where supplied&lt;/li&gt;
&lt;li&gt;Some arguments are required, so we check for &lt;code&gt;$domain&lt;/code&gt;, &lt;code&gt;$ip_addr&lt;/code&gt;, and &lt;code&gt;$record_name&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;There are some programs that are required on the system this script is run, namely &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;jq&lt;/code&gt; - the Bash script has a &lt;code&gt;checkForProgram&lt;/code&gt; function that will check for the existence of those programs&lt;/li&gt;
&lt;li&gt;First up, the &lt;code&gt;checkDomain&lt;/code&gt; function - this one is a pretty simple idea, before we do anything we want to make sure the domain in question has a Zone in DigitalOcean's DNS servers.  We attempt to &lt;a href="https://developers.digitalocean.com/documentation/v2/#retrieve-an-existing-domain" rel="noopener noreferrer"&gt;retrieve an existing domain&lt;/a&gt; from the DigitalOcean API and use &lt;code&gt;jq&lt;/code&gt; to check to see if the returned JSON value is null or not&lt;/li&gt;
&lt;li&gt;Next, we define the &lt;code&gt;checkRecord&lt;/code&gt; function which takes in a supplied domain and record name+type.  So say you wanted to check to see if the domain &lt;strong&gt;&lt;em&gt;example.com&lt;/em&gt;&lt;/strong&gt; has an &lt;strong&gt;&lt;em&gt;A&lt;/em&gt;&lt;/strong&gt; type record that is named &lt;strong&gt;&lt;em&gt;k8s&lt;/em&gt;&lt;/strong&gt;, or otherwise, that's to check if the &lt;strong&gt;&lt;em&gt;A&lt;/em&gt;&lt;/strong&gt; record for &lt;strong&gt;&lt;em&gt;k8s.example.com&lt;/em&gt;&lt;/strong&gt; exists or not.&lt;/li&gt;
&lt;li&gt;Since you can store multiple records with the same name+type, we need to have a way to delete extra records that are stale.  The &lt;code&gt;deleteRecord&lt;/code&gt; function makes the API call to do exactly that with whatever DigitalOcean DNS Record ID it is passed.&lt;/li&gt;
&lt;li&gt;Finally, the &lt;code&gt;writeDNS&lt;/code&gt; function writes a DNS Record of the specified name, type, and value to a specific domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of the script basically just goes through some logic to check if the domain exists, if so then check if the record exists - if not, create the specified record.  There is also a forced override that will delete previous matched records and create the intended one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Make sure to pass the executable bit to the script with &lt;code&gt;chmod +x ./config_dns.sh&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Before starting, take that DigitalOcean Personal Access Token and export it to a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DO_PAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-token-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1) Say we just used Ansible or Terraform to create a new Droplet.  This new Droplet has an IP address of &lt;strong&gt;&lt;em&gt;12.34.56.78&lt;/em&gt;&lt;/strong&gt; and will be hosting our new blog at &lt;strong&gt;&lt;em&gt;wombat-adventures.com&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;&lt;a href="http://www.wombat-adventures.com" rel="noopener noreferrer"&gt;www.wombat-adventures.com&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt;.  We can use this script like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;-d&lt;/span&gt; wombat-adventures.com &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"@"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"12.34.56.78"&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;--domain&lt;/span&gt; wombat-adventures.com &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--type&lt;/span&gt; &lt;span class="s2"&gt;"A"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--record&lt;/span&gt; &lt;span class="s2"&gt;"www"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--ip&lt;/span&gt; &lt;span class="s2"&gt;"12.34.56.78"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2) You have a new Load Balancer with an IP Address of &lt;strong&gt;&lt;em&gt;98.76.54.43&lt;/em&gt;&lt;/strong&gt; and want to set a CNAME record at &lt;strong&gt;&lt;em&gt;lb.internal.shootersbar.com&lt;/em&gt;&lt;/strong&gt;, deleting any other records that match and ensuring only one exists with the new Load Balancer IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;-d&lt;/span&gt; shootersbar.com &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"CNAME"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"lb.internal"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"98.76.54.43"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3) You need to configure GMail/GSuite MX records for mail at hyperloop-sandwiches.net&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;-d&lt;/span&gt; hyperloop-sandwiches.net &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"MX"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"@"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"ASPMX.L.GOOGLE.COM"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--force-add&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;-d&lt;/span&gt; hyperloop-sandwiches.net &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"MX"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"@"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"ALT1.ASPMX.L.GOOGLE.COM"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--priority&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--force-add&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;-d&lt;/span&gt; hyperloop-sandwiches.net &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"MX"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"@"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"ALT2.ASPMX.L.GOOGLE.COM"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--priority&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--force-add&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;-d&lt;/span&gt; hyperloop-sandwiches.net &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"MX"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"@"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"ALT3.ASPMX.L.GOOGLE.COM"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--priority&lt;/span&gt; &lt;span class="s2"&gt;"10"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--force-add&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;./config_dns.sh &lt;span class="nt"&gt;-d&lt;/span&gt; hyperloop-sandwiches.net &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s2"&gt;"MX"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"@"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"ALT4.ASPMX.L.GOOGLE.COM"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--priority&lt;/span&gt; &lt;span class="s2"&gt;"10"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
                  &lt;span class="nt"&gt;--force-add&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's that right there, pretty simple wrapper for the DigitalOcean API to set DNS records.  It's fairly extensible, supports a wide range of records and use cases, and is pretty simple to plug into most pipelines and workflows so &lt;strong&gt;&lt;em&gt;get to automatin'&lt;/em&gt;&lt;/strong&gt;!&lt;/p&gt;

</description>
      <category>bash</category>
      <category>devops</category>
      <category>linux</category>
      <category>100daysofcode</category>
    </item>
  </channel>
</rss>
