<?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: Javel Rowe</title>
    <description>The latest articles on DEV Community by Javel Rowe (@cloudkungfu).</description>
    <link>https://dev.to/cloudkungfu</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%2F572522%2F0e0557df-24bc-45da-b68b-c8abc0c246a8.png</url>
      <title>DEV Community: Javel Rowe</title>
      <link>https://dev.to/cloudkungfu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cloudkungfu"/>
    <language>en</language>
    <item>
      <title>Video Annotator</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Mon, 13 May 2024 06:37:13 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/video-annotator-375a</link>
      <guid>https://dev.to/cloudkungfu/video-annotator-375a</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/netlify"&gt;Netlify Dynamic Site Challenge&lt;/a&gt;: Build with Blobs.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;VNote is a video annotation tool. &lt;/p&gt;

&lt;p&gt;By adding a Youtube video URL to the input field on the landing page, users can access a note-taking interface integrated with the video player.  This enables users to create timestamped notes directly linked to specific moments in the video, aiding in comprehension, retention, and collaboration through a shareable link.&lt;/p&gt;

&lt;h3&gt;
  
  
  Target Users
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Educators&lt;/strong&gt;: Create interactive video lessons with embedded notes, questions, or links to supplementary materials.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Students&lt;/strong&gt;: Collaborate on study guides by annotating lecture videos or documentaries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Researchers&lt;/strong&gt;: Analyze and code qualitative video data with precise timestamps and linked resources.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Content Creators&lt;/strong&gt;: Include additional context, behind-the-scenes details, or interactive elements to videos.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Demo Link
&lt;/h3&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://vnote.pro/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--8Dmq5RdT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://objectstorage.ca-toronto-1.oraclecloud.com/p/XRwMBzmXfknfFlObnOaFiCIdJqeCpRMoFJix6H2UEOR-zJC-bSOXxnzxf4NWl-Yj/n/yzpjtx1indjl/b/vnote-pro/o/VNOTE.png" height="500" class="m-0" width="500"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://vnote.pro/" rel="noopener noreferrer" class="c-link"&gt;
          VNote Pro
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Annotate, share, and learn from videos.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--uJc39SUe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://vnote.pro/favicon.ico" width="256" height="256"&gt;
        vnote.pro
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Video Demo
&lt;/h3&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/-jEULJypvxM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Github Repo
&lt;/h3&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--A9-wwsHG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/perplexedyawdie"&gt;
        perplexedyawdie
      &lt;/a&gt; / &lt;a href="https://github.com/perplexedyawdie/video-annotator"&gt;
        video-annotator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Annotate, share, and learn from videos.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Welcome to VNote Pro 👋&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/690cb491a90ef7339f4fca931a87b1ced81691b289f50e0788403d078086e2c0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e312e302d626c75652e7376673f63616368655365636f6e64733d32353932303030"&gt;&lt;img alt="Version" src="https://camo.githubusercontent.com/690cb491a90ef7339f4fca931a87b1ced81691b289f50e0788403d078086e2c0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f76657273696f6e2d302e312e302d626c75652e7376673f63616368655365636f6e64733d32353932303030"&gt;&lt;/a&gt;
  &lt;a href="https://github.com/perplexedyawdie/video-annotator#"&gt;
    &lt;img alt="License: MIT" src="https://camo.githubusercontent.com/a4426cbe5c21edb002526331c7a8fbfa089e84a550567b02a0d829a98b136ad0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667"&gt;
  &lt;/a&gt;
&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Capture and share key moments from any video with time-stamped notes, links, etc. Easily collaborate and learn by sharing your annotated videos.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;🏠 &lt;a href="https://vnote.pro" rel="nofollow"&gt;Homepage&lt;/a&gt;
&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Install&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Make a copy of the &lt;code&gt;.env.example&lt;/code&gt; file and rename it to &lt;code&gt;.env.local&lt;/code&gt; then add the required env vars.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;You can run the command below or use the provided Dockerfile.&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm run dev&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Author&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;👤 &lt;strong&gt;Javel Rowe&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://javel.dev" rel="nofollow"&gt;https://javel.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/perplexedyawdie"&gt;@perplexedyawdie&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://linkedin.com/in/javel-rowe" rel="nofollow"&gt;@javel-rowe&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Show your support&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Give a ⭐️ if this project helped you!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This README was generated with ❤️ by &lt;a href="https://github.com/kefranabg/readme-md-generator"&gt;readme-md-generator&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/perplexedyawdie/video-annotator"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;&lt;strong&gt;Landing Page&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvutpuunwwjpcu871leg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzvutpuunwwjpcu871leg.png" alt="Landing Page" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Taking Notes&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0kfdy11kdf0hwfu907d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn0kfdy11kdf0hwfu907d.png" alt="Taking notes!" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Platform Primitives
&lt;/h2&gt;

&lt;p&gt;Netlify Blobs made it super easy to quickly store/retrieve notes data.&lt;/p&gt;

&lt;p&gt;Specifically, I was able to interact with Netlify Blobs by using the Typescript client. First, I initialized the client like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@netlify/blobs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;note-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;siteID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NETLIFY_SITE_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NETLIFY_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then I was able to easily save data like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noteData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VNote&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noteData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;noteData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and retrieving was just as simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getNoteData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noteId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;VNote&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;noteData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noteId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;noteData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;noteData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;noteData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;noteDetails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;noteData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;noteDetails&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unable to retrieve note!&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="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This site was hosted on Netlify &amp;amp; I added a custom domain.&lt;/p&gt;

</description>
      <category>netlifychallenge</category>
      <category>devchallenge</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Helm 101: Creating Helm Charts</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Tue, 06 Feb 2024 15:21:25 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/helm-101-creating-helm-charts-eon</link>
      <guid>https://dev.to/cloudkungfu/helm-101-creating-helm-charts-eon</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Helm is the package manager for Kubernetes, like apt, yum, or Homebrew for traditional operating systems. It simplifies the deployment and management of applications on Kubernetes clusters by packaging all necessary components into a single, manageable unit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Helm?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplifies Kubernetes Deployments&lt;/strong&gt;: Helm packages, known as charts, bundle together all the Kubernetes manifests (like deployments, services, etc.) needed to deploy an application, making the deployment process straightforward and repeatable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manages Dependencies&lt;/strong&gt;: Helm charts can include other charts, allowing complex applications to manage their dependencies effectively.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version Control for Deployments&lt;/strong&gt;: Helm tracks versions of deployments, enabling rollbacks to previous states if needed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What is it made of?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chart&lt;/strong&gt;: A Helm chart is a bundle containing all information necessary to create an instance of a Kubernetes application. It includes resource definitions and configuration files.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Config&lt;/strong&gt;: The configuration contains customizable parameters that merge with a chart to create a release. These parameters allow the same chart to be used in different environments or configurations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Release&lt;/strong&gt;: A release is an instance of a chart running in a Kubernetes cluster, combined with a specific configuration.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Helm Architecture Diagram
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FCgUx9GA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://storage.googleapis.com/jr-blog-images/helm-101-helm-architecture.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FCgUx9GA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://storage.googleapis.com/jr-blog-images/helm-101-helm-architecture.png" alt="Helm Architecture" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Helm Client&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chart Development&lt;/strong&gt;: Local chart development and managing repositories.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Release Management&lt;/strong&gt;: Manages releases/repos and request chart installation or release upgrade/uninstallation through Helm Library.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Helm Library&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chart Management&lt;/strong&gt;: Handles combining charts and configurations to create releases and installing/upgrading/uninstalling charts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interacts with Kubernetes&lt;/strong&gt;: Interfaces with the Kubernetes API server.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating a Chart
&lt;/h3&gt;

&lt;p&gt;Creating a Helm chart involves setting up a predefined directory structure with at least two files: &lt;code&gt;Chart.yaml&lt;/code&gt; for metadata about the chart and &lt;code&gt;values.yaml&lt;/code&gt; for default configuration values.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Structure&lt;/strong&gt;: A basic chart directory will have the following layout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Chart.yaml&lt;/code&gt;: Contains metadata about the chart such as name, version, and description.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;values.yaml&lt;/code&gt;: Specifies default configuration values for the chart.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;templates/&lt;/code&gt;: This directory contains template files that generate Kubernetes manifest files based on the values provided.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Templating&lt;/strong&gt;: Helm uses a templating engine to substitute values in the chart templates, creating Kubernetes manifests tailored to specific deployments. This allows for dynamic adjustment of resources, labels, and configurations without altering the original chart files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;A startup, "DevOps Solutions" adopts Helm to streamline their Kubernetes deployments. You're a consultant tasked with creating a basic Helm Chart for &lt;a href="https://n8n.io/"&gt;n8n&lt;/a&gt;. It should be customizable for different environments using values.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/perplexedyawdie/helm-learn.git

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Change directory to &lt;code&gt;creating-charts&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;helm-learn/creating-charts

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Spin up the environment using docker compose
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;SSH into the &lt;code&gt;ubuntu&lt;/code&gt; container
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;NoHostAuthenticationForLocalhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes &lt;/span&gt;root@localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 2222
&lt;span class="c"&gt;# password: test123&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Generate a chart directory along with sample files
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm create my-n8n

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;View the structure of the generated directory
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tree &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Output should look similar to this&lt;/span&gt;
&lt;span class="c"&gt;#`-- my-n8n&lt;/span&gt;
&lt;span class="c"&gt;#    |-- Chart.yaml&lt;/span&gt;
&lt;span class="c"&gt;#    |-- charts&lt;/span&gt;
&lt;span class="c"&gt;#    |-- templates&lt;/span&gt;
&lt;span class="c"&gt;#    |   |-- NOTES.txt&lt;/span&gt;
&lt;span class="c"&gt;#    |   |-- _helpers.tpl&lt;/span&gt;
&lt;span class="c"&gt;#    |   |-- deployment.yaml&lt;/span&gt;
&lt;span class="c"&gt;#    |   |-- hpa.yaml&lt;/span&gt;
&lt;span class="c"&gt;#    |   |-- ingress.yaml&lt;/span&gt;
&lt;span class="c"&gt;#    |   |-- service.yaml&lt;/span&gt;
&lt;span class="c"&gt;#    |   |-- serviceaccount.yaml&lt;/span&gt;
&lt;span class="c"&gt;#    |   `-- tests&lt;/span&gt;
&lt;span class="c"&gt;#    |       `-- test-connection.yaml&lt;/span&gt;
&lt;span class="c"&gt;#    `-- values.yaml&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;We'll only be using &lt;code&gt;Chart.yaml&lt;/code&gt; , &lt;code&gt;values.yaml&lt;/code&gt;, &lt;code&gt;my-n8n/templates/NOTES.txt&lt;/code&gt; and &lt;code&gt;my-n8n/templates/_helpers.tpl&lt;/code&gt; so we can remove the rest since we'll be adding our own manifest files.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; my-n8n/templates/&lt;span class="k"&gt;*&lt;/span&gt;.yaml my-n8n/templates/tests

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Update the appVersion in Chart.yaml to "1.27.2" &amp;amp; clear the contents of my-n8n/templates/NOTES.txt file then update with the following.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano my-n8n/Chart.yaml
&lt;span class="c"&gt;# appVersion: "1.27.2"&lt;/span&gt;

nano my-n8n/templates/NOTES.txt
&lt;span class="c"&gt;# Welcome to n8n.&lt;/span&gt;
&lt;span class="c"&gt;# Wait a few minutes until the status changes to RUNNING.&lt;/span&gt;
&lt;span class="c"&gt;# After it is ready, access it from: http://localhost:2223&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Update the &lt;code&gt;values.yaml&lt;/code&gt; file with the following data
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Default values for my-n8n.&lt;/span&gt;
&lt;span class="c1"&gt;# This is a YAML-formatted file.&lt;/span&gt;
&lt;span class="c1"&gt;# Declare variables to be passed into your templates.&lt;/span&gt;

&lt;span class="na"&gt;replicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8nio/n8n&lt;/span&gt;
  &lt;span class="na"&gt;pullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
  &lt;span class="c1"&gt;# Overrides the image tag whose default is the chart appVersion.&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.27.2"&lt;/span&gt;

&lt;span class="na"&gt;containerPorts&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;http&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;5678&lt;/span&gt;

&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&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;30200&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;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
&lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;

&lt;span class="na"&gt;autoscaling&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;enabled&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;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
  &lt;span class="na"&gt;targetCPUUtilizationPercentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="c1"&gt;# targetMemoryUtilizationPercentage: 80&lt;/span&gt;

&lt;span class="c1"&gt;# Additional volumes on the output Deployment definition.&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-data&lt;/span&gt;

&lt;span class="na"&gt;persistence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-data&lt;/span&gt;

&lt;span class="c1"&gt;# Additional volumeMounts on the output Deployment definition.&lt;/span&gt;
&lt;span class="na"&gt;volumeMounts&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;n8n-data&lt;/span&gt;
   &lt;span class="na"&gt;mountPath&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/node/.n8n"&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create the &lt;code&gt;deployment.yaml&lt;/code&gt; , &lt;code&gt;service.yaml&lt;/code&gt; and, &lt;code&gt;pvc.yaml&lt;/code&gt; files inside the &lt;code&gt;my-n8n/templates&lt;/code&gt; folder
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my-n8n/templates/pvc.yaml&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;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;n8n-data&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Gi&lt;/span&gt; &lt;span class="c1"&gt;# Adjust the size as per your requirement&lt;/span&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my-n8n/templates/deployment.yaml&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;apps/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;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;n8n&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
    &lt;span class="na"&gt;spec&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8nio/n8n&lt;/span&gt;
        &lt;span class="na"&gt;ports&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;http&lt;/span&gt;        
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
        &lt;span class="na"&gt;volumeMounts&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;n8n-data&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/node/.n8n&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-data&lt;/span&gt;
        &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n-data&lt;/span&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my-n8n/templates/service.yaml&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;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&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;n8n&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&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;5678&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30200&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Now, let's parameterize these using the go-template language to convert them to templates. Helm will fetch the appropriate values from the &lt;code&gt;values.yaml&lt;/code&gt; file to generate the manifest that will be deployed on K8S.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my-n8n/templates/pvc.yaml&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;PersistentVolumeClaim&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.persistence.claimName&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Gi&lt;/span&gt; &lt;span class="c1"&gt;# Adjust the size as per your requirement&lt;/span&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my-n8n/templates/deployment.yaml&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;apps/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;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;printf "%s-%s" .Release.Name .Chart.Name&lt;/span&gt;  &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
    &lt;span class="na"&gt;spec&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;printf "%s&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="nv"&gt;s" .Values.image.repository .Values.image.tag&lt;/span&gt;  &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- range .Values.containerPorts&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.port&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;        
        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- range .Values.volumeMounts&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.mountPath&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&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="nv"&gt;- range .Values.volumes&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.name&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
        &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;$.Values.persistence.claimName&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
      &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;          

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my-n8n/templates/service.yaml&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;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&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="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;printf "%s-%s" .Release.Name .Chart.Name&lt;/span&gt;  &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.service.type&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&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;5678&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5678&lt;/span&gt;
    &lt;span class="na"&gt;nodePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;.Values.service.port&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;    
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;n8n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Ensure that you're in the directory that contains the chart &lt;code&gt;my-n8n&lt;/code&gt; then run the linter
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;helm lint my-n8n&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install the chart
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;helm install my-n8n ./my-n8n&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Wait until the deployment is complete, you can continually check using the following command.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;kubectl get all&lt;/span&gt;

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

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;After the status has changed to running, you should be able to access it in your browser from: &lt;a href="http://localhost:2223"&gt;http://localhost:2223&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Great effort! You just learned how to create and configure a Helm chart then deploy it to a Kubernetes cluster!&lt;/p&gt;

</description>
      <category>helm</category>
      <category>kubernetes</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Ansible 101: Modularization &amp; Debugging</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Wed, 31 Jan 2024 12:51:20 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/ansible-101-modularization-debugging-247k</link>
      <guid>https://dev.to/cloudkungfu/ansible-101-modularization-debugging-247k</guid>
      <description>&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;When playbooks grow in size, they become challenging to manage. Modularization helps by separating logic from data, making playbooks cleaner and more maintainable. As you introduce these abstractions, it's crucial to integrate robust error handling and debugging techniques to ensure reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Roles
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulv3pgy1l6slft4hfmia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fulv3pgy1l6slft4hfmia.png" alt="Diagram showing roles directory structure" width="800" height="565"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Roles in Ansible are units of organization that allow you to group related tasks, variables, files, and templates. They make your playbooks modular. Here’s how to create and use them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Creating a Role:&lt;/strong&gt; Organize your playbook into a directory structure with separate files for tasks, handlers, defaults, vars, files, and templates.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using Roles:&lt;/strong&gt; Incorporate a role into your playbook with the &lt;code&gt;roles&lt;/code&gt; property or the &lt;code&gt;include_role&lt;/code&gt; module for dynamic inclusion.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&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;all&lt;/span&gt;
  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_custom_role&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Error Handling &amp;amp; Debugging
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ansible provides ways to define custom failure conditions and manage the playbook execution flow when encountering errors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Block and Rescue:&lt;/strong&gt; Use &lt;code&gt;block&lt;/code&gt; to group tasks and &lt;code&gt;rescue&lt;/code&gt; to define remediation steps if any task in the block fails.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;block&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;Attempt to do something&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/false&lt;/span&gt;
    &lt;span class="na"&gt;rescue&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;This will run on failure&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Debugger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ansible’s debugger lets you interactively troubleshoot tasks that fail during playbook execution. It can be invoked by adding &lt;code&gt;debugger&lt;/code&gt; to your playbook. Use one of the following values to control when the debugger is activated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;always&lt;/code&gt;: always activate debugger&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;never&lt;/code&gt;: never activate debugger&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;on_failed&lt;/code&gt;: activate only on task failure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;on_unreachable&lt;/code&gt;: activate when a host is unreachable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;on_skipped&lt;/code&gt;: activate when a task is skipped&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Install NGINX Web Server&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;webservers&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;Install NGINX&lt;/span&gt;
     &lt;span class="na"&gt;ansible.builtin.apt&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;httpd&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
     &lt;span class="na"&gt;debugger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on_failed&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Core commands when working with the debugger include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;print&lt;/code&gt;: display task details&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;task.args['arg-name'] = 'updated-val'&lt;/code&gt;: update arg value&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;redo&lt;/code&gt;: rerun the task&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;quit&lt;/code&gt;: exit the debugger&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;Our aim is to modularize the process of installing NGINX and updating the homepage. We also want to handle an error caused by incorrect package name and dynamically fix it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/perplexedyawdie/ansible-learn.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Spin up the environment using docker-compose&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. SSH into the Ansible server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;NoHostAuthenticationForLocalhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes &lt;/span&gt;root@localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 2200# password: test123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Change directory to &lt;code&gt;ansible_learn&lt;/code&gt;&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="nb"&gt;cd &lt;/span&gt;ansible_learn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Roles &amp;amp; Modularization
&lt;/h3&gt;

&lt;p&gt;5. Create the roles directory with the relevant folders. For this task, we'll only require &lt;code&gt;tasks&lt;/code&gt; , &lt;code&gt;templates&lt;/code&gt; and &lt;code&gt;handlers&lt;/code&gt;&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="nb"&gt;cd &lt;/span&gt;ansible_learn

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; roles/nginx/tasks roles/nginx/templates roles/nginx/handlers

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

&lt;/div&gt;



&lt;p&gt;6. Create the template that will generate the homepage.&lt;/p&gt;

&lt;p&gt;Filename: &lt;code&gt;index.html.j2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Location: &lt;code&gt;ansible_learn/roles/nginx/templates&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&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;title&amp;gt;&lt;/span&gt;Welcome to {{ ansible_facts['os_family'] }}&lt;span class="nt"&gt;&amp;lt;/title&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;h1&amp;gt;&lt;/span&gt;Server running on {{ ansible_facts['distribution'] }}&lt;span class="nt"&gt;&amp;lt;/h1&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;7. Create the tasks that will install NGINX, generate the homepage, and copy it to the server.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Filename: &lt;code&gt;main.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Location: &lt;code&gt;ansible_learn/roles/nginx/tasks&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Install NGINX for Debian-based systems&lt;/span&gt;   
  &lt;span class="na"&gt;ansible.builtin.apt&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;nginx&lt;/span&gt;
   &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&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;Create Homepage with Jinja2 Template for NGINX&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.html.j2&lt;/span&gt;
   &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/index.html&lt;/span&gt;
   &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;644'&lt;/span&gt;
  &lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restart nginx&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;8. Create the handler that will restart NGINX upon update.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Filename: &lt;code&gt;main.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Location: &lt;code&gt;ansible_learn/roles/nginx/handlers&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Restart NGINX&lt;/span&gt;
  &lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restart&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx"&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.service&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;nginx&lt;/span&gt;
   &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restarted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;9. Create the playbook.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Filename: &lt;code&gt;nginx_setup.yaml&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Location: &lt;code&gt;ansible_learn&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Install NGINX&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;all&lt;/span&gt;
  &lt;span class="na"&gt;roles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;    

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

&lt;/div&gt;



&lt;p&gt;10. Run the linter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-lint nginx_setup.yaml

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

&lt;/div&gt;



&lt;p&gt;11. Execute the playbook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml nginx_setup.yaml

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

&lt;/div&gt;



&lt;p&gt;12. Confirm deployment by visiting &lt;a href="http://localhost:2203/"&gt;http://localhost:2203&lt;/a&gt; in your browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error Handling
&lt;/h3&gt;

&lt;p&gt;The aim is to use &lt;code&gt;block&lt;/code&gt; and &lt;code&gt;rescue&lt;/code&gt; to print a custom message on error.&lt;/p&gt;

&lt;p&gt;13. Create a new playbook that should install Apache but use the wrong package name then add block &amp;amp; rescue properties.Filename: &lt;code&gt;error_test.yaml&lt;/code&gt;Location: &lt;code&gt;ansible_learn&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Install Apache on Ubuntu&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;all&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;Install Apache for Debian-based systems&lt;/span&gt;
      &lt;span class="na"&gt;block&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;Installer&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.apt&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;httpd&lt;/span&gt;
            &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
      &lt;span class="na"&gt;rescue&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;Installer errors&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;installing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Apache&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&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;ansible_facts['distribution']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;14. Run the linter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-lint error_test.yaml

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

&lt;/div&gt;



&lt;p&gt;15. Execute the playbook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml error_test.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Debugging
&lt;/h3&gt;

&lt;p&gt;The aim is to use the &lt;code&gt;debugger&lt;/code&gt; and dynamically fix an error in a task.&lt;/p&gt;

&lt;p&gt;16. Update the &lt;code&gt;error_test.yaml&lt;/code&gt; playbook and add the &lt;code&gt;debugger&lt;/code&gt; property&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Install Apache on Ubuntu&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;all&lt;/span&gt;
  &lt;span class="na"&gt;debugger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;on_failed&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;Install Apache for Debian-based systems&lt;/span&gt;
      &lt;span class="na"&gt;block&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;Installer&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.apt&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;httpd&lt;/span&gt;
            &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
      &lt;span class="na"&gt;rescue&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;Installer errors&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;installing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Apache&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&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;ansible_facts['distribution']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;17. Run the linter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-lint error_test.yaml

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

&lt;/div&gt;



&lt;p&gt;18. Execute the playbook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml error_test.yaml

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

&lt;/div&gt;



&lt;p&gt;19. In the interactive debugger, run the following commands to update the package name&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="c"&gt;# display all args in the failed task, we are interested in the name, since that contains the name of the package&lt;/span&gt;
p task.args

&lt;span class="c"&gt;# update the package name. When installing Apache on Ubuntu, we use apache2&lt;/span&gt;
task.args[&lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'apache2'&lt;/span&gt;

&lt;span class="c"&gt;# rerun the task&lt;/span&gt;
redo

&lt;span class="c"&gt;# exit the debugger&lt;/span&gt;
quit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;20. Confirm successful installation of Apache&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server1 apache2 &lt;span class="nt"&gt;-V&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Great effort! We learned how to create playbooks that are easier to extend and maintain along with how to handle errors and debug tasks to ensure reliability. For the final tutorial, we'll look at how to safely store sensitive data using Ansible Vault. Til then, take care! 👋&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>docker</category>
    </item>
    <item>
      <title>Ansible 101: Facts &amp; Templates</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Mon, 29 Jan 2024 12:36:16 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/ansible-101-facts-templates-5a3g</link>
      <guid>https://dev.to/cloudkungfu/ansible-101-facts-templates-5a3g</guid>
      <description>&lt;p&gt;In Ansible, variables and facts, along with templates, are foundational tools for creating flexible automation workflows. Variables allow you to manage and change your configurations dynamically. Facts are a special subset of variables that Ansible gathers from the remote systems, providing context-specific information. Templates enable the generation of variable-driven configuration files, making your playbooks adaptable to varied environments and scenarios.&lt;/p&gt;

&lt;p&gt;These tools make playbooks reusable and adaptable, allowing you to avoid hard-coding values and enabling customization for different environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Variables &amp;amp; Facts
&lt;/h3&gt;

&lt;p&gt;Variables allow for parameters to be modified without altering the playbook's core logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xfjr9qfh0f9aj28tg43.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xfjr9qfh0f9aj28tg43.png" alt="Diagram showing how variables, facts and templates work together" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Types of Variables:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boolean:&lt;/strong&gt; True or False values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List:&lt;/strong&gt; An ordered collection of items.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dictionary:&lt;/strong&gt; Key-value pairs for complex data structures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registered Variables:&lt;/strong&gt; Captures the output of tasks to use later in your playbook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Facts:&lt;/strong&gt; Auto-collected variables that provide details about the remote systems you are managing.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;NB:&lt;/strong&gt; Avoid conflicts in variable names by using bracket notation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Print the distribution of the target&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;all&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;curr_time&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;now()&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&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;Distro Check&lt;/span&gt;
     &lt;span class="na"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&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;ansible_facts['distribution']&lt;/span&gt;&lt;span class="nv"&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;Timestamp:&lt;/span&gt;&lt;span class="nv"&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;curr_time&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Templates &amp;amp; Files
&lt;/h3&gt;

&lt;p&gt;Templates in Ansible use the Jinja2 templating language to dynamically create files using variable interpolation, loops, and conditionals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Write distro name&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;all&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;Write distro name&lt;/span&gt;
     &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;distro.j2&lt;/span&gt;
      &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/root/distro.txt&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;644'&lt;/span&gt;

&lt;span class="c1"&gt;# src: location of jinja2 template file&lt;/span&gt;
&lt;span class="c1"&gt;# dest: location it will be copied to&lt;/span&gt;
&lt;span class="c1"&gt;# permissions that will be granted to the file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;We're going to use the OS Family to determine whether to install NGINX of Lighttpd then we'll deploy a custom homepage to the remote host containing NGINX all without hardcoding hostnames.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/perplexedyawdie/ansible-learn.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Spin up the environment using docker-compose&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. SSH into the Ansible server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;NoHostAuthenticationForLocalhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes &lt;/span&gt;root@localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 2200# password: test123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Change directory to &lt;code&gt;ansible_learn&lt;/code&gt;&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="nb"&gt;cd &lt;/span&gt;ansible_learn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Variables &amp;amp; facts
&lt;/h3&gt;

&lt;p&gt;5. Create a playbook called &lt;code&gt;server_setup.yaml&lt;/code&gt;. Here, we'll setup NGINX &amp;amp; Lighttpd then output the name of the distro for each remote host&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Install NGINX on Debian &amp;amp; Lighttpd on RedHat&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;all&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;dev1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Debian"&lt;/span&gt;
   &lt;span class="na"&gt;dev2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RedHat"&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;Install NGINX for Debian-based systems&lt;/span&gt;   
     &lt;span class="na"&gt;ansible.builtin.apt&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;nginx&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
     &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ansible_facts['os_family'] == dev1&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;Install Lighttpd for RedHat-based systems&lt;/span&gt; 
     &lt;span class="na"&gt;ansible.builtin.yum&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;lighttpd&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
     &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ansible_facts['os_family'] == dev2&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;Display the distribution&lt;/span&gt;
     &lt;span class="na"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="nv"&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;ansible_facts['distribution']&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6. Run &lt;code&gt;ansible-lint&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-lint server_setup.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7. Run the playbook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml server_setup.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;8. Confirm that setup was successful&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server3 nginx &lt;span class="nt"&gt;-V&lt;/span&gt;

ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server2 lighttpd &lt;span class="nt"&gt;-v&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server1 lighttpd &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Templates &amp;amp; Files
&lt;/h3&gt;

&lt;p&gt;9. Create a Jinja2 template file called &lt;code&gt;index.html.j2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It will get auto populated with the OS Family &amp;amp; Distribution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&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;title&amp;gt;&lt;/span&gt;Welcome to {{ ansible_facts['os_family'] }}&lt;span class="nt"&gt;&amp;lt;/title&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;h1&amp;gt;&lt;/span&gt;Server running on {{ ansible_facts['distribution'] }}&lt;span class="nt"&gt;&amp;lt;/h1&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;10. Create a playbook called &lt;code&gt;custom_homepage.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We're deploying the custom homepage created above to NGINX then restarting the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Deploy Custom Homepage and restart&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;all&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;dev1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Debian"&lt;/span&gt;
   &lt;span class="na"&gt;dev2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RedHat"&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;Create Homepage with Jinja2 Template for NGINX&lt;/span&gt;
     &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;index.html.j2&lt;/span&gt;
      &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/index.html&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;644'&lt;/span&gt;
     &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ansible_facts['os_family'] == dev1&lt;/span&gt;
     &lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restart nginx&lt;/span&gt;

  &lt;span class="na"&gt;handlers&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;Restart NGINX&lt;/span&gt;
     &lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;restart&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx"&lt;/span&gt;
     &lt;span class="na"&gt;ansible.builtin.service&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;nginx&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restarted&lt;/span&gt;
     &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ansible_facts['os_family'] == dev1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;11. Run the linter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-lint custom_homepage.yaml

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

&lt;/div&gt;



&lt;p&gt;12. Run the playbook&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml custom_homepage.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;13. Confirm deployment by visiting &lt;a href="http://localhost:2203"&gt;&lt;code&gt;http://localhost:2203&lt;/code&gt;&lt;/a&gt; in your browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Awesome effort! 🙌 We've learned how to use variables &amp;amp; facts in a playbook along with how to create dynamic files using templates. Next we'll look at modularization and error handling. Til then, take care!&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>automation</category>
      <category>docker</category>
    </item>
    <item>
      <title>Ansible 101: Playbooks, Task Controls &amp; Handlers</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Wed, 24 Jan 2024 08:35:58 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/playbooks-task-controls-handlers-3143</link>
      <guid>https://dev.to/cloudkungfu/playbooks-task-controls-handlers-3143</guid>
      <description>&lt;h2&gt;
  
  
  What is a Playbook?
&lt;/h2&gt;

&lt;p&gt;Ansible playbooks allow you to declare tasks to be performed on remote hosts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Overview of Playbook Structure:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kft30h7pqqnw12q7r4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6kft30h7pqqnw12q7r4d.png" alt="Playbook analogy. A pic of a cookbook represents the playbook, Each recipe in the cookbook is a play and the steps in the recipe are tasks. Modules are represented by an image of cooking utensils and ingredients." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Play:&lt;/strong&gt; specifies a group of tasks to be executed on a set of hosts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Task:&lt;/strong&gt; an action to be performed on the host.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Module:&lt;/strong&gt; a piece of code for a specific task.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is Task Control &amp;amp; Handler?
&lt;/h2&gt;

&lt;p&gt;These are methods of controlling how and when your automation scripts are executed. Task control lets you specify conditions for running tasks while handlers are a form of tasks that you trigger only when changes need to be applied.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task Control Overview
&lt;/h3&gt;

&lt;p&gt;Task control in Ansible allows you to state the conditions under which your scripts should run. This can be implemented using conditionals such as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This conditional allows you to execute a task only if it evaluates to true.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ansible Facts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ansible_facts&lt;/code&gt; are variables containing information like network interfaces, operating system, IP addresses, and more, which are gathered from host machines and can be used in conditionals.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task Handlers Overview
&lt;/h3&gt;

&lt;p&gt;Handlers are tasks set to run only when certain changes occur, such as when a service's configuration file is updated by another task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notify&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;notify&lt;/code&gt; directive in a task triggers a handler if the task results in any changes to the host.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Listen&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;listen&lt;/code&gt;  directive allows multiple tasks to notify the same handler even if they use different &lt;code&gt;notify&lt;/code&gt; names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;We'll start with two pre-configured inventory files. Inventory-webserver1.yaml contains two hosts while inventory-webserver2.yaml contains one host.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/perplexedyawdie/ansible-learn.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Spin up the environment using docker-compose&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. SSH into the Ansible server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;NoHostAuthenticationForLocalhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes &lt;/span&gt;root@localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 2200# password: test123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Change directory to &lt;code&gt;ansible_learn&lt;/code&gt;&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="nb"&gt;cd &lt;/span&gt;ansible_learn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Working with Playbooks
&lt;/h3&gt;

&lt;p&gt;5. Create playbook file&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="nb"&gt;touch &lt;/span&gt;install_nginx.yaml

nano install_nginx.yaml

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

&lt;/div&gt;



&lt;p&gt;6. Add the following config to install &lt;strong&gt;nginx&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ansible_learn/install_nginx.yaml&lt;/span&gt;
&lt;span class="c1"&gt;# setting the state param to "present" will ensure that it is installed&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;Install NGINX Web Server&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;webserver1&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;Install NGINX&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt&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;nginx&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;7. Run the linter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-lint install_nginx.yaml

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

&lt;/div&gt;



&lt;p&gt;8. Execute the playbook on &lt;code&gt;inventory-webserver1.yaml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory-webserver1.yaml install_nginx.yaml

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

&lt;/div&gt;



&lt;p&gt;9. Confirm installation on server1 &amp;amp; server2 then check if it's installed on server3&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server1 nginx &lt;span class="nt"&gt;-V&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server2 nginx &lt;span class="nt"&gt;-V&lt;/span&gt;


ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server3 nginx &lt;span class="nt"&gt;-V&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Task Controls &amp;amp; Handlers
&lt;/h3&gt;

&lt;p&gt;We want to create a playbook that installs Apache on servers that do not have a specific file (e.g., &lt;code&gt;/var/www/html/index.nginx-debian.html&lt;/code&gt;), and then use a handler to restart Apache if it is newly installed or updated.&lt;/p&gt;

&lt;p&gt;10. Create &amp;amp; open the &lt;code&gt;install_nginx.yaml&lt;/code&gt; file&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="nb"&gt;touch &lt;/span&gt;conditional_install_apache.yaml

nano conditional_install_apache.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;11. Add condition to install Apache if &lt;code&gt;/var/www/html/index.nginx-debian.html&lt;/code&gt; doesn't exist then add a handler to restart Apache if it is newly installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;Conditional Install and Restart Apache&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;all&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;Check if NGINX Default Page Exists&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.stat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/index.nginx-debian.html&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;nginx_page&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;Install Apache&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.apt&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;apache2&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
      &lt;span class="na"&gt;when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;not nginx_page.stat.exists&lt;/span&gt;
      &lt;span class="na"&gt;notify&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Restart Apache&lt;/span&gt;

  &lt;span class="na"&gt;handlers&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;Restart Apache&lt;/span&gt;
      &lt;span class="na"&gt;listen&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Restart&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Apache"&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.service&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;apache2&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restarted&lt;/span&gt;

&lt;span class="c1"&gt;# The stat module checks for the existence of the default NGINX page. The result is registered in nginx_page.&lt;/span&gt;
&lt;span class="c1"&gt;# The installation task uses the when condition to install Apache only if the file does not exist.&lt;/span&gt;
&lt;span class="c1"&gt;# If the installation task runs, it notifies the handler to restart Apache.&lt;/span&gt;
&lt;span class="c1"&gt;# The handler listens for the notification and uses the service module to restart Apache.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12. Run the linter&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-lint conditional_install_apache.yaml

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

&lt;/div&gt;



&lt;p&gt;13. Execute the playbook on &lt;code&gt;inventory-webserver1.yaml&lt;/code&gt; &amp;amp; &lt;code&gt;inventory-webserver2.yaml&lt;/code&gt;&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="c"&gt;# webserver1&lt;/span&gt;
ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory-webserver1.yaml conditional_install_apache.yaml

&lt;span class="c"&gt;#webserver2&lt;/span&gt;
ansible-playbook &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory-webserver2.yaml conditional_install_apache.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;14. Confirm installation on server3 then check if it's installed on server1 &amp;amp; server2&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server1 apache2 &lt;span class="nt"&gt;-V&lt;/span&gt;
ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server2 apache2 &lt;span class="nt"&gt;-V&lt;/span&gt;


ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server3 apache2 &lt;span class="nt"&gt;-V&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Awesome effort! We just learned how to define Ansible playbooks and how to control the flow of execution using conditionals. Next, we'll look on variables, gathering facts about host machines and how to manage config files using templates.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
      <category>automation</category>
      <category>docker</category>
    </item>
    <item>
      <title>Ansible 101: Inventory Files &amp; Ad-hoc Cmds</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Mon, 22 Jan 2024 09:49:52 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/ansible-101-inventory-files-ad-hoc-cmds-1bli</link>
      <guid>https://dev.to/cloudkungfu/ansible-101-inventory-files-ad-hoc-cmds-1bli</guid>
      <description>&lt;h2&gt;
  
  
  What is Ansible?
&lt;/h2&gt;

&lt;p&gt;This is a task automation tool that uses scripts to run commands on remote or local systems. A core idea is that you input the state you want the system to be in (e.g: have docker installed) then Ansible will ensure that it maintains this state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4gzo34tvachgq2cfbq5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4gzo34tvachgq2cfbq5.jpg" alt="Ansible Architecture" width="601" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main idea is to have the Ansible CLI installed on a machine you have access to, then you'll create a list of servers and IP addresses in what's called an &lt;code&gt;Inventory&lt;/code&gt; file. After which you can use Ansible CLI to run &lt;code&gt;ad-hoc commands&lt;/code&gt; to execute tasks on the servers you listed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inventory Files
&lt;/h2&gt;

&lt;p&gt;This is where you define the list of hosts (this can be a local instance of a machine, remote servers etc) that you want to manage using Ansible. These files can be in &lt;code&gt;.ini&lt;/code&gt; or &lt;code&gt;.yaml&lt;/code&gt; format. Here's an example that defines a group of webservers:&lt;/p&gt;

&lt;h3&gt;
  
  
  inventory.ini
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;webservers]
170.187.144.11
170.187.144.12
170.187.144.13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  inventory.yaml
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;webservers&lt;/span&gt;&lt;span class="pi"&gt;:&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;170.187.144.11&lt;/span&gt;
    &lt;span class="s"&gt;170.187.144.12&lt;/span&gt;
    &lt;span class="s"&gt;170.187.144.13&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Both formats accomplish the same thing, however &lt;code&gt;.yaml&lt;/code&gt; is commonly used in many config files and allows you to give each host a unique name so I'd recommend using that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ad-hoc Commands
&lt;/h2&gt;

&lt;p&gt;To automate a task, you can use scripts that contain specific commands or you can run commands directly in the CLI. The latter is referred to as an &lt;code&gt;ad-hoc command&lt;/code&gt; and allows you to execute simple one-time commands. Using the &lt;code&gt;inventory.ini&lt;/code&gt; file we created above, we can use &lt;code&gt;ad-hoc commands&lt;/code&gt; to do the following:&lt;/p&gt;

&lt;h3&gt;
  
  
  Ping
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible webservers &lt;span class="nt"&gt;-m&lt;/span&gt; ping &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml
&lt;span class="c"&gt;# -m Tells Ansible which module to use, in this case, ping&lt;/span&gt;
&lt;span class="c"&gt;# --key-file Specifies the SSH private key file to use for connections to the remote hosts.&lt;/span&gt;
&lt;span class="c"&gt;# -u Indicates that Ansible should connect as the `root` user.&lt;/span&gt;
&lt;span class="c"&gt;# -i Specifies the inventory file to use.&lt;/span&gt;


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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Copy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible webservers &lt;span class="nt"&gt;-m&lt;/span&gt; copy &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"src=test.txt dest=/root/"&lt;/span&gt; &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml
&lt;span class="c"&gt;# -a Specifies the arguments to pass to the module. This module copies files from the local machine on which Ansible is running to the remote hosts specified in the inventory.yaml file.&lt;/span&gt;


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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;I've created a docker compose file that will provision an environment that will allow you to practice what we learned above.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Clone the repo
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/perplexedyawdie/ansible-learn.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Spin up the environment using docker-compose&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. SSH into the Ansible server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;NoHostAuthenticationForLocalhost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes &lt;/span&gt;root@localhost &lt;span class="nt"&gt;-p&lt;/span&gt; 2200# password: test123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. Change directory to &lt;code&gt;ansible_learn&lt;/code&gt;&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="nb"&gt;cd &lt;/span&gt;ansible_learn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;5. Create an &lt;code&gt;inventory.yaml&lt;/code&gt; file and populate with hosts&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="nb"&gt;touch &lt;/span&gt;inventory.yaml

nano inventory.yaml

&lt;span class="c"&gt;# Paste the following in your inventory.yaml file.&lt;/span&gt;
&lt;span class="c"&gt;# webservers:&lt;/span&gt;
&lt;span class="c"&gt;#  hosts:&lt;/span&gt;
&lt;span class="c"&gt;#    server1:&lt;/span&gt;
&lt;span class="c"&gt;#    server2:&lt;/span&gt;
&lt;span class="c"&gt;#    server3:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;6. Ping the hosts to ensure that you can connect to them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible webservers &lt;span class="nt"&gt;-m&lt;/span&gt; ping &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml

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

&lt;/div&gt;



&lt;p&gt;7. Copy a file to each host&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello Mate!!"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; test.txt

ansible webservers &lt;span class="nt"&gt;-m&lt;/span&gt; copy &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"src=test.txt dest=/root/"&lt;/span&gt; &lt;span class="nt"&gt;--key-file&lt;/span&gt; /root/.ssh/id_rsa_ansible &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-i&lt;/span&gt; inventory.yaml

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

&lt;/div&gt;



&lt;p&gt;8. SSH into a host to see your copied file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;StrictHostKeyChecking&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no &lt;span class="nt"&gt;-i&lt;/span&gt; /root/.ssh/id_rsa_ansible root@server1

&lt;span class="nb"&gt;ls

cat &lt;/span&gt;test.txt

&lt;span class="nb"&gt;exit

exit&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;9. Stop and remove containers to clean up&lt;br&gt;
&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;You just learned the basics of Ansible automation! Specifically, how to define the hosts you want to remotely manage and how to execute commands on them. In the next article, we'll look on Playbooks along with task control mechanisms and handlers.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>ClipCraft: Image Background Remover</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Wed, 24 May 2023 01:30:58 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/image-background-remover-39im</link>
      <guid>https://dev.to/cloudkungfu/image-background-remover-39im</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;For this hackathon, I built a lightweight image background remover that can be installed as a PWA. It comes as part of a suite of media editing mini apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;Phone Friendly: Projects built for Mobile (PWA readiness, iOS/Android)&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://clipcraft.studio"&gt;ClipCraft Studio&lt;/a&gt;&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://clipcraft.studio/" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--yaAeKLY5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://objectstorage.ca-toronto-1.oraclecloud.com/p/ojOKJ0J1wcI5Rw7ioFY5nwIZialA43XcKAMhwoLij9Kgp7oPbV5sTPw3JTdx2-ts/n/yzpjtx1indjl/b/clip-craft-studio-assets/o/icon-512x512.png" height="512" class="m-0" width="512"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://clipcraft.studio/" rel="noopener noreferrer" class="c-link"&gt;
          ClipCraft - Clip, Create, Share
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Clip out interesting bits of your photo and remove the background
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--c7aXxhtB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://clipcraft.studio/favicon.ico" width="256" height="256"&gt;
        clipcraft.studio
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8649gxufotp7giisgh8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8649gxufotp7giisgh8d.png" alt="App Store" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1phzmyuvi0utjjmfcfgb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1phzmyuvi0utjjmfcfgb.png" alt="Clipper Mini App" width="800" height="712"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi85c0l8gzuezd8qw40dp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi85c0l8gzuezd8qw40dp.png" alt="Image upload" width="800" height="833"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwl97bq7dw3bvda7zehq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flwl97bq7dw3bvda7zehq.png" alt="Clipped Image" width="800" height="824"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/aKuFXw8ol14"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;This app is a handy tool that focuses on simplifying various aspects of media editing. With its U2net-powered background removal feature, users can remove backgrounds from their images, making them more versatile and visually appealing. The app's backend is built using Python Flask, ensuring a smooth and reliable user experience.&lt;/p&gt;

&lt;p&gt;In addition to the background removal feature, the app also offers multiple mini apps to enhance your media editing capabilities. Soon, you can expect the addition of a photo filter mini app, which will allow you to add creative filters to your images. Furthermore, the app will introduce a photo dump video creator, enabling you to compile your photos into engaging videos. Another exciting feature in the pipeline is the Podclip mini app, which utilizes the OpenAI Whisper model to generate transcriptions and create short videos from podcast audio clips. Lastly, the app will include a convenient Clipshare mini app that simplifies media sharing across devices through the use of QR codes. &lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/perplexedyawdie/clipcraft-u2net-flask"&gt;Backend&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/perplexedyawdie/clip-craft"&gt;Frontend&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;MIT&lt;/p&gt;

&lt;h2&gt;
  
  
  Background (What made you decide to build this particular app? What inspired you?)
&lt;/h2&gt;

&lt;p&gt;It all started when I began researching and playing around with AI tools. As I delved deeper into the world of artificial intelligence, I discovered the incredible potential and versatility of different types of models and their various use cases. One area that particularly caught my attention was media editing. I stumbled upon some fascinating models that could perform tasks like background removal seamlessly. The possibilities seemed endless, and I was inspired to build an app that could harness this power and make media editing more accessible and user-friendly. That's how the idea took shape.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it (How did you utilize GitHub Actions or GitHub Codespaces? Did you learn something new along the way? Pick up a new skill?)
&lt;/h3&gt;

&lt;p&gt;When it came to utilizing GitHub Actions and GitHub Codespaces for my project, I found them to be invaluable tools in streamlining the development and deployment process. With GitHub Actions, I was able to create a multi-architecture container build effortlessly, ensuring compatibility across different environments. Moreover, GitHub Actions also facilitated pushing the container to GitHub Container Registry (ghcr) smoothly, making it readily accessible for deployment. Speaking of deployment, I relied on GitHub Actions once again to deploy the container to my server seamlessly. These powerful features provided an efficient and automated workflow, saving me time and effort.&lt;/p&gt;

&lt;p&gt;Throughout this journey, I learned a great deal. One notable aspect was working with the U2NET model for salient object detection, specifically for background removal. It was fascinating to explore the capabilities of this model and see how well it performs. Additionally, I picked up a new skill in utilizing Onnxruntime, which allowed me to easily deploy the U2NET model in different environments. However, it wasn't without its challenges. I encountered numerous obstacles when deploying the model, and the experience taught me a valuable lesson about the intricacies and potential pitfalls involved in deploying AI models. Nevertheless, this entire process has been incredibly enriching, from exporting models to ONNX format to setting up Onnxruntime in Python, and it has deepened my understanding of deploying models effectively. Ultimately, my aim remains to move everything client-side, and with the skills and knowledge gained, I'm optimistic about achieving that goal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://onnxruntime.ai/"&gt;Onnxruntime&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/xuebinqin/U-2-Net"&gt;U2-net&lt;/a&gt;&lt;/p&gt;

</description>
      <category>githubhack23</category>
      <category>python</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Linode + DEV Hackathon WavScribe</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Mon, 20 Feb 2023 23:35:45 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/linode-dev-hackathon-wavscribe-4no9</link>
      <guid>https://dev.to/cloudkungfu/linode-dev-hackathon-wavscribe-4no9</guid>
      <description>&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;I built an app that makes it easy to transcribe audio files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Category Submission:
&lt;/h3&gt;

&lt;p&gt;SaaS Superstars: Awarded to the team whose app shows the greatest potential for success as a Software-as-a-Service (SaaS) product or money-making venture.&lt;/p&gt;

&lt;p&gt;Integration Innovators: Awarded to the team whose app shows the best integration with other services offered by Linode, such as managed databases or object storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  App Link
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://wavscribe.xyz"&gt;WavScribe&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20xm68hohfj9t5u356fn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F20xm68hohfj9t5u356fn.png" alt="Image of the landing page" width="800" height="382"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forx2kaigd00vhx5dzs80.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Forx2kaigd00vhx5dzs80.png" alt="Upload complete" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkeg7ywos5131ax6a4gdt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkeg7ywos5131ax6a4gdt.png" alt="Transcription" width="800" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Description
&lt;/h3&gt;

&lt;p&gt;WavScribe is a web app that aims to make audio transcription accessible to everybody! It's free for now for 1 minute of transcription. You just upload your audio file then wait a short while til your transcription job is completed then voila! 🗣📝&lt;br&gt;
Sound to words like magic!&lt;/p&gt;

&lt;p&gt;After upload, you get the file id (copy and keep this!!!).&lt;br&gt;
Next step is to click the status link in the nav bar.&lt;br&gt;
You'll be taken to a page where you can paste the file id and poll until your transcription appears 😅&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Source Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/perplexedyawdie/wav-scribe"&gt;WavScribe Web App&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/perplexedyawdie/wavscribe-speech2text"&gt;WavScribe Python Speech2Text&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Permissive License
&lt;/h3&gt;

&lt;p&gt;MIT&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;A few friends of mine started podcasts and wanted a way to generate transcripts easily. It so happened that I was playing around with AI libs and came upon openai/whisper 😁🤫&lt;br&gt;
I loved the fact that it was performant even on a CPU only machine so I spun up this web app to make it easy for them to get their transcriptions!&lt;/p&gt;

&lt;h3&gt;
  
  
  How I built it
&lt;/h3&gt;

&lt;p&gt;I used Linode Object storage to keep the audio files and generate presigned urls. I also used Linode Managed MYSQL to store the transcriptions and provide them to users!&lt;br&gt;
Finally, I used Linode Domains to handle the DNS records for my domain. Instead of going back and forth between their and PorkBun, I just did everything in one place 😃 How cool is that??&lt;/p&gt;

&lt;p&gt;For the frontend I used Nextjs with Tailwind 💕&lt;br&gt;
For the Speech to Text aspect I used openai/whisper through HuggingFace transformer pipelines. It made it less stressful to work with and get a demo up and running 🤗&lt;br&gt;
I learnt a lot of sys admin stuff on the journey, For example: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to setup and configure NGINX with certbot&lt;/li&gt;
&lt;li&gt;Using a process manager (PM2)&lt;/li&gt;
&lt;li&gt;Configuring firewalls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the backend side, I learnt how to use RabbitMQ! I've been hearing alot about Kafka and other queue systems so it was cool to get the chance to play around with it.&lt;br&gt;
I used it to handle the influx of requests and do them sequentially.&lt;br&gt;
Finally, I learnt a bit more about Docker and passing arguments. 🚢&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources/Info
&lt;/h3&gt;

&lt;p&gt;I want to add a few more features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User registration so you can see your past transcriptions&lt;/li&gt;
&lt;li&gt;The ability to play the audio and see the timestamped transcription&lt;/li&gt;
&lt;li&gt;The ability to generate a shareable link to that transcription with timestamps&lt;/li&gt;
&lt;li&gt;Payment plans to allow for longer transcriptions!&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linodehackathon</category>
    </item>
    <item>
      <title>Generate a resignation letter in a few steps 😀</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Fri, 14 Jan 2022 07:35:12 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/mongodb-submission-post-placeholder-title-5f1h</link>
      <guid>https://dev.to/cloudkungfu/mongodb-submission-post-placeholder-title-5f1h</guid>
      <description>&lt;h3&gt;
  
  
  Overview of My Submission
&lt;/h3&gt;

&lt;p&gt;I've been seeing lotsa talk about the great resignation so I created a Resignation Letter generator 😁&lt;br&gt;
You pretty much just enter a few details and the letter is generated right in front your eyes. There are over 15 templates available and you can generate a shareable link as well!&lt;br&gt;
I tried out the &lt;a href="https://docs.atlas.mongodb.com/api/data-api/"&gt;MongoDB Data API&lt;/a&gt;. It made it pretty uncomplicated to insert and retrieve documents via API calls. All that's needed is the API key (of course). They provide excellent documentation and code samples for a few languages (I used Node.js). I'll definitely be using it going forward for the times when I might not want to install the NPM package.&lt;/p&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;Choose-Your-Own-Adventure&lt;/p&gt;

&lt;h3&gt;
  
  
  Link to Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/perplexedyawdie/resignator"&gt;Github Link&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;I used the following tools when building the web app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://theme-ui.com/"&gt;Theme-UI, it made setting up the layout super straightforward 💯&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Link to Resignator
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.resignator.xyz/"&gt;Resignator&lt;/a&gt;&lt;br&gt;
&lt;a href="https://resignator.vercel.app/"&gt;Resignator - Vercel&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo Video
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://youtu.be/PCMzfPSGbF8"&gt;Demo&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lpkmq71z07cbqrafuhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lpkmq71z07cbqrafuhg.png" alt="Landing Page" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkocydqo0q6b551a9dh5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqkocydqo0q6b551a9dh5.png" alt="Letter Generator section" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>atlashackathon</category>
      <category>nextjs</category>
      <category>mongodb</category>
    </item>
    <item>
      <title>Resize a cross domain iFrame (the hackiest way)</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Sun, 24 Oct 2021 03:35:30 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/resize-a-cross-domain-iframe-the-hackiest-way-1j57</link>
      <guid>https://dev.to/cloudkungfu/resize-a-cross-domain-iframe-the-hackiest-way-1j57</guid>
      <description>&lt;h3&gt;
  
  
  Situation 🤔
&lt;/h3&gt;

&lt;p&gt;Some time ago, I was implementing a cross domain iFrame in Next.js and ran into a slight problem when it came to dynamically setting its height. Due to limitations imposed by the browser, we're prevented from using JS magic to access the page and getting the actual height of the document. A library, &lt;a href="http://davidjbradshaw.github.io/iframe-resizer/"&gt;iframe-resizer&lt;/a&gt; promised to solve the issue however it required a small script to be placed on the page hosting the iFrame. Unfortunately that wasn't an option 🚫&lt;/p&gt;

&lt;h3&gt;
  
  
  Task 📃
&lt;/h3&gt;

&lt;p&gt;Fortunately, at two redbulls deep, &lt;a href="https://www.theoi.com/Daimon/Poros.html"&gt;Poros&lt;/a&gt; had mercy and an ✨&lt;em&gt;idea&lt;/em&gt;✨ came outta no where. What I wanted to accomplish was, on page load, assign a height to the iFrame that will display all it's contents without unneeded whitespace. Why not do it in the most straight forward way possible? Remotely load the URL that will get displayed in the iFrame, get the height of the loaded contents then apply that height to the iFrame. &lt;/p&gt;

&lt;h3&gt;
  
  
  Lights, Camera... &lt;strong&gt;Action!&lt;/strong&gt; 🎬
&lt;/h3&gt;

&lt;p&gt;One way to do this was through browser automation. There's &lt;a href="https://www.selenium.dev/"&gt;Selenium&lt;/a&gt; but I went with &lt;a href="https://developers.google.com/web/tools/puppeteer"&gt;Puppeteer&lt;/a&gt;. I made an API endpoint that when hit, uses Puppeteer to load the site then get &amp;amp; return the height using &lt;a href="https://stackoverflow.com/questions/1145850/how-to-get-height-of-entire-document-with-javascript"&gt;this method I saw on stackoverflow&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Results 💯
&lt;/h3&gt;

&lt;p&gt;The result was a page that takes a little longer to load but has an iFrame that correctly shows all the contents! Check out the code sample below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// npm i puppeteer
const puppeteer = require('puppeteer')

async function getContentHeight({ clientWidth, clientHeight }) {
    const browser = await puppeteer.launch({ headless: true });
    const page = await browser.newPage();
    await page.setViewport({ width: clientWidth, height: clientHeight })
    await page.goto('my-url-was-here-b4-yours');
    const contentHeight = await page.evaluate(() =&amp;gt; {
        let body = document.body,
            html = document.documentElement;

        let height = Math.max(body.scrollHeight, body.offsetHeight,
            html.clientHeight, html.scrollHeight, html.offsetHeight);
        return height
    });
    console.log(contentHeight);

    await browser.close();
}

getContentHeight({ clientWidth: 1366, clientHeight: 768 })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Disclaimer
&lt;/h3&gt;

&lt;p&gt;Pleaaase don't use this in prod! But, if you do.. let me know how it goes 😂 &lt;/p&gt;

&lt;h3&gt;
  
  
  Attribution
&lt;/h3&gt;

&lt;p&gt;Cover image: &lt;a href="https://www.instagram.com/andyparkart/"&gt;Andy Park Art&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>TAWIL: How to use Typescript ️</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Sat, 06 Feb 2021 02:24:20 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/tawil-how-to-use-typescript-3ged</link>
      <guid>https://dev.to/cloudkungfu/tawil-how-to-use-typescript-3ged</guid>
      <description>&lt;p&gt;Today at work I learnt how to do the above in preparation for a project where I want to use &lt;a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/"&gt;type-driven design&lt;/a&gt;. Let's fire up &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt; and take the four steps needed to layer a type-system onto a JS project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 - Ingredients: Sugar, spice and everything niceee
&lt;/h3&gt;

&lt;p&gt;In addition to &lt;a href="https://nodejs.org/en/"&gt;Node.js&lt;/a&gt; and &lt;a href="https://code.visualstudio.com/"&gt;VS Code&lt;/a&gt;, we just need to sprinkle &lt;code&gt;npm init&lt;/code&gt; onto our command line. After initializing our environment, we add a dash of &lt;code&gt;npm install typescript&lt;/code&gt; then finish up with a side of &lt;code&gt;npx tsc -v&lt;/code&gt; for good measure. After the smoke clears, you should see something like this appear:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Version 4.1.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may not be those exact version but it means we got typescript onto our system successfully.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2 - Where do we put all this stuff?
&lt;/h3&gt;

&lt;p&gt;We're going to end up summoning a regular degular javascript file with the concoction we created above so let's setup a space for it to appear!&lt;br&gt;
In your command line, type &lt;code&gt;npx tsc --init&lt;/code&gt;. A file titled &lt;strong&gt;tsconfig.json&lt;/strong&gt; should appear. At line &lt;strong&gt;17&lt;/strong&gt; in the file, remove comments from &lt;strong&gt;outDir&lt;/strong&gt; then add &lt;strong&gt;"./test"&lt;/strong&gt; as the key value.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3 - It's a bird, it's a plane..... it's TYPESCRIPT!
&lt;/h3&gt;

&lt;p&gt;Now, let's exercise the strength in our typing fingers and bring some typescript into existence! Create a file titled &lt;strong&gt;test.ts&lt;/strong&gt; then add this bit of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let message:string = "Hello world!";
console.log(message);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;message:string&lt;/code&gt; we are setting the type of the variable &lt;strong&gt;message&lt;/strong&gt; to string. From now til the end of time it will only accept strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Compile!
&lt;/h3&gt;

&lt;p&gt;The final step is to compile the file. This will output a regular javascript file that we can then use like usual. In your terminal, run &lt;code&gt;npx tsc&lt;/code&gt;. A new file titled &lt;strong&gt;test.js&lt;/strong&gt; should appear in the &lt;strong&gt;test&lt;/strong&gt; folder we had created earlier.&lt;/p&gt;

&lt;p&gt;Just like that, you've used typescript! Here's some extra enough to get you up and running:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html"&gt;Typescript for JS Programmers&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>node</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>TAWIL: How to write tests with Jest 🃏</title>
      <dc:creator>Javel Rowe</dc:creator>
      <pubDate>Thu, 04 Feb 2021 00:14:00 +0000</pubDate>
      <link>https://dev.to/cloudkungfu/tawil-how-to-write-tests-with-jest-2phm</link>
      <guid>https://dev.to/cloudkungfu/tawil-how-to-write-tests-with-jest-2phm</guid>
      <description>&lt;p&gt;Hello world. Today at work I learnt how to do the above. It's never too late to begin increasing your confidence in your code. In four steps, we'll have a small testing suite up and running. &lt;strong&gt;Let's start!&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 - Dependencies
&lt;/h3&gt;

&lt;p&gt;These 3 steps should be similar for all editors from VS Code to Notepad.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check that you have Node.js installed by running
&lt;code&gt;node -v&lt;/code&gt; in your terminal. You should see something like this:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v14.9.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;CD to a folder of your choice (even Desktop, I won't judge), then run &lt;code&gt;npm init&lt;/code&gt; then &lt;code&gt;npm i jest --save-dev&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Finally, in the package.json file that appeared out of nowhere, add &lt;code&gt;jest --verbose&lt;/code&gt; under scripts in the key titled 'test' like so:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjy1w6c282plju4lp4glk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fjy1w6c282plju4lp4glk.gif" alt="edit-pkg_json" width="713" height="493"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2 - Functions to test
&lt;/h3&gt;

&lt;p&gt;Let us create a file titled &lt;strong&gt;countr.js&lt;/strong&gt;. In it, we shall write a set of functions for a counter app. The functions will accept a number as an argument then increase or decrease its value by 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const increase = (a) =&amp;gt; ++a;
const decrease = (b) =&amp;gt; --b;

module.exports = { increase, decrease };
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3 - A test for our functions
&lt;/h3&gt;

&lt;p&gt;Now we shall write some tests! In the same directory, create a file titled &lt;strong&gt;countr.test.js&lt;/strong&gt;. In here, add the following bit of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { increase, decrease } = require('./countr');

test('Increaase yah noww', () =&amp;gt; {
  expect(increase(3)).toBe(4);
});

test('Beg yuh one nuh', () =&amp;gt; {
  expect(decrease(4)).toBe(3);
});

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

&lt;/div&gt;



&lt;h5&gt;
  
  
  Teching Things Apart
&lt;/h5&gt;

&lt;p&gt;This &lt;code&gt;expect(increase(2)).toBe(3)&lt;/code&gt; bit of code does the actual work for us. The &lt;strong&gt;expect()&lt;/strong&gt; function accepts as an input the value that our code ( increase(3) ) outputs. The &lt;strong&gt;toBe()&lt;/strong&gt; &lt;em&gt;matcher&lt;/em&gt; function accepts the value we hope our code will output.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4 - Testing 123..
&lt;/h3&gt;

&lt;p&gt;Finally, in your terminal, run the command &lt;code&gt;npm test&lt;/code&gt; anddd 🥁 🥁 🥁 🥁 &lt;br&gt;
You should see these beautiful messages appear before your very eyes:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu5og77j6rgfhycw1p7ht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu5og77j6rgfhycw1p7ht.png" alt="Screenshot 2021-02-03 at 7.03.37 PM" width="558" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! Welcome to the club!&lt;/p&gt;

&lt;h4&gt;
  
  
  Attributions
&lt;/h4&gt;

&lt;p&gt;Header - &lt;a href="https://www.freepik.com/vectors/design"&gt;Design vector created by macrovector - &lt;/a&gt;&lt;a href="http://www.freepik.com"&gt;www.freepik.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>node</category>
      <category>javascript</category>
      <category>jest</category>
    </item>
  </channel>
</rss>
