<?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: Henk van den Brink</title>
    <description>The latest articles on DEV Community by Henk van den Brink (@hvdb).</description>
    <link>https://dev.to/hvdb</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%2F156035%2F66690a04-30b1-4a5a-a255-4afe0169352e.jpeg</url>
      <title>DEV Community: Henk van den Brink</title>
      <link>https://dev.to/hvdb</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hvdb"/>
    <language>en</language>
    <item>
      <title>The circle of life: Embracing technical debt</title>
      <dc:creator>Henk van den Brink</dc:creator>
      <pubDate>Tue, 08 Apr 2025 14:22:59 +0000</pubDate>
      <link>https://dev.to/hvdb/embracing-technical-debt-the-circle-of-life-20a1</link>
      <guid>https://dev.to/hvdb/embracing-technical-debt-the-circle-of-life-20a1</guid>
      <description>&lt;h2&gt;
  
  
  Embracing technical debt: A journey through life
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;It is good to have technical debt, when managed wisely.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consider how our lives evolve, accumulating “debt” much like the mess we allow to pile up in our homes. At every stage, we make choices that balance the need for maintenance and support against our available resources.&lt;/p&gt;




&lt;h2&gt;
  
  
  Life’s phases: a living analogy
&lt;/h2&gt;

&lt;p&gt;Imagine your life as a series of evolving chapters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Student days&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  When you’re living in a dorm or a shared student flat, keeping your space immaculate isn’t a top priority. The “mess” or technical debt is allowed to accumulate because there’s minimal pressure to change.   &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Young adulthood &amp;amp; Relationships&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  Entering a relationship changes everything. Suddenly, clearing the clutter becomes a sign of care and commitment. You start to pay attention to the little details because someone else is now part of your life.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Family &amp;amp; Parenthood&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  As kids arrive, maintaining a pristine home becomes a luxury. With so many pressing priorities, the technical debt builds up even faster. Every spare moment isn’t dedicated to cleaning, but that’s okay; it’s part of life.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rebalancing in later years&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  As your children grow and you reclaim some time, you begin cleaning up again. However, your priorities now include other passions and pursuits. Not every room needs daily attention, just enough to keep things in balance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retirement&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  In your later years, tackling household chores becomes more challenging. At this point, support systems like professional cleaners or caregivers come into play. The cost of maintenance increases, and you lean on external help to keep the balance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting the right support
&lt;/h2&gt;

&lt;p&gt;At each phase, you choose your level of help based on three key factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Necessity:&lt;/strong&gt; How critical is the task?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget:&lt;/strong&gt; What can you afford?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lifestyle:&lt;/strong&gt; What suits your way of being?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From parental interventions during your student years to hiring full-time support as you age, you’re always balancing effort (and costs) with the quality of life.&lt;/p&gt;




&lt;h2&gt;
  
  
  Striking a balance
&lt;/h2&gt;

&lt;p&gt;Life is a series of decisions, whether you’re weighing the cost of a new gadget or deciding whether to move houses. These choices are part of every aspect of your existence. Some people opt to outsource many tasks by hiring help, while others shoulder responsibilities on their own, sometimes at the cost of increased stress. Ultimately, balance is the secret ingredient to maintaining both your life and your technical debt.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parallels in software development
&lt;/h2&gt;

&lt;p&gt;The lifecycle of a software product mirrors our lives chapters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product launch&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  Like starting out as a student, a new product begins with minimal structure, technical debt might be low, or even accumulating unnoticed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Growth phase&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  As new features are added and demand increases, the balance between innovation and maintenance becomes critical. Decisions about addressing technical debt come into play much like cleaning up your living space when entering a new personal phase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maturity &amp;amp; Maintenance&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  Just as family life brings its own challenges, a mature product faces high technical debt. You must decide whether to invest time in refactoring or risk losing market ground.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;End-of-Life&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
  Similar to retirement, maintaining old software may require substantial external support; yet, investing too much might not be feasible when the product’s time has passed.&lt;/p&gt;

&lt;p&gt;In both life and software, technical debt isn’t inherently bad, it’s all about making the right choices at the right time.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              +-------------------------------------+
              |        The Circle of Life           |
              +-------------------------------------+
                         /               \
         +-------------------------+   +-------------------------+
         |    Personal Life        |   |  Software Development   |
         | (Managing Technical Debt)|   | (Handling Technical Debt)|
         +-------------------------+   +-------------------------+
         |                         |   |                         |
         |  [Student Days]         |   | [Product Launch]        |
         |  (Minimal debt)         |   | (Initial, manageable)   |
         |                         |   |                         |
         |         ↓               |   |         ↓               |
         |  [Young Adulthood]      |   | [Growth Phase]          |
         |  (Cleaning up for love) |   | (Debt begins to rise)   |
         |                         |   |                         |
         |         ↓               |   |         ↓               |
         |  [Family &amp;amp; Parenthood]  |   | [Maturity]              |
         |  (Debt accumulates)     |   | (High technical debt)   |
         |                         |   |                         |
         |         ↓               |   |         ↓               |
         |  [Retirement]           |   | [End of Life]           |
         | (Reliance on support)   |   | (Balancing maintenance) |
         +-------------------------+   +-------------------------+
                         \               /
                           +-----------+
                           |  Balance  |
                           | is the key|
                           +-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Yes, it is good to have technical debt, but only to a certain degree. Just as every stage in your life requires you to weigh costs vs. benefits, so does every phase of a software project. Finding and maintaining the right balance is unique to every individual and every project.&lt;/p&gt;

&lt;p&gt;In both life and code, embracing a little debt can pave the way for growth. Yet, always remain mindful that unaddressed debt can accumulate and ultimately affect your overall stability.&lt;/p&gt;

&lt;p&gt;Risks vs Rewards.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Additional food for thought:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Perhaps you might also explore how agile methodologies in software are shaped by these balancing decisions, or reflect on personal experiences where strategic ignorance paved the way for (unexpected) innovation. The interplay between calculated risk and measured maintenance is what creates resilience, both for our homes and in our code.&lt;/p&gt;

&lt;p&gt;What are your thoughts on embracing debt (technical or otherwise) as a tool for progress?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Backup MotionEye to OneDrive</title>
      <dc:creator>Henk van den Brink</dc:creator>
      <pubDate>Thu, 03 Aug 2023 06:02:44 +0000</pubDate>
      <link>https://dev.to/hvdb/backup-motioneye-to-onedrive-f7e</link>
      <guid>https://dev.to/hvdb/backup-motioneye-to-onedrive-f7e</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;MotionEye doesn't have an integration for OneDrive, so you can't backup files as easy as when using Google Drive.&lt;/p&gt;

&lt;p&gt;But since I have 1TB of storage on OneDrive, I do want to use it :-)&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;So the solution is some scripting and &lt;a href="https://rclone.org/onedrive/"&gt;rclone&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a few steps that needs to be done.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setup new remote in rclone&lt;/li&gt;
&lt;li&gt;Create script that does the syncing&lt;/li&gt;
&lt;li&gt;Create log directory&lt;/li&gt;
&lt;li&gt;Update MotionEye to use the script&lt;/li&gt;
&lt;li&gt;Done.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. Setup rclone
&lt;/h3&gt;

&lt;p&gt;Either login as the user that is used by MotionEye (in my case &lt;code&gt;motion&lt;/code&gt;) &lt;br&gt;
Or execute it as &lt;code&gt;root&lt;/code&gt; and later copy the rclone conf (and chown to right user)&lt;/p&gt;

&lt;p&gt;Please follow the instruction from &lt;a href="https://rclone.org/onedrive/"&gt;here&lt;/a&gt; to setup RClone OneDrive remote.&lt;br&gt;
It is fairly easy, if your server does not have a browser you do need a second machine that does have a browser to finish the setup.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Create script for sync
&lt;/h3&gt;

&lt;p&gt;Go to &lt;code&gt;/etc/libmotioneye/&lt;/code&gt;&lt;br&gt;
create a new file: &lt;code&gt;backupToOneDrive.sh&lt;/code&gt; &lt;br&gt;
ie &lt;code&gt;nano backupToOneDrive.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!/bin/bash
requiredUser="motion" #Edit to suit username of the RCLone config
remoteFolderName="onedrive:backups/motioneye" #Edit to suit your OneDrive remote folder
remoteFolder=$1
if [ $remoteFolder = "Achtertuin" ]; then #Edit to suit your camera name in MotionEye
localFolder="Camera1" #Edit to suit you local camera folder
fi
if [ $remoteFolder = "Bijkeuken" ]; then #Edit to suit your camera name in MotionEye
localFolder="Camera2" #Edit to suit you local camera folder
fi

#Add more cameras here as required#
if [ $remoteFolder = "" ]; then
echo "ERROR, ROOT Folder" $rootFolder "NOT FOUND" &amp;gt; /var/log/RClone/error.txt
exit
else
rm -f /var/log/RClone/error.txt
fi
DIR="/etc/motioneye" # get cur dir of this script
progName=$(basename -- "$0")
cd $DIR
if pidof -o %PPID -x "$progName"; then
echo "WARNING - $progName Already Running. Only One Allowed." &amp;gt;&amp;gt; /var/log/RClone/upload.txt
echo "WARNING - $progName Already Running. Only One Allowed."
else

localCamFolder="/var/lib/motioneye/"$localFolder
remoteCamFolderName=$remoteFolderName"/"$remoteFolder
#echo $localCamFolder "to" $remoteCamFolderName
if [ ! -d "$localCamFolder" ] ; then # Check if Local sync Folder Exists
echo $localCamFolder "Folder NOT Found " $num
exit 1
fi
nowDay=$(date +"%d")
nowSuffix="th"
if [ $nowDay -eq 1 ]; then
nowSuffix=$nowDay"st"
fi
if [ $nowDay -eq 21 ]; then
nowSuffix=$nowDay"st"
fi
if [ $nowDay -eq 21 ]; then
nowSuffix=$nowDay"st"
fi
nowDate=$(date +"%A,%d$nowSuffix %B %Y")
logDate=$(date +"%d-%m-%Y")
nowTime1=$(date +"%T")
#echo "Upload Started on" $nowDate "at" $nowTime1
#echo $num "- Sending" $localCamFolder "to" $remoteCamFolderName
sudo -u $requiredUser rclone move -v --log-file /var/log/RClone/RCloneLog.txt $localCamFolder $remoteCamFolderName
nowTime2=$(date +"%T")
#echo "Upload Started on" $nowDate "at" $nowTime1 "- FINISHED AT" $nowTime2
echo $logDate "Started" $nowTime1 "Finished" $nowTime2 &amp;gt;&amp;gt; /var/log/RClone/uploads.txt
echo $localCamFolder "to" $remoteCamFolderName &amp;gt;&amp;gt; /var/log/RClone/uploads.txt
fi

echo "Finished uploading files"
exit 0

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;note: This base of the script comes from this &lt;a href="https://github.com/motioneye-project/motioneye/issues/1382"&gt;ticket&lt;/a&gt; at GitHub. So big thanks to him for creating it.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The script is altered a bit, since the original didn't work for me on Debian at least.&lt;br&gt;
And it would not sync 'beyond the day'. &lt;/p&gt;

&lt;p&gt;Let's walk through the script a bit.&lt;/p&gt;

&lt;p&gt;In essence what it does is, it moves all the movies recorded by MotionEye from local to remote folder. &lt;br&gt;
In order to do that, there are some settings that needs to be set. &lt;/p&gt;

&lt;p&gt;There are a few important settings and options that needs to be set.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requiredUser&lt;/li&gt;
&lt;li&gt;remoteFolderName&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;requiredUser&lt;/strong&gt;&lt;br&gt;
This is the user that will execute the rclone command.&lt;br&gt;
So this must be the user that is used to start the motioneye process.&lt;br&gt;
&lt;em&gt;note: it needs to have sudo right, more on that later&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;remoteFolderName&lt;/strong&gt;&lt;br&gt;
You can define the folder name that must be used as root in OneDrive.&lt;/p&gt;

&lt;p&gt;And the camera section:&lt;br&gt;
MotionEye saves the movies in a folder called &lt;code&gt;Camera*ID*&lt;/code&gt;&lt;br&gt;
If you upload that, it can be hard to know which Camera is &lt;code&gt;Camera1&lt;/code&gt;&lt;br&gt;
So there is some transformation done to map the ID directory to a nice name.&lt;/p&gt;

&lt;p&gt;Currently there are two, but you can remove or add a new one.&lt;br&gt;
Just copy the &lt;code&gt;if&lt;/code&gt; until &lt;code&gt;fi&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if [ $remoteFolder = "Garden" ]; then #Edit to suit your camera name in MotionEye
localFolder="Camera1" #Edit to suit you local camera folder
fi
if [ $remoteFolder = "Kitchen" ]; then #Edit to suit your camera name in MotionEye
localFolder="Camera2" #Edit to suit you local camera folder
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is it. Script is ready, let's move on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I am running MotionEye inside a LXC container in Proxmox and want to limit the storage, so I &lt;code&gt;move&lt;/code&gt; the files to OneDrive to free up space.&lt;br&gt;
If you don't want to &lt;code&gt;move&lt;/code&gt;, replace it with &lt;code&gt;copy&lt;/code&gt; in the rclone command.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Create log dir
&lt;/h3&gt;

&lt;p&gt;Now create the log directory for rclone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir `/var/log/RClone`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Make sure that the execution user has rights, either login as that user or chown the file.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Update MotionEye to use the script.
&lt;/h3&gt;

&lt;p&gt;Go to your MotionEye instance.&lt;br&gt;&lt;br&gt;
Open the settings of a Camera.&lt;br&gt;
Go to the &lt;code&gt;File Storage&lt;/code&gt; tab&lt;br&gt;
Click &lt;code&gt;Run a command&lt;/code&gt;&lt;br&gt;
And use your newly created script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sh /etc/motioneye/backupToOneDrive.sh Garden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last part of the command is the name that maps to the camera ID. &lt;br&gt;
So if you specified &lt;code&gt;Camera1&lt;/code&gt; in the script to be the &lt;code&gt;Garden&lt;/code&gt;, use that.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Done!
&lt;/h3&gt;

&lt;p&gt;Done. Now it will move all your recordings to your OneDrive&lt;/p&gt;

&lt;p&gt;This is an initial setup and will update this over time, as the camera mapping part can be a bit more elegant (Use the name specified in MotionEye)&lt;br&gt;
And probably having this script on GitHub with version control would help.&lt;/p&gt;

&lt;p&gt;But for now, good luck! &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cascading build triggers in Azure DevOps</title>
      <dc:creator>Henk van den Brink</dc:creator>
      <pubDate>Wed, 02 Aug 2023 06:41:45 +0000</pubDate>
      <link>https://dev.to/hvdb/azure-devops-trigger-build-from-other-repository-j70</link>
      <guid>https://dev.to/hvdb/azure-devops-trigger-build-from-other-repository-j70</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Say you want to combine the documentation of all your projects into one project. Whenever one of your projects is build it should trigger a new build of the documentation project so that one can also be updated.&lt;/p&gt;

&lt;p&gt;In simple words: trigger downstream builds when your project is build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The situation
&lt;/h2&gt;

&lt;p&gt;We have a project: &lt;code&gt;apple&lt;/code&gt;&lt;br&gt;
And a project: &lt;code&gt;fruit-basket&lt;/code&gt; that depends on &lt;code&gt;apple&lt;/code&gt;&lt;br&gt;
Whenever &lt;code&gt;apple&lt;/code&gt; is build a build of &lt;code&gt;fruit-basket&lt;/code&gt; must be triggered.&lt;/p&gt;
&lt;h3&gt;
  
  
  Apple
&lt;/h3&gt;

&lt;p&gt;This project just does his thing.&lt;br&gt;
Build your &lt;code&gt;Apple&lt;/code&gt; project based on your needs, nothing special.&lt;br&gt;
Maybe make sure to tag the sources if you want to make sure u use the same version later on.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If the documentation that you want to show needs to be build and you don't want to do that in &lt;code&gt;fruit-basket&lt;/code&gt; make sure to upload the build-artefact.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Fruit-basket
&lt;/h3&gt;

&lt;p&gt;This project needs a trigger that when &lt;code&gt;apple&lt;/code&gt; is build it also executed the build.&lt;/p&gt;

&lt;p&gt;You can do this by adding a &lt;code&gt;pipeline&lt;/code&gt; resource.&lt;br&gt;
Open your &lt;code&gt;azure-pipelines.yaml&lt;/code&gt; (based on the name you gave it, or create a new one)&lt;/p&gt;

&lt;p&gt;Then add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources:
  pipelines:
    - pipeline: appleBuild # Identifier
      source: 'apple-build' # This needs to be the name of the pipeline, including spaces if needed!
      trigger: # When do we want it to be triggered?
        branches:
          include: # When these branches are build
          - releases/*
          - main
          - master
        stages:
          - Publish # But only after this stages from apple build

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

&lt;/div&gt;



&lt;p&gt;Now whenever a master build is executed on &lt;code&gt;Apple&lt;/code&gt; and the &lt;code&gt;Publish&lt;/code&gt; is succeeded it will trigger the &lt;code&gt;fruit-basket&lt;/code&gt; build :)&lt;/p&gt;

&lt;p&gt;And inside that build you can then combine the &lt;code&gt;Apple&lt;/code&gt; documentation and others to create one documentation. &lt;/p&gt;

&lt;h4&gt;
  
  
  Using apple
&lt;/h4&gt;

&lt;p&gt;There are two ways to do this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Checkout the source code of &lt;code&gt;apple&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt; Download the build artefact of the &lt;code&gt;apple&lt;/code&gt; build&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Checkout the source code.
&lt;/h5&gt;

&lt;p&gt;For this you need to do two things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the &lt;code&gt;apple&lt;/code&gt; sources as repository&lt;/li&gt;
&lt;li&gt;Checkout the &lt;code&gt;apple&lt;/code&gt; sources at each &lt;code&gt;job&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At the resources part you need to add a &lt;code&gt;repositories&lt;/code&gt; option:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resources: # Already there
  repositories:
    - repository: apple # Identifier to be used in this build
      type: git
      name: FruitCompany/apple # Name of the Azure DevOps repo
      ref: refs/heads/master # Branch to checkout
# As we are going to checkout multiple repositories, we also need to define that we want to use our own sources (apple-basket, ie: self)
    - repository: self
      type: git
      name: self
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now in order to actually use it, at the beginning of each &lt;code&gt;job&lt;/code&gt; you need to &lt;code&gt;checkout&lt;/code&gt; the sources.&lt;/p&gt;

&lt;p&gt;Example stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stages:
  - stage: generate_documentation
    jobs:
      - job:
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - checkout: self # Checkout apple-basket

          - checkout: apple
            path: 'apple'
            fetchDepth: 1

         # Here you can then do whatever you want with the two sources :)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  Download build artefact
&lt;/h5&gt;

&lt;p&gt;Inside a job/step you need to add the following:&lt;br&gt;
(For instance instead of the &lt;code&gt;apple&lt;/code&gt; checkout, or after when you want to use &lt;code&gt;pear&lt;/code&gt; build artefact.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;         - task: DownloadPipelineArtifact@2
            inputs:
              source: specific # We want to use a specific pipeline
              project: FruitCompany # Projectname in Azure DevOps
              pipeline: apple-build # Pipeline 
              artifact: documentation-dist # Name of the uploaded build artefact                
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;With patterns you can limit what it should download from the uploaded artefact&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              patterns: |
                *.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More information on the &lt;code&gt;DownloadPipelineArtifact&lt;/code&gt; task can be  found &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/tasks/reference/download-pipeline-artifact-v2?view=azure-pipelines" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Other triggers
&lt;/h2&gt;

&lt;p&gt;Maybe you want &lt;code&gt;fruit-basket&lt;/code&gt; to build whenever code is pushed to &lt;code&gt;apple&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;You can do that by using the &lt;code&gt;repositories&lt;/code&gt; resource option.&lt;br&gt;
(Don't forget to 'checkout' the source code in each stage)&lt;/p&gt;

&lt;p&gt;And there are a few other options that can trigger downstream build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;containers&lt;/code&gt; # Whenever a new container is pushed to Azure Container Registry &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages&lt;/code&gt; # Whenever a new package is released.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webhooks&lt;/code&gt; # You can create whatever you want with this one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Full description of all the possibilities and options &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/resources?view=azure-devops&amp;amp;tabs=schema" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;

</description>
      <category>azure</category>
      <category>azuredevops</category>
      <category>build</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Multi NPM registries usage made easy (Azure DevOps)</title>
      <dc:creator>Henk van den Brink</dc:creator>
      <pubDate>Mon, 31 Jul 2023 09:06:36 +0000</pubDate>
      <link>https://dev.to/hvdb/multiple-npmrcs-azure-devops-feeds-4a4l</link>
      <guid>https://dev.to/hvdb/multiple-npmrcs-azure-devops-feeds-4a4l</guid>
      <description>&lt;h1&gt;
  
  
  The problem
&lt;/h1&gt;

&lt;p&gt;I use Azure DevOps for development and have several projects in it.&lt;br&gt;&lt;br&gt;
And I am using the feeds possibility from Azure DevOps.&lt;br&gt;
This means that I need to change the NPMRC each time I switch projects so that it uses the correct registry.&lt;/p&gt;

&lt;p&gt;Or you put that in a .npmrc in the repository but then you need to still handle the authentication somehow...&lt;/p&gt;

&lt;h1&gt;
  
  
  What?
&lt;/h1&gt;

&lt;p&gt;I thought there must be an easier way.&lt;br&gt;&lt;br&gt;
Inspired by the &lt;a href="https://www.npmjs.com/package/npmrc" rel="noopener noreferrer"&gt;npmrc&lt;/a&gt; project I created my own solution, more tailored for Azure DevOps.&lt;br&gt;&lt;br&gt;
But you should still be able to use it as a npmrc switcher, just like the other package but with some added functionality. &lt;br&gt;
Like an interactive list. &lt;/p&gt;

&lt;p&gt;To break it down a bit what it does:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It will create an NPMRC with the provided feed-name as registry &lt;strong&gt;AND&lt;/strong&gt; create a PAT that is allowed to pull data from the artifact feed.&lt;br&gt;
And provide options to &lt;em&gt;automatically&lt;/em&gt; renew the PAT.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So how to use it? &lt;/p&gt;

&lt;h4&gt;
  
  
  Easiest
&lt;/h4&gt;

&lt;p&gt;Only one parameter is required&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feedName (the name of the Artifact feed as created in Azure DevOps)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then there are a few defaults:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name of the NPMRC (defaults to the directory name)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next to the name of the NPMRC and the feedname we also need the details of the Azure DevOps &lt;code&gt;Organization&lt;/code&gt; and &lt;code&gt;Project&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
These two values can be retrieved from the &lt;code&gt;repository.url&lt;/code&gt; if that is set in the &lt;code&gt;package.json&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;azOrganization (first part of Azure DevOps url) &lt;/li&gt;
&lt;li&gt;azProject (second part of Azure DevOps url)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example Azure DevOps url:  &lt;code&gt;https://dev.azure.com/henkvandenbrink/kitchensink&lt;/code&gt;&lt;br&gt;&lt;br&gt;
azOrganization = &lt;code&gt;henkvandenbrink&lt;/code&gt;&lt;br&gt;&lt;br&gt;
azProject = &lt;code&gt;kitchensink&lt;/code&gt;&lt;br&gt;&lt;br&gt;
feedName = As specified&lt;/p&gt;

&lt;p&gt;Full example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;snmprc create feed-name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This will generate an NPMRC with the specified feed-name as registry, the azure details are retrieved from package.json and current directory name is used as name.&lt;/em&gt;  &lt;/p&gt;

&lt;h5&gt;
  
  
  More details options
&lt;/h5&gt;

&lt;p&gt;When you are not able to use the defaults or you just want to have more control, you can specify all the parameters as well.  &lt;/p&gt;

&lt;p&gt;&lt;code&gt;snpmrc create feed-name henkvandenbrink kitchensink directory-name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This will generate an NPMRC with the specified feed-name as registry, the azure details that are provided and directory-name is used as name.&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;For all other options, more information and details please go to the &lt;a href="https://www.npmjs.com/package/simple-aznpmrcs" rel="noopener noreferrer"&gt;simple-aznpmrcs&lt;/a&gt; npmjs package where there is a full readme available.&lt;/p&gt;

&lt;p&gt;Feature requested are welcome and/or pull requests.&lt;/p&gt;

&lt;p&gt;Just another step into automating those manual steps :)&lt;/p&gt;

</description>
      <category>azuredevops</category>
      <category>npm</category>
      <category>npmrc</category>
    </item>
    <item>
      <title>How to test your Ansible playbook with Vagrant</title>
      <dc:creator>Henk van den Brink</dc:creator>
      <pubDate>Mon, 20 Sep 2021 19:59:21 +0000</pubDate>
      <link>https://dev.to/hvdb/how-to-test-your-ansible-playbook-with-vagrant-ao1</link>
      <guid>https://dev.to/hvdb/how-to-test-your-ansible-playbook-with-vagrant-ao1</guid>
      <description>&lt;h2&gt;
  
  
  How it all started
&lt;/h2&gt;

&lt;p&gt;Infrastructure as Code is getting more and more attention and for good reasons. &lt;br&gt;
Previously infrastructures where created and maintained by hand, which works well until...&lt;/p&gt;

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

&lt;p&gt;So when I needed to create a new database cluster I looked into a setup that would be reproducible and it should be testable before it was deployed on the actual servers.&lt;/p&gt;

&lt;p&gt;For stateless things we can used &lt;a href="//www.docker.com"&gt;docker&lt;/a&gt; but that wasn't a great fit for the database servers as we need to have persistent storage and scaling and/or changes where not expected that often.&lt;/p&gt;

&lt;p&gt;I went for a golden oldie: &lt;a href="https://www.ansible.com" rel="noopener noreferrer"&gt;Ansible&lt;/a&gt;&lt;br&gt;
While I had worked with it before, it was mainly putting some roles together run the script and validate, manually, that it worked.&lt;/p&gt;

&lt;p&gt;This time around I didn't want to 'test' the provisioning manually. As the setup was a bit larger and the overhead (commit, deploy) was bigger.  &lt;/p&gt;

&lt;p&gt;So I thought there should be a smarter and better way.&lt;/p&gt;
&lt;h2&gt;
  
  
  Start of the journey
&lt;/h2&gt;

&lt;p&gt;As I am mainly a dev engineer, I didn't had thorough expertise on Ansible. (still don't) So &lt;a href="//www.google.com"&gt;Google&lt;/a&gt; it was to get the journey started. Looking through all the things, I mostly saw tests for &lt;a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html" rel="noopener noreferrer"&gt;Ansible Roles&lt;/a&gt;. Or ways to test the playbook on the target systems, but I wanted to test the playbook &lt;em&gt;before&lt;/em&gt; deployment during the build and/or locally.&lt;/p&gt;

&lt;p&gt;Turned out it was not that hard to create, but getting all the information was.&lt;br&gt;
Because of the combination of Ansible and Vagrant and probably my lack of knowledge on those topics 😃~&lt;/p&gt;
&lt;h2&gt;
  
  
  The project setup
&lt;/h2&gt;

&lt;p&gt;First off, what are we actually going to provision?&lt;br&gt;
We will be provisioning a 3 server cluster of a database server, &lt;a href="https://rethinkdb.com/" rel="noopener noreferrer"&gt;RethinkDB&lt;/a&gt;&lt;br&gt;
You probably didn't heard from it before, and you can forget about it after reading this. &lt;/p&gt;

&lt;p&gt;There will be some specific commands for rethinkDB, but most of it will be about the testing setup and ways you should test.&lt;/p&gt;
&lt;h3&gt;
  
  
  Project layout
&lt;/h3&gt;

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

&lt;p&gt;A pretty normal layout for Ansible projects.&lt;br&gt;
The part we will focus on is the &lt;code&gt;test&lt;/code&gt; folder.&lt;/p&gt;
&lt;h2&gt;
  
  
  Test
&lt;/h2&gt;

&lt;p&gt;There are a few levels of testing that can be done for the Ansible playbook.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You can have Ansible roles unit tests, which will validate if the roles 'works'.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And you can have tests for the playbook.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In my case the roles I created where specific for my situation so I didn't, wanted to, create unit tests as that would have been a bit overkill.&lt;/p&gt;

&lt;p&gt;Instead I focussed on a few other things:&lt;/p&gt;

&lt;p&gt;The role itself should validate if the work it did was actually executed and working as intended.&lt;/p&gt;
&lt;h3&gt;
  
  
  Making sure roles are doing what they should
&lt;/h3&gt;

&lt;p&gt;So for instance the &lt;code&gt;install-rethinkdb&lt;/code&gt; role should validate that it did install the package provided.&lt;/p&gt;

&lt;p&gt;To do so, we get the version that is installed by executing the &lt;code&gt;rethinkdb -v&lt;/code&gt; command and save the output. In another step we validate that the output is what we expect.&lt;br&gt;
This way we are verifying that the installation of the (new) version went successful as the version is being used.&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 rethinkdb&lt;/span&gt;
      &lt;span class="na"&gt;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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/tmp/rethinkdb-{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rethinkdb_version&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.x86_64.rpm"&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;allow_downgrade&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
        &lt;span class="na"&gt;disable_gpg_check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;        

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get installed rethinkdb version&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;rethinkdb -v&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;rethinkdb_version_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;Validate rethinkdb version used is the same as wanted version&lt;/span&gt;
      &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&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;rethinkdb_version&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;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rethinkdb_version_installed.stdout"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In another role I updated the configuration and needed to restart the database in order to get the configuration working.&lt;/p&gt;

&lt;p&gt;For this we restart the service via &lt;code&gt;systemd&lt;/code&gt; and wait for couple of seconds.&lt;br&gt;
We then get the &lt;a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/service_facts_module.html" rel="noopener noreferrer"&gt;service_facts&lt;/a&gt; and validate that the service is in a &lt;code&gt;running&lt;/code&gt; state.&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;Make sure rethinkdb service is restarted&lt;/span&gt;
    &lt;span class="s"&gt;systemd&lt;/span&gt;&lt;span class="err"&gt;:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rethinkdb.service&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
      &lt;span class="na"&gt;daemon_reload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&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;Pause for 10 seconds to start the service&lt;/span&gt;
    &lt;span class="na"&gt;pause&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get service statuses&lt;/span&gt;
    &lt;span class="na"&gt;service_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_state_rethinkdb&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;Validate status of rethinkdb service&lt;/span&gt;
    &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&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;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;service_state_rethinkdb.ansible_facts.services['rethinkdb.service'].state"&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The take way here is that you should look at your roles as parts that will have a single purpose and that you should also validate that they do what you expect. Role tests and such are great but verifying it during runtime is a &lt;strong&gt;&lt;em&gt;must&lt;/em&gt;&lt;/strong&gt;, as that is what matters in the end&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the playbook in a controlled (local/build) environment
&lt;/h3&gt;

&lt;p&gt;For testing the playbook we need servers to executed the playbook on. &lt;br&gt;
In comes, if you looked at the layout you could have guessed it, &lt;a href="https://www.vagrantup.com" rel="noopener noreferrer"&gt;Vagrant&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So here is the Vagrantfile that I used, I added some comments in there to help you understand what is going on.&lt;/p&gt;

&lt;p&gt;In short, it will provision 3 servers that are in an own private network and execute the (test) playbook on those servers. Effectively creating a local database cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# -*- mode: ruby -*-&lt;/span&gt;
&lt;span class="c1"&gt;# vi: set ft=ruby :&lt;/span&gt;

&lt;span class="c1"&gt;# Copy certificate files to destination they are expected.&lt;/span&gt;
&lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cp&lt;/span&gt; &lt;span class="sx"&gt;%w(tests/files/ca.pem tests/files/cert.pem tests/files/key.pem)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'roles/ansible-role-rethinkdb-configure/files/'&lt;/span&gt;
&lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cp&lt;/span&gt; &lt;span class="sx"&gt;%w(tests/files/vagrant1.local.net.crt tests/files/vagrant1.local.net.key)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'files/'&lt;/span&gt;
&lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cp&lt;/span&gt; &lt;span class="sx"&gt;%w(tests/files/vagrant2.local.net.crt tests/files/vagrant2.local.net.key)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'files/'&lt;/span&gt;
&lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cp&lt;/span&gt; &lt;span class="sx"&gt;%w(tests/files/vagrant3.local.net.crt tests/files/vagrant3.local.net.key)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'files/'&lt;/span&gt;

&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

  &lt;span class="c1"&gt;# If you have issues with SSL certificates add this&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box_download_insecure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="c1"&gt;# We want to use redhat7 as the target servers are also redhat 7&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"generic/rhel7"&lt;/span&gt;

  &lt;span class="c1"&gt;# To save overhead and time we use linked_clones (https://www.vagrantup.com/docs/providers/virtualbox/configuration#linked-clones)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;linked_clone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# We want to provision 3 servers for our cluster&lt;/span&gt;
  &lt;span class="no"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="no"&gt;N&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;machine_id&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="c1"&gt;# Give the server a unique name&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"vagrant&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;machine_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="c1"&gt;# same for the hostname of the VM&lt;/span&gt;
      &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vagrant&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;machine_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
      &lt;span class="c1"&gt;# Here we will be setting up the private network for the cluster.&lt;/span&gt;
      &lt;span class="c1"&gt;# In this network the cluster can communicate to each other.&lt;/span&gt;
      &lt;span class="c1"&gt;# We also want to expose (forward) some ports that are used inside the VM to the host (so that we can connect to the dashboard)&lt;/span&gt;
      &lt;span class="c1"&gt;# In this example I only added it for server1.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;machine_id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="c1"&gt;# Private network instruction for vagrant&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"192.168.77.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;machine_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;virtualbox__intnet: &lt;/span&gt;&lt;span class="s2"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"network"&lt;/span&gt;
        &lt;span class="c1"&gt;# Forward all the ports below to the host. auto_correct is set to true to chose an available port if the one specified is taken&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"forwarded_port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;10082&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;10082&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auto_correct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"forwarded_port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;10081&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;10081&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auto_correct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"forwarded_port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;8443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;8443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auto_correct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"forwarded_port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;9088&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;9088&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auto_correct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"forwarded_port"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;guest: &lt;/span&gt;&lt;span class="mi"&gt;9089&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;host: &lt;/span&gt;&lt;span class="mi"&gt;9089&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;auto_correct: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="c1"&gt;# The other servers (2,3) should be in the private network&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"192.168.77.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;machine_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;virtualbox__intnet: &lt;/span&gt;&lt;span class="s2"&gt;"network"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;

      &lt;span class="c1"&gt;# Only execute when all the machines are up and ready.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;machine_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;N&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="ss"&gt;:ansible&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
          &lt;span class="c1"&gt;# Disable default limit to connect to all the machines&lt;/span&gt;
          &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"all"&lt;/span&gt;
          &lt;span class="c1"&gt;# Playbook that should be executed, in this case the test playbook&lt;/span&gt;
          &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;playbook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tests/test.yml"&lt;/span&gt;
          &lt;span class="c1"&gt;# Please log it all&lt;/span&gt;
          &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verbose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
          &lt;span class="c1"&gt;# We need to provied a few extra vars to the playbook&lt;/span&gt;
          &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extra_vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="no"&gt;RETHINKDB_DATABASE_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="no"&gt;NGINX_DASHBOARD_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'pasword'&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="c1"&gt;# These groups are the same as you normally specify in the inventorie file&lt;/span&gt;
          &lt;span class="n"&gt;ansible&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="ss"&gt;leader: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vagrant1"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="ss"&gt;followers: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vagrant2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"vagrant3"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 'test' playbook looks like this:&lt;br&gt;
&lt;em&gt;I am using a specific playbook for tests as I needed to add a few things that are normally there if you get a server from your provider. At least that was the case in my project.&lt;/em&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="nn"&gt;---&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install rethinkdb&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="c1"&gt;# Executed this on all 3 servers&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt; &lt;span class="c1"&gt;# become root&lt;/span&gt;

  &lt;span class="c1"&gt;# pre_tasks are there to create the rethinkdb user. &lt;/span&gt;
  &lt;span class="c1"&gt;# These will be executed before the rest.&lt;/span&gt;
  &lt;span class="na"&gt;pre_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;Add group "rethinkdb01"&lt;/span&gt;
      &lt;span class="na"&gt;group&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;rethinkdb01&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;Add user "ansi_rethinkdb01"&lt;/span&gt;
      &lt;span class="na"&gt;user&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;ansi_rethinkdb01&lt;/span&gt;
        &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rethinkdb01&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/sbin/nologin&lt;/span&gt;
        &lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
        &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vagrant&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nologin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;User"&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;Add user "rethinkdb01"&lt;/span&gt;
      &lt;span class="na"&gt;user&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;ansi_rethinkdb01&lt;/span&gt;
        &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rethinkdb01&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/sbin/nologin&lt;/span&gt;
        &lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
        &lt;span class="na"&gt;comment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vagrant&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nologin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;User"&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;Allow 'rethinkdb01' group to have passwordless sudo&lt;/span&gt;
      &lt;span class="na"&gt;lineinfile&lt;/span&gt;&lt;span class="pi"&gt;:&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;/etc/sudoers&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;regexp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;^%rethinkdb01'&lt;/span&gt;
        &lt;span class="na"&gt;line&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%rethinkdb01&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL=(ALL)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NOPASSWD:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ALL'&lt;/span&gt;
        &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;visudo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-cf&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;%s'&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;Add sudoers users to rethinkdb01 group&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;name=ansi_rethinkdb01 groups=rethinkdb01 append=yes state=present createhome=yes&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;Add sudoers users to rethinkdb01 group&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;name=rethinkdb01 groups=rethinkdb01 append=yes state=present createhome=yes&lt;/span&gt;

&lt;span class="c1"&gt;# As the firewall was interrupting with the private network I disabled it&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;Stop and disable firewalld.&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;firewalld&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;stopped&lt;/span&gt;
        &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False&lt;/span&gt;

 &lt;span class="c1"&gt;# Rethinkdb can be memory happy so we needed to increase the virtual memory on the real servers. Here to test if it works.&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;Increase virtual memory&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "vm.max_map_count=262144" &amp;gt;&amp;gt; /etc/sysctl.d/rethinkdb.conf &amp;amp;&amp;amp; sudo sysctl --system&lt;/span&gt;

&lt;span class="c1"&gt;# We need to have  machine certificates for cluster security.&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;Copy certificate files&lt;/span&gt;
      &lt;span class="na"&gt;copy&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="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;item.src&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;dest&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;item.dest&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;group&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;target_group&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;owner&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;target_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
      &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&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_hostname&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.local.net.crt'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/etc/ssl/certs/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ansible_hostname&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.local.net.crt'&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&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_hostname&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.local.net.key'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/etc/ssl/certs/{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ansible_hostname&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.local.net.key'&lt;/span&gt;&lt;span class="pi"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# The roles that will be executed.&lt;/span&gt;
&lt;span class="err"&gt;  &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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../roles/ansible-role-install-rethinkdb-offline&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;../roles/ansible-role-rethinkdb-configure&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;../roles/ansible-role-install-node-exporter-offline&lt;/span&gt;

&lt;span class="c1"&gt;# This is the real 'testing' part of the playbook&lt;/span&gt;
&lt;span class="c1"&gt;# As described before each role should make sure it does what it should do.&lt;/span&gt;
&lt;span class="c1"&gt;# But in order to make sure the provisioning works as a whole we will be testing some things here again&lt;/span&gt;

  &lt;span class="na"&gt;post_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get virtual memory config&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sysctl vm.max_map_count&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;rethinkdb_virtual_memory&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;Validate virtual memory config is correct&lt;/span&gt;
      &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'vm.max_map_count&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;262144'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rethinkdb_virtual_memory.stdout"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get service status&lt;/span&gt;
      &lt;span class="na"&gt;service_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_state_rethinkdb&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;Validate status of rethinkdb service&lt;/span&gt;
      &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&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;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;service_state_rethinkdb.ansible_facts.services['rethinkdb.service'].state"&lt;/span&gt;

&lt;span class="c1"&gt;# Call the node_exporter locally and save the output&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 node exporter is working&lt;/span&gt;
      &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://localhost:10081/metrics&lt;/span&gt;
        &lt;span class="na"&gt;return_content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
        &lt;span class="na"&gt;validate_certs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node_exporter_response&lt;/span&gt;

&lt;span class="c1"&gt;# Validate that there is some output.  &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;Validate node exporter running&lt;/span&gt;
      &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'go_goroutines'&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;node_exporter_response.content"&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 horizon and proxy&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;leader&lt;/span&gt; &lt;span class="c1"&gt;# Only execute this on the servers in the `leader` group&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;../roles/ansible-role-rethinkdb-horizon&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;../roles/ansible-role-rethinkdb-exporter&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;../roles/ansible-role-rethinkdb-nginx&lt;/span&gt;

  &lt;span class="na"&gt;post_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get service status&lt;/span&gt;
      &lt;span class="na"&gt;service_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_state_rethinkdb&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;Validate status of rethinkdb service&lt;/span&gt;
      &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&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;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;service_state_rethinkdb.ansible_facts.services['rethinkdb.service'].state"&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;Validate status of rethinkdb-exporter service&lt;/span&gt;
      &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&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;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;service_state_rethinkdb.ansible_facts.services['rethinkdb-exporter.service'].state"&lt;/span&gt;
      &lt;span class="na"&gt;run_once&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;    

&lt;span class="c1"&gt;# The rethinkdb rethinkdb_exporter should provided metrics, and we want to check the results so save it to a variable.&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 all nodes are connected&lt;/span&gt;
      &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://localhost:10082/metrics&lt;/span&gt;
        &lt;span class="na"&gt;return_content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
        &lt;span class="na"&gt;validate_certs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no&lt;/span&gt;
      &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exporter_response&lt;/span&gt;
      &lt;span class="na"&gt;run_once&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="c1"&gt;# Validate that there are three servers connected to the cluster according to the rethinkdb_exporter&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;Validate number of nodes running&lt;/span&gt;
      &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;that&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;'rethinkdb_cluster_servers_total&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;rethinkdb_number_of_nodes&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;in&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exporter_response.content"&lt;/span&gt;
      &lt;span class="na"&gt;run_once&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;...&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;post_taks&lt;/code&gt; you can add as many assertions as you want/need. Make sure that you at least test the basics, so that the thing you installed is up and working.&lt;/p&gt;

&lt;h3&gt;
  
  
  Execute it
&lt;/h3&gt;

&lt;p&gt;Well we only need to run one command to test the whole thing:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Or if you want to execute it on Azure DevOps:&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="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verifying&lt;/span&gt;
    &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;macOS-latest'&lt;/span&gt;
    &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test_the_setup&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;or(eq('${{parameters.testRethinkDBSetup}}', &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="s"&gt;), eq(variables.isMain, &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="s"&gt;))&lt;/span&gt;
        &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;brew install ansible&lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ansible'&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
              &lt;span class="s"&gt;cd rethinkdb&lt;/span&gt;
              &lt;span class="s"&gt;vagrant up    &lt;/span&gt;
            &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rethinkdb&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ansible&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;scripts'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start the Vagrantfile and in effect create the VM's and start the playbook execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant1: Calculating and comparing box checksum...
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; vagrant1: Successfully added box &lt;span class="s1"&gt;'generic/rhel7'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;v3.2.10&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="s1"&gt;'virtualbox'&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; vagrant1: Preparing master VM &lt;span class="k"&gt;for &lt;/span&gt;linked clones...
    vagrant1: This is a one &lt;span class="nb"&gt;time &lt;/span&gt;operation. Once the master VM is prepared,
    vagrant1: it will be used as a base &lt;span class="k"&gt;for &lt;/span&gt;linked clones, making the creation
    vagrant1: of new VMs take milliseconds on a modern system.
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; vagrant1: Importing base box &lt;span class="s1"&gt;'generic/rhel7'&lt;/span&gt;...

&lt;span class="nt"&gt;---&lt;/span&gt;
PLAY &lt;span class="o"&gt;[&lt;/span&gt;Install rethinkdb] &lt;span class="k"&gt;*******************************************************&lt;/span&gt;

TASK &lt;span class="o"&gt;[&lt;/span&gt;Gathering Facts] &lt;span class="k"&gt;*********************************************************&lt;/span&gt;
ok: &lt;span class="o"&gt;[&lt;/span&gt;vagrant2]
ok: &lt;span class="o"&gt;[&lt;/span&gt;vagrant3]
ok: &lt;span class="o"&gt;[&lt;/span&gt;vagrant1]

TASK &lt;span class="o"&gt;[&lt;/span&gt;Add group &lt;span class="s2"&gt;"rethinkdb01"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="k"&gt;*************************************************&lt;/span&gt;
changed: &lt;span class="o"&gt;[&lt;/span&gt;vagrant2] &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"changed"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;, &lt;span class="s2"&gt;"gid"&lt;/span&gt;: 1001, &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"rethinkdb01"&lt;/span&gt;, &lt;span class="s2"&gt;"state"&lt;/span&gt;: &lt;span class="s2"&gt;"present"&lt;/span&gt;, &lt;span class="s2"&gt;"system"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
changed: &lt;span class="o"&gt;[&lt;/span&gt;vagrant1] &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"changed"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;, &lt;span class="s2"&gt;"gid"&lt;/span&gt;: 1001, &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"rethinkdb01"&lt;/span&gt;, &lt;span class="s2"&gt;"state"&lt;/span&gt;: &lt;span class="s2"&gt;"present"&lt;/span&gt;, &lt;span class="s2"&gt;"system"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
changed: &lt;span class="o"&gt;[&lt;/span&gt;vagrant3] &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"changed"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;, &lt;span class="s2"&gt;"gid"&lt;/span&gt;: 1001, &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"rethinkdb01"&lt;/span&gt;, &lt;span class="s2"&gt;"state"&lt;/span&gt;: &lt;span class="s2"&gt;"present"&lt;/span&gt;, &lt;span class="s2"&gt;"system"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt;

PLAY RECAP &lt;span class="k"&gt;*********************************************************************&lt;/span&gt;
vagrant1                   : &lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;79   &lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;48   &lt;span class="nv"&gt;unreachable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;skipped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;rescued&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0   
vagrant2                   : &lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;44   &lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;27   &lt;span class="nv"&gt;unreachable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;skipped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;rescued&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0   
vagrant3                   : &lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;44   &lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;27   &lt;span class="nv"&gt;unreachable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;skipped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;rescued&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0    &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup you are sure that the provisioned servers are working as intended and that if you make a change it is validated. Even after the creator left 😄&lt;/p&gt;

&lt;p&gt;We now have a local running database cluster that is tested and, as a bonus, you can use it for development.&lt;/p&gt;

&lt;p&gt;If you have any question regarding testing Ansible playbooks and or setting up a local Vagrant (cluster) environment, please let me know! (Not sure if I can answer them but will do my best 😰)&lt;/p&gt;

&lt;p&gt;Also any other remarks/improvements are always welcome.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>infra</category>
      <category>testing</category>
      <category>servers</category>
    </item>
    <item>
      <title>Build versioning made easy in Azure DevOps</title>
      <dc:creator>Henk van den Brink</dc:creator>
      <pubDate>Fri, 17 Sep 2021 20:41:15 +0000</pubDate>
      <link>https://dev.to/hvdb/build-versioning-made-easy-on-azure-devops-1e33</link>
      <guid>https://dev.to/hvdb/build-versioning-made-easy-on-azure-devops-1e33</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Versioning is a very important part of creating, deploying and maintaining code. Having a understandable and readable version is required. &lt;/p&gt;

&lt;p&gt;Here are a few example of versioning systems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;SemVer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://calver.org/" rel="noopener noreferrer"&gt;Calendar Versioning (CalVer)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/romversioning/romver" rel="noopener noreferrer"&gt;Romantic Versioning (RomVer)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And probably a few others are out there.&lt;br&gt;&lt;br&gt;
I have only used the one that most people use: SemVer&lt;/p&gt;

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

&lt;p&gt;I moved to &lt;a href="https://dev.azure.com" rel="noopener noreferrer"&gt;Azure DevOps&lt;/a&gt; for building and releasing software.&lt;br&gt;
After getting used to it there was one big thing that bugged me, the versioning of the builds and releases...&lt;/p&gt;

&lt;p&gt;By default Azure DevOps uses &lt;code&gt;$(Date:yyyyMMdd).$(Rev:r)&lt;/code&gt; as the build and release number.  &lt;/p&gt;

&lt;p&gt;And that is something where you cannot do anything with...&lt;br&gt;
Yes it is unique but there is absolutely no way for you to find out which build/release pipeline contained the version you wanted to look at.&lt;/p&gt;

&lt;p&gt;The other issue I had was the fact that when building a PR the version that was used was the one from master/main (if branched from there) and most likely this version was already released. So creating an artefact for test deployment for instance would fail, it should be a unique version.&lt;/p&gt;
&lt;h1&gt;
  
  
  The solution helper
&lt;/h1&gt;

&lt;p&gt;So there must be a way to get the behaviour I wanted, after searching and searching I could not find an existing solution that was easy to use and wasn't polluting the templates.&lt;/p&gt;

&lt;p&gt;So I created a little NPM module to help me: &lt;a href="https://www.npmjs.com/package/simple-versioner" rel="noopener noreferrer"&gt;simple-versioner&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;This helper reads the version from the file that contains the version, defaults to &lt;code&gt;package.json&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;There are two cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Stable release (ie: master, or other stable branch provided&lt;br&gt;
It will use the version as is. IE &lt;code&gt;1.0.1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build is for a non stable branch and/or a Pull Request&lt;br&gt;
The versioning specified will be postfixed with the branch name and the commit sha.&lt;br&gt;
IE: &lt;code&gt;1.0.1-refs-heads-branch-8a9fee0b&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This way the version used in a Pull Request will always be unique, so that you can use the artefact.&lt;br&gt;
And in the build pipeline overview you can easily find the build of your version:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uuyodr6y6kfzvmao7qj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5uuyodr6y6kfzvmao7qj.png" alt="Screenshot 2021-09-17 at 22.14.29"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Extra bonus: It validates it the version already exists or not (git tag with that version exists) if it exists it will fail and therefor stopping the build&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How you can use it
&lt;/h2&gt;

&lt;p&gt;So hopefully I got your exited to also use it or at least give it a try. &lt;/p&gt;

&lt;p&gt;Well as the aim was to have a simple plugin, you only need to add two lines to your azure yml. (make sure to add it as one of the first tasks in your pipeline)&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;bash&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;npx&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;simple-versioner'&lt;/span&gt;
  &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Get&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;set&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;correct&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;version'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. &lt;br&gt;
It will update the &lt;code&gt;Build.Buildnumber&lt;/code&gt; parameter in Azure and the provided versioning file with the (new) correct version.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Possible options
&lt;/h3&gt;

&lt;p&gt;There are a few options available to customise the execution of the plugin. Please have a look at &lt;a href="https://www.npmjs.com/package/simple-versioner" rel="noopener noreferrer"&gt;simple-versioner&lt;/a&gt; to see the latest possible options.&lt;/p&gt;

&lt;h2&gt;
  
  
  For what can you use it?
&lt;/h2&gt;

&lt;p&gt;Per default it expect a &lt;code&gt;package.json&lt;/code&gt; that's because I created the plugin for that purpose.&lt;br&gt;
But you can use it on any JSON file that has a &lt;code&gt;version&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;So for instance the &lt;code&gt;vss-extension.json&lt;/code&gt; which is used for Azure DevOps tasks.&lt;/p&gt;

&lt;p&gt;Other files and or structure can easily be added, have a look at the code and PR's are welcome :) &lt;a href="https://github.com/hvdb/simple-versioner" rel="noopener noreferrer"&gt;simple-versioner&lt;/a&gt; &lt;/p&gt;

</description>
      <category>azure</category>
      <category>azuredevops</category>
      <category>semver</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
