<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Linux Handbook]]></title><description><![CDATA[Linux Command Line, Server, DevOps and Cloud]]></description><link>https://linuxhandbook.com/</link><image><url>https://linuxhandbook.com/favicon.png</url><title>Linux Handbook</title><link>https://linuxhandbook.com/</link></image><generator>Ghost 3.39</generator><lastBuildDate>Fri, 11 Dec 2020 07:29:43 GMT</lastBuildDate><atom:link href="https://linuxhandbook.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[How to Migrate CentOS 8 to CentOS Steam]]></title><description><![CDATA[CentOS 8 reaches end of life by the end of 2021. Learn how to update CentOS 8 to CentOS Stream.]]></description><link>https://linuxhandbook.com/update-to-centos-stream/</link><guid isPermaLink="false">5fd0cab288ca90000100f3c6</guid><category><![CDATA[Tips]]></category><dc:creator><![CDATA[Debdut Chakraborty]]></dc:creator><pubDate>Wed, 09 Dec 2020 14:55:15 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/12/centos-to-centos-stream-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/12/centos-to-centos-stream-1.png" alt="How to Migrate CentOS 8 to CentOS Steam"><p>Red Hat and CentOS recently announced that <a href="https://blog.centos.org/2020/12/future-is-centos-stream/">CentOS will be converted to a rolling release distribution</a> in the form of CentOS Stream.</p><p>While CentOS 7 will be supported till 2024, CentOS 8 support ends by the end of 2021. </p><p>With this development, the current CentOS 8 users are left with two choices, either move to server distributions like Debian, <a href="https://www.opensuse.org/">openSUSE</a>, Ubuntu LTS, or update the current CentOS system to CentOS Stream. </p><p>In this tutorial, I'm going to show you how you can update your current CentOS 8 install to CentOS Stream.</p><h2 id="upgrading-centos-8-to-centos-stream">Upgrading CentOS 8 to CentOS Stream</h2><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/12/centos-to-centos-stream.png" class="kg-image" alt="How to Migrate CentOS 8 to CentOS Steam" srcset="https://linuxhandbook.com/content/images/size/w600/2020/12/centos-to-centos-stream.png 600w, https://linuxhandbook.com/content/images/2020/12/centos-to-centos-stream.png 800w" sizes="(min-width: 720px) 720px"></figure><p>The idea is simple. To convert, you need to add Stream's repos, and remove the existing ones. </p><p>Fortunately, you don't have to do all that manually. There is a handy tool provided by the CentOS team for this purpose.</p><blockquote>Make a backup before you update. The update procedure is simple but create backup for the sake of it.</blockquote><h3 id="step-1-install-the-repo-files">Step 1: Install the repo files</h3><p>Install the package <code>centos-release-stream</code>. This contains all the repo files that are needed.</p><pre><code class="language-bash">dnf install centos-release-stream -y
</code></pre><h3 id="step-2-update-the-system">Step 2: Update the system</h3><p>Update the system or the packages to be specific, by running the <code>distro-sync</code> command.</p><pre><code class="language-bash">dnf distro-sync -y
</code></pre><p>This syncs all the local packages to the upstream's versions.</p><h3 id="step-3-reboot-and-double-check-the-installed-version">Step 3: Reboot and double-check the installed version</h3><p>Now, reboot your server: </p><pre><code>reboot</code></pre><p>After the system is booted successfully, verify the migration by <a href="https://linuxhandbook.com/check-centos-version/">checking the CentOS version</a>.</p><p>You can do that by reading the <code>os-release</code> file:</p><pre><code>[root@li2029-76 ~]# cat /etc/centos-release 
CentOS Stream release 8</code></pre><p>Or, read the <code>centos-release</code> file:</p><pre><code class="language-bash">[root@li2029-76 ~]# cat /etc/os-release 
NAME="CentOS Stream"
VERSION="8"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="8"
PLATFORM_ID="platform:el8"
PRETTY_NAME="CentOS Stream 8"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:8"
HOME_URL="https://centos.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux 8"
REDHAT_SUPPORT_PRODUCT_VERSION="CentOS Stream"
</code></pre><p>You should see a similar output. </p><p>I have made a video of the entire process. The video is being edited but you can follow it nonetheless.</p><figure class="kg-card kg-embed-card"><iframe src="https://player.vimeo.com/video/488877467?app_id=122963" width="1280" height="720" frameborder="0" allow="autoplay; fullscreen" allowfullscreen title="How to Update CentOS 8 to CentOS Steam"></iframe></figure><h2 id="is-it-safe-to-upgrade-to-centos-stream">Is it safe to upgrade to CentOS Stream?</h2><p>How careful should you be before starting the update? Is it safe? To be honest, I can't tell you "Oh do it, it'll be just alright" in confidence. A lot of moving parts contribute to the stability of a system. This process doesn't exactly make sure <em>nothing</em> will break.</p><p>To roughly test whether the process will break all of the existing setups or not, I deployed a CentOS 8 server on <a href="https://www.linode.com/?r=19db9d1ce8c1c91023c7afef87a28ce8c8c067bd">Linode</a>. On this server, I installed Nextcloud, natively i.e. no containers, HTTPS enabled. I also disabled SELinux and firewalld just to make the process slightly faster. </p><p>After installing the <code>centos-release-stream</code> package and running the <code>dnf distro-sync</code> command, there was a total of 101 packages that needed to be updated. I updated and rebooted afterwards, fortunately, everything was just fine.</p><p>But here's the thing, this experiment of mine is no proof of anything. If anything, this shows that <strong>not all of the existing setups will break</strong>, if you're updating to CentOS Stream from 8. This still doesn't confirm whether it's <strong>totally</strong> safe or not. The stability of your system post-upgrade, depends on a lot of things, like:</p><ul><li>How many services is the server currently running?</li><li>How the services are set up or installed?</li><li>How many packages does it currently have installed?</li><li>When was the last time it was updated?</li></ul><p>This is why I suggest taking a snapshot of your system if you are running in a VM. Take backup because you can never be too careful.</p><p>As for service downtime, if your system is part of a cluster, the orchestrator should take care of the total number of running instances, eliminating downtime. If you're using a single node docker environment, using the <code>live-restore</code> feature of docker will eliminate any downtime in case a docker update is on the queue. Other than that, your current methods of countering any downtime <em>should</em> be good enough.</p><p>I hope this article was helpful to you. You can reach me <a href="https://twitter.com/imdebdut">@imdebdut</a>, or <a href="https://twitter.com/linuxhandbook">@linuxhandbook</a>. You can also join our <a href="https://t.me/linuxhandbook_official">Telegram group</a>.</p>]]></content:encoded></item><item><title><![CDATA[RHCE Ansible Series #10: RHEL System Roles]]></title><description><![CDATA[In this chapter, you'll learn about RHEL specific system roles. Needless to say, this is for Red Hat and CentOS only.]]></description><link>https://linuxhandbook.com/rhel-system-roles/</link><guid isPermaLink="false">5fc70d5ccefb9200017bd25c</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[Ahmed Alkabary]]></dc:creator><pubDate>Wed, 02 Dec 2020 05:57:06 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/12/RHEL-System-Roles-Ansible.png" medium="image"/><content:encoded/></item><item><title><![CDATA[How to Resize LVM Partition Inside an Extended Partition]]></title><description><![CDATA[Resizing a logical volume in Linux is not very difficult but it can be tricky if the root is under an extended partition.]]></description><link>https://linuxhandbook.com/resize-lvm-partition/</link><guid isPermaLink="false">5fc243f5cefb9200017bd121</guid><category><![CDATA[Tips]]></category><dc:creator><![CDATA[Rakesh Jain]]></dc:creator><pubDate>Sat, 28 Nov 2020 14:39:40 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/Resize-LVM-Partition.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/Resize-LVM-Partition.png" alt="How to Resize LVM Partition Inside an Extended Partition"><p>Resizing a logical volume in Linux is not very difficult and can be achieved through very straightforward approach. Here are the usual steps:</p><ol><li>Create new partition on hard disk.</li><li>Add the partition you just created as a physical volume.</li><li>Add the new physical volume to the volume group.</li><li>Assign space from the volume group to the logical volume.</li><li>Resize the filesystem.</li></ol><p>But in this scenario, you have the root filesystem (as an <a href="https://tldp.org/HOWTO/LVM-HOWTO/whatislvm.html">LVM</a> partition) mounted under an extended partition, not within a primary partition. You just have one primary partition which is mounted on /boot and rest all space is part of that extended partition.</p><p>Sounds troublesome? Let me show you how to resize LVM inside extended partition.</p><h2 id="resizing-lvm-partition-inside-extended-partition">Resizing LVM partition inside extended partition</h2><p>I am using a Linux installed in virtual machine in this tutorial.</p><blockquote>Please keep in mind that you should be very careful while dealing with disk partitions. </blockquote><h3 id="step-1-shut-down-your-vm-and-increase-the-disk-size">Step 1:  Shut down your VM and increase the disk size</h3><p>First, shut down your VM and increase the disk size. Here, I have increased the disk /dev/sda size by 20 GB to around 40 GB. Then start your VM and go to the console.</p><p>Have a look at our disk partitions.</p><pre><code class="language-bash">root@Ubuntu14:~# fdisk -l

Disk /dev/sda: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000a975f

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          501758    41940991    20719617    5  Extended
/dev/sda5          501760    41940991    20719616   8e  Linux LVM</code></pre><p>If you <a href="https://linuxhandbook.com/df-command/">analyze the disk space with df command</a>, here's what it shows for me:</p><pre><code class="language-bash">root@Ubuntu14:~# df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  989M  4.0K  989M   1% /dev
tmpfs          tmpfs     201M  716K  200M   1% /run
/dev/dm-0      ext4       19G  1.5G   16G   9% /
none           tmpfs     4.0K     0  4.0K   0% /sys/fs/cgroup
none           tmpfs     5.0M     0  5.0M   0% /run/lock
none           tmpfs    1001M     0 1001M   0% /run/shm
none           tmpfs     100M     0  100M   0% /run/user
/dev/sda1      ext2      236M   40M  184M  18% /boot
</code></pre><p>Here, the object is to increase the size of the partition /dev/dm-0 which is mounted on /dev/sda5.</p><p>Let me also show the current status of physical volumes, volume groups and logical volumes:</p><pre><code class="language-bash">root@Ubuntu14:~# lvs
  LV     VG          Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  root   ubuntu14-vg -wi-ao---   18.74g                                           
  swap_1 ubuntu14-vg -wi-ao--- 1020.00m   
  
root@Ubuntu14:~# pvs
  PV         VG          Fmt  Attr PSize  PFree 
  /dev/sda5  ubuntu14-vg lvm2 a--  19.76g 20.00m
  
root@Ubuntu14:~# vgs
  VG          #PV #LV #SN Attr   VSize  VFree 
  ubuntu14-vg   1   2   0 wz--n- 19.76g 20.00m
</code></pre><p>They all have around 20 GB of storage space assigned to them.</p><h3 id="step-2-begin-the-lvm-resizing-process">Step 2: Begin the LVM resizing process</h3><p>Here are the steps for resizing the LVM partition:</p><p>Open fdisk utility and look at the partitions:</p><pre><code>root@Ubuntu14:~# fdisk /dev/sda

Command (m for help): p

Disk /dev/sda: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000a975f

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          501758    41940991    20719617    5  Extended
/dev/sda5          501760    41940991    20719616   8e  Linux LVM</code></pre><p>Delete the extended partition (/dev/sda2) using command <code>d</code>, which will automatically delete the underlying LVM partition which is /dev/sda5.</p><pre><code>Command (m for help): d
Partition number (1-5): 2</code></pre><p>Create a new partition again as extended using command <code>n</code> with default start and end cylinder values.</p><pre><code>Command (m for help): n
Partition type:
   p   primary (1 primary, 0 extended, 3 free)
   e   extended
Select (default p): e
Partition number (1-4, default 2): 
Using default value 2
First sector (499712-83886079, default 499712): 
Using default value 499712
Last sector, +sectors or +size{K,M,G} (499712-83886079, default 83886079): 
Using default value 83886079

Command (m for help): p

Disk /dev/sda: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000a975f

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          499712    83886079    41693184    5  Extended
</code></pre><p>Create a logical partition (dev/sda5) using the default start and end cylinder values.</p><pre><code>Command (m for help): n
Partition type:
   p   primary (1 primary, 1 extended, 2 free)
   l   logical (numbered from 5)
Select (default p): l
Adding logical partition 5
First sector (501760-83886079, default 501760): 
Using default value 501760
Last sector, +sectors or +size{K,M,G} (501760-83886079, default 83886079): 
Using default value 83886079</code></pre><p>Switch to expert mode by pressing <code>x</code>.</p><pre><code>Command (m for help): x
</code></pre><p>Run expert command <code>b</code> to adjust the beginning of the partition (this changes the partition size, not where it ends). Enter the start value as it was earlier before deleting the partitions. Here it is 501760.</p><pre><code>Expert command (m for help): b
Partition number (1-5): 5
New beginning of data (499713-83886079, default 501760): 501760</code></pre><p>Then run <code>r</code> to return to the main menu.</p><pre><code>Expert command (m for help): r</code></pre><p>Check the partition number just to make sure.</p><pre><code>Command (m for help): p

Disk /dev/sda: 42.9 GB, 42949672960 bytes
255 heads, 63 sectors/track, 5221 cylinders, total 83886080 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000a975f

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      499711      248832   83  Linux
/dev/sda2          499712    83886079    41693184    5  Extended
/dev/sda5          501760    83886079    41692160   83  Linux</code></pre><p>Now change the partition type to LVM by pressing <code>t</code> command and chose type <code>8e</code>.</p><pre><code>Command (m for help): t
Partition number (1-5): 5
Hex code (type L to list codes): 8e
Changed system type of partition 5 to 8e (Linux LVM)</code></pre><p>Press <code>w</code> to write all the changes to the disk.</p><pre><code>Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.

WARNING: Re-reading the partition table failed with error 16: Device or resource busy.
The kernel still uses the old table. The new table will be used at
the next reboot or after you run partprobe(8) or kpartx(8)
Syncing disks.</code></pre><h3 id="step-3-make-manual-changes-to-physical-and-logical-volume">Step 3: Make manual changes to physical and logical volume</h3><p>Run partprobe command to inform OS about partition table changes:</p><pre><code class="language-bash">root@Ubuntu14:~# partprobe /dev/sda</code></pre><p>Run lsblk command to see that /dev/sda5 is now around 40 GB in size (for me).</p><pre><code class="language-bash">root@Ubuntu14:~# lsblk 
NAME                           MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                              8:0    0    40G  0 disk 
├─sda1                           8:1    0   243M  0 part /boot
├─sda2                           8:2    0     1K  0 part 
└─sda5                           8:5    0  39.8G  0 part 
  ├─ubuntu14--vg-root (dm-0)   252:0    0  18.8G  0 lvm  /
  └─ubuntu14--vg-swap_1 (dm-1) 252:1    0  1020M  0 lvm  [SWAP]
sr0                             11:0    1  1024M  0 rom  
</code></pre><p>Run the df command and you'll notice that /dev/dm-0  still shows the old size details:</p><pre><code class="language-bash">root@Ubuntu14:~# df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  989M  4.0K  989M   1% /dev
tmpfs          tmpfs     201M  716K  200M   1% /run
/dev/dm-0      ext4       19G  1.5G   16G   9% /
none           tmpfs     4.0K     0  4.0K   0% /sys/fs/cgroup
none           tmpfs     5.0M     0  5.0M   0% /run/lock
none           tmpfs    1001M     0 1001M   0% /run/shm
none           tmpfs     100M     0  100M   0% /run/user
/dev/sda1      ext2      236M   40M  184M  18% /boot
</code></pre><p>Same is the case with Physical volumes, Volume groups and Logical volumes:</p><pre><code class="language-bash">root@Ubuntu14:~# lvs
  LV     VG          Attr      LSize    Pool Origin Data%  Move Log Copy%  Convert
  root   ubuntu14-vg -wi-ao---   18.74g                                           
  swap_1 ubuntu14-vg -wi-ao--- 1020.00m   
  
root@Ubuntu14:~# pvs
  PV         VG          Fmt  Attr PSize  PFree 
  /dev/sda5  ubuntu14-vg lvm2 a--  19.76g 20.00m
  
root@Ubuntu14:~# vgs
  VG          #PV #LV #SN Attr   VSize  VFree 
  ubuntu14-vg   1   2   0 wz--n- 19.76g 20.00m
</code></pre><p>You'l have to do some manual effort here.</p><p>Resize the Physical volume:</p><pre><code class="language-bash">root@Ubuntu14:~# pvresize /dev/sda5 
Physical volume "/dev/sda5" changed
1 physical volume(s) resized / 0 physical volume(s) not resized
</code></pre><p>Now check Physical Volume and Volume group status and see that it is properly showing the new size:</p><pre><code class="language-bash">root@Ubuntu14:~# pvs
PV         VG          Fmt  Attr PSize  PFree 
/dev/sda5  ubuntu14-vg lvm2 a--  39.76g 20.02g

root@Ubuntu14:~# vgs
VG          #PV #LV #SN Attr   VSize  VFree 
ubuntu14-vg   1   2   0 wz--n- 39.76g 20.02g
</code></pre><p>Similarly, resize the logical volume:</p><pre><code class="language-bash">root@Ubuntu14:~# lvextend -l +100%FREE /dev/ubuntu14-vg/root
  Extending logical volume root to 38.76 GiB
  Logical volume root successfully resized
</code></pre><p>Lastly, resize the filesystem:</p><pre><code class="language-bash">root@Ubuntu14:~# resize2fs /dev/ubuntu14-vg/root
resize2fs 1.42.9 (4-Feb-2014)
Filesystem at /dev/ubuntu14-vg/root is mounted on /; on-line resizing required
old_desc_blocks = 2, new_desc_blocks = 3
The filesystem on /dev/ubuntu14-vg/root is now 10161152 blocks long.
</code></pre><p>Verify the disk status and see that LVM is now resized properly:</p><pre><code class="language-bash">root@Ubuntu14:~# df -hT
Filesystem     Type      Size  Used Avail Use% Mounted on
udev           devtmpfs  989M  4.0K  989M   1% /dev
tmpfs          tmpfs     201M  716K  200M   1% /run
/dev/dm-0      ext4       39G  1.5G   35G   4% /
none           tmpfs     4.0K     0  4.0K   0% /sys/fs/cgroup
none           tmpfs     5.0M     0  5.0M   0% /run/lock
none           tmpfs    1001M     0 1001M   0% /run/shm
none           tmpfs     100M     0  100M   0% /run/user
/dev/sda1      ext2      236M   40M  184M  18% /boot
</code></pre><p>That's all! You have successfully resized the LVM partition inside an extended partition.</p><p>Questions or suggestion? Leave a comment below.</p>]]></content:encoded></item><item><title><![CDATA[RHCE Ansible Series #9: Ansible Roles]]></title><description><![CDATA[This is the ninth chapter of RHCE Ansible EX 294 exam preparation series. You'll understand how roles are structured in Ansible. You'll also learn to use ready-made roles from Ansible Galaxy and create your own custom Ansible roles.]]></description><link>https://linuxhandbook.com/ansible-roles/</link><guid isPermaLink="false">5fbb41a78ad9e10001e097dc</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[Ahmed Alkabary]]></dc:creator><pubDate>Tue, 24 Nov 2020 04:34:53 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/ansible-roles.png" medium="image"/><content:encoded/></item><item><title><![CDATA[How to SSH into a Docker Container]]></title><description><![CDATA[You can easily enter docker container but if you want to access it via SSH directly, here's how to configure SSH access to a container.]]></description><link>https://linuxhandbook.com/ssh-into-container/</link><guid isPermaLink="false">5fb4f64d432fb2000126178a</guid><category><![CDATA[SSH]]></category><category><![CDATA[Tips]]></category><dc:creator><![CDATA[Debdut Chakraborty]]></dc:creator><pubDate>Thu, 19 Nov 2020 06:12:29 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/ssh-into-container-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/ssh-into-container-1.png" alt="How to SSH into a Docker Container"><p>How do you use SSH to enter a Docker container? The traditional approach consists of two steps:</p><p><strong>Step 1</strong>: SSH into your remote Linux server (if you are running the container in a remote system).‌</p><pre><code>ssh user_name@server_ip_address</code></pre><p><strong>Step 2</strong>: And then you <a href="https://linuxhandbook.com/run-docker-container/">enter the shell of your running Docker container in interactive mode</a> like this:</p><pre><code>docker exec -it container_ID_or_name /bin/bash</code></pre><p>With that, you can run Linux command or do some maintenance of the service running inside the container.</p><p><strong><em>There is nothing wrong with the above method. This is the traditional and recommended way of effortlessly entering containers.</em></strong></p><p>However, with some efforts, you can actually SSH into a running container directly, without logging into the host system first.</p><h2 id="ssh-into-a-docker-container-but-why">SSH into a Docker container: But why?</h2><p>This is kind of weird, isn't it? Logging into a container, through SSH. Though it sounds non-traditional, it <em>might</em> still be useful to you, according to your use cases.</p><p>Here are a few things you can achieve with the ability to SSH into a container:</p><ol><li>You can set up a <em>fake</em> host for any potential attacker. By using a non-standard port for your host's SSH daemon, and serving an SSH connection at port 22 for the attackers.</li><li>A totally separate authorization level, i.e. password logins or different ssh keys all up to you and separate from whatever your host is currently using.</li><li>Running any automated remote process, without using the same ssh keys that are used to log in by your team's individuals.</li></ol><p>Before I show you how to do all the above things, I'll walk you through the idea of how this actually works.</p><blockquote>Using ssh login for existing container' is not recommended. That kills the whole point of host isolation.</blockquote><h2 id="setting-up-ssh-access-for-docker-containers-intermediate-to-expert-">Setting up SSH access for Docker containers [Intermediate to Expert]</h2><p>If you're not interested in the workings of this, you can safely ignore this section. I am going to show you with a dummy container. You may follow the steps to practice.</p><h3 id="run-a-container">Run a container</h3><p>First, you need to start a Docker container. I'll use the extremely small <code>alpine:latest</code> image for now. Start the container with this command:</p><pre><code>docker run --rm --name ssh-test -it -p 22:7655 alpine:latest ash </code></pre><p>Some noticeable points regarding the command line options are as follows</p><ul><li>With the <code>--rm</code> option, you don't have to explicitly <a href="https://linuxhandbook.com/remove-docker-containers/">remove the container</a> afterwards.</li><li>The <code>-it</code> options are there so that you can have a working, interactive shell of the container.</li><li>Finally, you are binding the container's port 22 to the host's port number 7655 (or any other port number that is not already used by SSH daemon on your host system). Keep in mind which port you are using.</li></ul><h3 id="set-up-the-ssh-daemon-in-the-container">Set up the SSH daemon in the container</h3><p>Now you need to install the ssh server inside the container. In <a href="https://www.alpinelinux.org">Alpine Linux</a>, you can use these commands:‌</p><pre><code>apk update; apk add openssh-server</code></pre><p>Next, you need to change a configuration parameter quickly to allow root logins. You can do it by manually editing the /etc/ssh/sshd_config file or using this command:</p><pre><code>sed -E 's/^#(PermitRootLogin )no/\1yes/' /etc/ssh/sshd_config -i </code></pre><p>Generate the host keys with:</p><pre><code>ssh-keygen -A</code></pre><p>Finally, start the ssh server, run <code>/usr/sbin/sshd &amp;</code>. Check if it's running by <code>ps aux</code>.</p><h3 id="set-a-password-for-your-container-s-root-account">Set a password for your container's root account</h3><p>By default, your container's root account doesn't have a password. If you are opening SSH access to it, you must set the password for the root account.</p><p>You can <a href="https://linuxhandbook.com/passwd-command/">use the passwd command</a> without any option and follow the instructions on the screen:</p><pre><code>passwd</code></pre><h3 id="log-into-the-container-via-ssh">Log into the container via SSH</h3><p>From another host, try to log into the container now. </p><pre><code>ssh root@IP_address_of_host_server -p port_number</code></pre><p>You don't need the <code>-p</code> option if you bound to port 22 previously. For IP use the host server's IP address (not the container's).</p><p>When you run the command, you should see an output similar to this:</p><pre><code>debdut@shinchan:/mnt/data/documents/Linux Handbook/container-ssh$ ssh root@domain.com
   root@domain.com's password: 
   Welcome to Alpine!
   
   The Alpine Wiki contains a large amount of how-to guides and general
   information about administrating Alpine systems.
   See &lt;http://wiki.alpinelinux.org/&gt;.
   
   You can setup the system with the command: setup-alpine
   
   You may change this message by editing /etc/motd.
   
   c4585d951883:~#</code></pre><h3 id="how-does-this-work">How does this work?</h3><p>This can be understood better visually. Take a look at the following diagram:‌</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/ssh-into-container.png" class="kg-image" alt="How to SSH into a Docker Container" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/ssh-into-container.png 600w, https://linuxhandbook.com/content/images/2020/11/ssh-into-container.png 800w" sizes="(min-width: 720px) 720px"></figure><p>Think of the containers as a virtual machine whose port 22 is glued together with the host's port 7655 (or the one you chose). This enables you to have two different ssh processes running on the same machine bound to different ports.</p><p>Let's say you use some other port for SSH on the host system and glue the port 22 with the container's port. Now, if somebody tries to connect to the host server using SSH default port 22, he/she will be inside the container's root file system.</p><h2 id="setting-up-ssh-for-containers-using-docker-compose-experts-">Setting up SSH for containers using Docker Compose [Experts]</h2><p>It wouldn't be fair if I leave you at this spot without providing some reliable option for an SSH server container.</p><p>If you wanted to take advantage of having a different, isolated ssh server with a separate root file system running on your remote system, that could be done, but not by following the previous walk-through, i.e. installing and configuring sshd on a running base container. </p><p>Simply because it's not easily reproducible, every change you make on the running container, are not persistent, a container restart and everything is gone. </p><p>So, here I give you a much simpler, easily reproducible, configurable way of deploying an SSH server container on your remote host.</p><h3 id="prerequisites">Prerequisites</h3><p>You need to have docker compose installed obviously. Basic knowledge of docker compose is a must here.</p><p>Since you will be accessing the server via SSH keys, you need to <a href="https://linuxhandbook.com/add-ssh-public-key-to-server/">add the public SSH key of your local system to your host Linux server's</a> directory where docker-compose file is located and keep the name "id_rsa.pub" just to be sure.</p><h3 id="prepare-the-compose-file">Prepare the compose file</h3><p>I suggest using the <code>linuxserver/openssh-server</code> image. This is a very lightweight image with good enough configuration options through environment variables.</p><p>Here the whole compose file is going to be pasted. Copy this over to some location on your server and name the file <code>docker-compose.yaml</code>.‌</p><pre><code>version: "3.7"

services:
    ssh:
        image: "linuxserver/openssh-server"
        ports:
            - "22:2222"
        volumes:
            - "./id_rsa.pub:/pubkey:ro"
        environment:
            PUID: ${ID}
            PGID: ${ID}
            TZ: ${TZ}
            PUBLIC_KEY_FILE: "/pubkey"
            SUDO_ACCESS: "false"
            PASSWORD_ACCESS: "false"
            USER_NAME: ${USER_NAME}
        restart: "always"</code></pre><p>Quite a small compose file, isn't it? Let me explain different parts of this compose file.</p><p><strong>Volumes: </strong>You only have a single bind mount, which mounts the public key within the container as <code>pubkey</code>. It's also read only.</p><p><strong>Ports: </strong>The sshd process inside the container runs on port 2222. That's why I have bound that port to my host's port 22. Change 22 according to your needs but remember that as you'll need it to log into the container through SSH later.</p><p><strong>Environment Variables:</strong></p><ul><li>USER_NAME :The in-container user, you'll be logging in as from your local machine.</li><li>PUID &amp; PGID: USER_NAME's UID and GID. This is optional, the container will assign a pair of non-root IDs automatically if the environment variables are not set.</li><li>TZ: Your current time zone. You can get that by <code>cat /etc/timezone</code>.</li><li>PUBLIC_KEY_FILE: The location of the public key file</li><li>SUDO_ACCESS &amp; PASSWORD_ACCESS: Self explanatory.</li></ul><p><strong>Restart policy: </strong>I have set the "always" restart policy which will restart the container even if the daemon is reloaded.</p><h3 id="deploy-the-service">Deploy the service</h3><p>To make it easy for you, I have made a Bash script that will ask you a couple of questions, and based on the answer it'll deploy the service.</p><pre><code>#! /usr/bin/env bash

vars=("ID" "USER_NAME")
defaults=("991" "dummy")
questions=("What'd be your preferred UID &amp; GID for the username of your choice? [default] " "Your preferred username for the container? [dummy] ")

put()
{
    echo "$1" &gt;&gt; .env
}

for i in {1..0}; do
    read -p "${questions[$i]}" ans
    case $ans in
        ""|"default")
            put "${vars[$i]}=${defaults[$i]}" ;;
        *)
            put "${vars[$i]}=$ans" ;;
    esac
done

put "TZ=$(cat /etc/timezone)"

docker-compose up -d</code></pre><p>I saved the bash script as deploy.sh in the same directory where docker-compose file was located. </p><p>Now, if you run this script, it will ask you some questions and then run the docker container:</p><pre><code>bash deploy.sh</code></pre><p>Once it's done, try logging into the server:</p><pre><code>ssh user@ip -p port </code></pre><p>That concludes this article on ssh with docker containers. If you liked it, or have anything else to mention, feel free to comment down below or tweet to me <a href="https://twitter.com/imdebdut">@imdebdut</a>. </p><p>If you'd like me to write some other article feel free to let me know.</p>]]></content:encoded></item><item><title><![CDATA[RHCE Ansible Series #8: Encrypting Content With Ansible Vault]]></title><description><![CDATA[This is the eighth chapter of RHCE Ansible EX 294 exam preparation series. In this tutorial, you'll learn about securing sensitive information with Ansible Vault. The chapter will be available to non-members after a week.]]></description><link>https://linuxhandbook.com/ansible-vault/</link><guid isPermaLink="false">5fb36f5f432fb20001261630</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[Ahmed Alkabary]]></dc:creator><pubDate>Tue, 17 Nov 2020 07:26:22 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/ansible-vault.png" medium="image"/><content:encoded/></item><item><title><![CDATA[Using Bash printf Command for Printing Formatted Outputs]]></title><description><![CDATA[You may print simple outputs with echo command but that's not enough for complicated formatted outputs.]]></description><link>https://linuxhandbook.com/bash-printf/</link><guid isPermaLink="false">5fb246e9432fb2000126145c</guid><category><![CDATA[Bash]]></category><dc:creator><![CDATA[Abhishek Prakash]]></dc:creator><pubDate>Mon, 16 Nov 2020 13:55:03 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/printf-command-in-bash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/printf-command-in-bash.jpg" alt="Using Bash printf Command for Printing Formatted Outputs"><p>The simplest way to <a href="https://linuxhandbook.com/echo-command/">print in Linux command line is by using echo command</a>.</p><pre><code>echo "Value of var is $var"</code></pre><p>However, echo command won't be adequate when you need to print formatted output.</p><p>This is where printf command helps you. The bash printf command operates like the printf command in C/<a href="https://www.cplusplus.com/doc/tutorial/">C++ programming language</a>.</p><pre><code>printf "My brother %s is %d years old.\n" Prakash 21</code></pre><p>Can you guess the output?</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/bash-printf-command-example.png" class="kg-image" alt="Using Bash printf Command for Printing Formatted Outputs" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/bash-printf-command-example.png 600w, https://linuxhandbook.com/content/images/2020/11/bash-printf-command-example.png 732w" sizes="(min-width: 720px) 720px"></figure><p>The first argument <code>%s</code> expects a string, <code>%d</code> expects a decimal integer, just like C/C++. Don't worry if you are not familiar with C/C++. </p><p>You don't need to learn it just for using printf command in your Bash scripts. Let me show you some examples of Bash printf command.</p><h2 id="using-bash-printf-command">Using Bash printf command</h2><p>Let's start with the syntax first.</p><pre><code>printf format [arguments]</code></pre><p>Here, <code>format</code> is a string that determines how the subsequent values will be displayed.</p><p>In the example <code>printf "My brother %s is %d years old.\n" Prakash 21</code>, the entire "My brother %s is %d years old.\n" is format and it is followed by the arguments <code>Prakash</code> and <code>21</code>. The arguments are used for replacing the format specifiers (%s, %d etc) which I'll explain later in this article.</p><p>In its simplest form, printf can be used as echo command for displaying a string. </p><pre><code>printf "Hello World\n"</code></pre><p>Notice the new line character <code>\n</code> at the end? The <strong><em>difference between echo and printf command</em></strong> is that echo automatically adds a new line character at the end but for printf, you have to explicitly add it.</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/echo-and-printf-command-outputs.png" class="kg-image" alt="Using Bash printf Command for Printing Formatted Outputs" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/echo-and-printf-command-outputs.png 600w, https://linuxhandbook.com/content/images/2020/11/echo-and-printf-command-outputs.png 723w" sizes="(min-width: 720px) 720px"></figure><h3 id="pay-special-attention-to-type-and-number-of-arguments">Pay special attention to type and number of arguments</h3><p>Keep in mind that the format string tries to be applied to all the arguments. This is why you should pay special attention to it.</p><pre><code>abhishek@handbook:~$ printf "Hello, %s! \n" Abhishek Prakash
Hello, Abhishek! 
Hello, Prakash!</code></pre><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/bash-printf-string-format.png" class="kg-image" alt="Using Bash printf Command for Printing Formatted Outputs" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/bash-printf-string-format.png 600w, https://linuxhandbook.com/content/images/2020/11/bash-printf-string-format.png 723w" sizes="(min-width: 720px) 720px"></figure><p>Similarly, you should also pay attention to the kind of format specifier it expects in the format string.</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/printf-command-examples.png" class="kg-image" alt="Using Bash printf Command for Printing Formatted Outputs" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/printf-command-examples.png 600w, https://linuxhandbook.com/content/images/2020/11/printf-command-examples.png 840w" sizes="(min-width: 720px) 720px"></figure><p>As you can see in the above example, if it doesn't find intended arguments, it displays the default values which is null for strings and 0 for integers.</p><pre><code>printf "Hi %s, your room number is %d. \n" Abhishek Prakash 131
bash: printf: Prakash: invalid number
Hi Abhishek, your room number is 0. 
Hi 131, your room number is 0.</code></pre><p>Here, it takes <code>Abhishek Prakash</code> as first set of arguments and <code>131</code> as second set of arguments.</p><p>When it finds a string (Prakash) instead of an integer, it complains but it continues to display the output with second argument at its default value 0.</p><p>Similarly, it sees 131 as string in the second set of arguments and since the second argument is absent, it defaults to 0.</p><h3 id="format-specification-characters">Format specification characters</h3><p>There are several format specifiers available for you to display your output in desired format. Here are some of the most common ones:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Character</th>
<th>Usage</th>
</tr>
</thead>
<tbody>
<tr>
<td>%s</td>
<td>String</td>
</tr>
<tr>
<td>%c</td>
<td>Single character</td>
</tr>
<tr>
<td>%d</td>
<td>Integers</td>
</tr>
<tr>
<td>%o</td>
<td>Octal integers</td>
</tr>
<tr>
<td>%x</td>
<td>Hexadecimal integers</td>
</tr>
<tr>
<td>%f</td>
<td>Floating point</td>
</tr>
<tr>
<td>%b</td>
<td>String with backslash escape character</td>
</tr>
<tr>
<td>%%</td>
<td>Percent sign</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Now that you are familiar with the format specifier, let's see some more examples that shows them in action.</p><h2 id="bash-printf-command-examples">Bash printf command examples</h2><p>I don't think you need detailed explanation for most of these examples as they are self-explanatory.</p><pre><code>abhishek@handbook:~$ printf "The octal value of %d is %o\n" 30 30
The octal value of 30 is 36</code></pre><p>Let's see the use of the <code>%b</code> specifier for correctly interpreting the backslash escaped character.</p><pre><code>abhishek@handbook:~$ printf "String with backslash: %s\n" "Hello\nWorld!"
String with backslash: Hello\nWorld!</code></pre><p>You see for <code>%s</code>, it makes no difference. But with <code>%b</code> the new line escape character will be correctly interpreted:</p><pre><code>abhishek@handbook:~$ printf "String with backslash: %b\n" "Hello\nWorld!"
String with backslash: Hello
World!</code></pre><p>When you use the <code>%c</code>, it reads only character at a time. </p><pre><code>abhishek@handbook:~$ printf "Character: %c\n" a
Character: a
abhishek@handbook:~$ printf "Character: %c\n" a b c
Character: a
Character: b
Character: c
abhishek@handbook:~$ printf "Character: %c\n" abc
Character: a
</code></pre><h3 id="using-modifiers-to-display-printf-output-in-specific-style">Using modifiers to display printf output in specific style</h3><p>There are modifiers to change the look of the output as per your liking.</p><h3 id="-modifier-for-octal-and-hexadecimal-numbers"># modifier for octal and hexadecimal numbers</h3><p>Earlier you saw the use of <code>%o</code> for converting decimal to octal. However, it wasn't very clear that it was an octal number. You can use the <code>#</code> modifier to display octal and hexadecimal numbers in proper format.</p><pre><code>abhishek@handbook:~$ printf "%d is %#o in octal and %#x in hexadecimal\n" 30 30 30
30 is 036 in octal and 0x1e in hexadecimal</code></pre><h3 id="space-modifier-for-positive-integers">Space modifier for positive integers</h3><p>You can use a space between <code>%</code> and <code>d</code> in <code>%d</code> to display a positive integer with a leading space. This helps when you have a column of positive and negative numbers. See which one looks more 'pretty':</p><pre><code>abhishek@handbook:~$ printf "%d \n%d \n%d \n" 10 -10 10
10 
-10 
10 
abhishek@handbook:~$ printf "% d \n%d \n% d \n" 10 -10 10
 10 
-10 
 10 
</code></pre><h3 id="width-modifier">Width modifier</h3><p>The width modifier is a integer that specifies the minimum field width of the argument.</p><p>By default, it is right aligned:</p><pre><code>abhishek@handbook:~$ printf "%10s| %5d\n" Age 23
       Age|    23
</code></pre><p>You can make it left aligned by adding <code>-</code> flag to it:</p><pre><code>abhishek@handbook:~$ printf "%-10s| %-5d\n" Age 23
Age       | 23</code></pre><h3 id="precision-modifier">Precision modifier</h3><p>You can use the precision modifier (.) dot to specify a minimum number of digits to be displayed with <code>%d</code>, <code>%u</code>, <code>%o</code>, <code>%x</code>. It adds zero padding on the left of the value.</p><pre><code>abhishek@handbook:~$ printf "Roll Number: %.5d\n" 23
Roll Number: 00023</code></pre><p>If you use the precision modifier with a string, it specifies the maximum length of the string. If the string is longer, it gets truncated in the display.</p><pre><code>abhishek@handbook:~$ printf "Name: %.4s\n" Abhishek
Name: Abhi
</code></pre><p>You may combine both width and precision modifier:</p><pre><code>abhishek@handbook:~$ printf "Name: %.4s\n" Abhishek
Name: Abhi
abhishek@handbook:~$ printf "Name: %10.4s\n" Abhishek
Name:       Abhi</code></pre><h2 id="bonus-example-display-output-in-tabular-format-with-printf">Bonus Example: Display output in tabular format with printf</h2><p>Let's put what you have learned about printf command to some good use with a slightly more complicated example. This will showcase the true potential of the printf command in bash scripts.</p><p>Let's use printf command to print the following table using bash:</p><!--kg-card-begin: markdown--><table>
<thead>
<tr>
<th>Name</th>
<th>ID</th>
<th>Age</th>
<th>Grades</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sherlock Holmes</td>
<td>0000122</td>
<td>23</td>
<td>A</td>
</tr>
<tr>
<td>James Bond</td>
<td>0000007</td>
<td>27</td>
<td>F</td>
</tr>
<tr>
<td>Hercules Poirot</td>
<td>0006811</td>
<td>59</td>
<td>G</td>
</tr>
<tr>
<td>Jane Marple</td>
<td>1234567</td>
<td>71</td>
<td>C</td>
</tr>
</tbody>
</table>
<!--kg-card-end: markdown--><p>Here's the shell script I wrote for this task. Your script may vary:</p><pre><code>#/bin/bash
seperator=--------------------
seperator=$seperator$seperator
rows="%-15s| %.7d| %3d| %c\n"
TableWidth=37

printf "%-15s| %-7s| %.3s| %s\n" Name ID Age Grades
printf "%.${TableWidth}s\n" "$seperator"
printf "$rows" "Sherlock Holmes" 122 23 A
printf "$rows" "James Bond" 7 27 F
printf "$rows" "Hercules Poirot" 6811 59 G
printf "$rows" "Jane Marple" 1234567 71 C</code></pre><p>Now <a href="https://linuxhandbook.com/run-shell-script/">if I run this bash shell script</a>, here's what it prints:</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/bash-printf-table-output.png" class="kg-image" alt="Using Bash printf Command for Printing Formatted Outputs" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/bash-printf-table-output.png 600w, https://linuxhandbook.com/content/images/2020/11/bash-printf-table-output.png 669w"></figure><p>Isn't it wonderful to use the bash printf command to print beautifully formatted output for your scripts?</p><p>I hope you liked this detailed tutorial on the printf command in Linux. If you have questions or suggestions, please let me know. And don't forget to become a member of Linux Handbook for more such informational Linux learnings.</p>]]></content:encoded></item><item><title><![CDATA[RHCE Ansible Series #7: Jinja2 Templates]]></title><description><![CDATA[Learn how to use Jinja2 templating engine to carry out more involved and dynamic file modifications with Ansible.]]></description><link>https://linuxhandbook.com/jinja2-templates/</link><guid isPermaLink="false">5fadff17f43da7000127401b</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[Ahmed Alkabary]]></dc:creator><pubDate>Fri, 13 Nov 2020 04:36:04 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/Jinja2-Templates-Ansible.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/Jinja2-Templates-Ansible.jpg" alt="RHCE Ansible Series #7: Jinja2 Templates"><p>In the <a href="https://linuxhandbook.com/decision-making-ansible/">previous tutorial about decision making in Ansible</a>, you learned how to do simple file modifications by using the <strong>blockinfile </strong>or <strong>inline </strong>Ansible modules. </p><p>In this tutorial, you will learn how to use <a href="https://palletsprojects.com/p/jinja/"><strong>Jinja2 </strong>templating engine</a> to carry out more involved and dynamic file modifications. </p><p>You will learn how to access variables and facts in Jinja2 templates. Furthermore, you will learn how to use conditional statements and loop structures in Jinja2.</p><blockquote>To try the examples in this tutorial, you should <a href="https://linuxhandbook.com/tag/ansible/">follow the entire RHCE Ansible tutorial series</a> in the correct order.</blockquote><h2 id="accessing-variables-in-jinja2">Accessing Variables in Jinja2</h2><p>Ansible will look for jinja2 template files in your project directory or in a directory named <strong>templates </strong>under your project directory.</p><p>Let’s create a templates directory to keep thing cleaner and more organized:</p><pre><code>[elliot@control plays]$ mkdir templates
[elliot@control plays]$ cd templates/</code></pre><p>Now create your first Jinja2 template with the name <strong>index.j2</strong>:</p><pre><code>[elliot@control templates]$ cat index.j2 
A message from {{ inventory_hostname }}
{{ webserver_message }}</code></pre><p><em><strong>Notice that Jinja2 template filenames must end with the .j2 extension.</strong></em></p><p>The <strong>inventory_hostname </strong>is another Ansible built-in (aka special or magic) variable that references that ‘current’ host being iterated over in the play. The <strong>webserver_message </strong>is a variable that you will define in your playbook.</p><p>Now go one step back to your project directory and create the following <strong>check-apache.yml</strong>:</p><pre><code>[elliot@control plays]$ cat check-apache.yml 
---
- name: Check if Apache is Working
  hosts: webservers
  vars:
    webserver_message: "I am running to the finish line."
  tasks:
    - name: Start httpd
      service:
        name: httpd
        state: started

    - name: Create index.html using Jinja2
      template:
        src: index.j2
        dest: /var/www/html/index.html</code></pre><p>Note that the <strong>httpd </strong>package was already installed in a previous tutorial.</p><p>In this playbook, you first make sure Apache is running in the first task <code>Start httpd</code>. Then use the <strong>template </strong>module in the second task <code>Create index.html</code> using Jinja2to process and transfer the <strong>index.j2 </strong>Jinja2 template file you created to the destination <strong>/var/www/html/index.html</strong>.</p><p>Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook check-apache.yml 

PLAY [Check if Apache is Working] **********************************************

TASK [Gathering Facts] *********************************************************
ok: [node3]
ok: [node2]

TASK [Start httpd] *************************************************************
ok: [node2]
ok: [node3]

TASK [Create index.html using Jinja2] ******************************************
changed: [node3]
changed: [node2]

PLAY RECAP *********************************************************************
node2                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0</code></pre><p>Everything looks good so far; let’s run a quick <a href="https://linuxhandbook.com/ansible-ad-hoc/">ad-hoc Ansible command</a> to check the contents of <strong>index.html </strong>on the webservers nodes:</p><pre><code>[elliot@control plays]$ ansible webservers -m command -a "cat /var/www/html/index.html"
node3 | CHANGED | rc=0 &gt;&gt;
A message from node3
I am running to the finish line.
node2 | CHANGED | rc=0 &gt;&gt;
A message from node2
I am running to the finish line.</code></pre><p>Amazing! Notice how Jinja2 was able to pick up the values of the <strong>inventory_hostname </strong>built-in variable and the <strong>webserver_message </strong>variable in your playbook.</p><p>You can also <a href="https://linuxhandbook.com/curl-command-examples/">use the curl<strong> </strong>command to see</a> if you get a response from both webservers:</p><pre><code>[elliot@control plays]$ curl node2.linuxhandbook.local
A message from node2
I am running to the finish line.
[elliot@control plays]$ curl node3.linuxhandbook.local
A message from node3
I am running to the finish line.</code></pre><h2 id="accessing-facts-in-jinja2">Accessing Facts in Jinja2</h2><p>You can access facts in Jinja2 templates the same way you access facts from your playbook.</p><p>To demonstrate, change to your <strong>templates </strong>directory and create the <strong>info.j2 </strong>Jinja2 file with the following contents:</p><pre><code>[elliot@control templates]$ cat info.j2 
Server Information Summary
--------------------------

hostname={{ ansible_facts['hostname'] }}
fqdn={{ ansible_facts['fqdn'] }}
ipaddr={{ ansible_facts['default_ipv4']['address'] }}
distro={{ ansible_facts['distribution'] }}
distro_version={{ ansible_facts['distribution_version'] }}
nameservers={{ ansible_facts['dns']['nameservers'] }}
totalmem={{ ansible_facts['memtotal_mb'] }}
freemem={{ ansible_facts['memfree_mb'] }}</code></pre><p>Notice that <strong>info.j2 </strong>accesses eight different facts. Now go back to your project directory and create the following <strong>server-info.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat server-info.yml 
---
- name: Server Information Summary
  hosts: all
  tasks:
   - name: Create server-info.txt using Jinja2
     template:
       src: info.j2
       dest: /tmp/server-info.txt</code></pre><p>Notice that you are creating <strong>/tmp/server-info.txt </strong>on all hosts based on the <strong>info.j2 </strong>template file. Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook server-info.yml 

PLAY [Server Information Summary] *******************************************

TASK [Gathering Facts] **********************************
ok: [node4]
ok: [node1]
ok: [node3]
ok: [node2]

TASK [Create server-info.txt using Jinja2] ********
changed: [node4]
changed: [node1]
changed: [node3]
changed: [node2]

PLAY RECAP *************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node4                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0</code></pre><p>Everything looks good! Now let’s run a quick ad-hoc command to inspect the contents of the <strong>/tmp/server-info.txt </strong>file on one of the nodes:</p><pre><code>[elliot@control plays]$ ansible node1 -m command -a "cat /tmp/server-info.txt"
node1 | CHANGED | rc=0 &gt;&gt;
Server Information Summary
--------------------------

hostname=node1
fqdn=node1.linuxhandbook.local
ipaddr=10.0.0.5
distro=CentOS
distro_version=8.2
nameservers=['168.63.129.16']
totalmem=1896
freemem=1087</code></pre><p>As you can see, Jinja2 was able to access and process all the facts.</p><h2 id="conditional-statements-in-jinja2">Conditional statements in Jinja2</h2><p>You can use the <strong>if </strong>conditional statement in Jinja2 for testing various conditions and comparing variables. This allows you to determine your file template execution flow according to your test conditions.</p><p>To demonstrate, go to your <strong>templates </strong>directory and create the following <strong>selinux.j2 </strong>template:</p><pre><code>[elliot@control templates]$ cat selinux.j2 
{% set selinux_status = ansible_facts['selinux']['status'] %}

{% if selinux_status == "enabled" %}
	"SELINUX IS ENABLED"
{% elif selinux_status == "disabled" %}
	"SELINUX IS DISABLED"
{% else %}
	"SELINUX IS NOT AVAILABLE"
{% endif %}</code></pre><p>The first statement in the template creates a new variable <code>selinux_statusand</code> set its value to <code>ansible_facts['selinux']['status']</code>.</p><p>You then use <code>selinux_status</code> in your <strong>if </strong>test condition to <a href="https://linuxhandbook.com/selinux/">determine whether <strong>SELinux </strong>is enabled, disabled, or not installed</a>. In each of the three different cases, you display a message that reflects Selinux status.</p><p>Notice how the <strong>if </strong>statement in Jinja2 mimics Python’s <strong>if </strong>statement; just don’t forget to use <code>{% endif %}</code>.</p><p>Now go back to your project directory and create the following <strong>selinux-status.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat selinux-status.yml 
---
- name: Check SELinux Status
  hosts: all
  tasks:
    - name: Display SELinux Status
      debug:
        msg: "{{ ansible_facts['selinux']['status'] }}"

    - name: Create selinux.out using Jinja2
      template:
        src: selinux.j2
        dest: /tmp/selinux.out</code></pre><p>Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook selinux-status.yml 

PLAY [Check SELinux Status] ****************************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node2]
ok: [node3]
ok: [node1]

TASK [Display SELinux Status] **************************************************
ok: [node1] =&gt; {
    "msg": "enabled"
}
ok: [node2] =&gt; {
    "msg": "disabled"
}
ok: [node3] =&gt; {
    "msg": "enabled"
}
ok: [node4] =&gt; {
    "msg": "Missing selinux Python library"
}

TASK [Create selinux.out using Jinja2] *****************************************
changed: [node4]
changed: [node1]
changed: [node3]
changed: [node2]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    node4                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0  </code></pre><p>From the playbook output; you can see that SELinux is enabled on both <strong>node1 </strong>and <strong>node3</strong>. I disabled SELinux on <strong>node2 </strong>before running the playbook and <strong>node4 </strong>doesn’t have SELinux installed because Ubuntu uses AppArmor instead of SELinux.</p><p>Finally, you can run the following ad-hoc command to inspect the contents of <strong>selinux.out </strong>on all the managed hosts:</p><pre><code>[elliot@control plays]$ ansible all -m command -a "cat /tmp/selinux.out"
node4 | CHANGED | rc=0 &gt;&gt;

	"SELINUX IS NOT AVAILABLE"
 
node2 | CHANGED | rc=0 &gt;&gt;

	"SELINUX IS DISABLED"
 
node3 | CHANGED | rc=0 &gt;&gt;

	"SELINUX IS ENABLED"
 
node1 | CHANGED | rc=0 &gt;&gt;

	"SELINUX IS ENABLED"</code></pre><h2 id="looping-in-jinja2">Looping in Jinja2</h2><p>You can use the <strong>for </strong>statement in Jinja2 to loop over items in a list, range, etc. For example, the following for loop will iterate over the numbers in the <strong>range(1,11)</strong>and will hence display the numbers from 1-&gt;10:</p><pre><code>{% for i in range(1,11) %}
	Number {{ i }}
{% endfor %}</code></pre><p>Notice how the for loop in Jinja2 mimics the syntax of Python’s for loop; again don’t forget to end the loop with <code>{% endfor %}</code>.</p><p>Now let’s create a full example that shows off the power of for loops in Jinja2. Change to your templates directory and create the following <strong>hosts.j2 </strong>template file:</p><pre><code>[elliot@control templates]$ cat hosts.j2 
{% for host in groups['all'] %}
{{ hostvars[host].ansible_facts.default_ipv4.address }}  {{ hostvars[host].ansible_facts.fqdn }}  {{ hostvars[host].ansible_facts.hostname }}
{% endfor %}</code></pre><p>Notice here you used a new built-in special (magic) variable <strong>hostvars </strong>which is basically a dictionary that contains all the hosts in inventory and variables assigned to them.</p><p>You iterated over all the hosts in your inventory and then for each host; you displayed the value of three variables:</p><ol><li>{{ hostvars[host].ansible_facts.default_ipv4.address }}</li><li>{{ hostvars[host].ansible_facts.fqdn }}</li><li>{{ hostvars[host].ansible_facts.hostname }}</li></ol><p>Notice also that you must include those three variables on the same line side by side to match the format of the <strong>/etc/hosts </strong>file.</p><p>Now go back to your projects directory and create the following <strong>local-dns.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat local-dns.yml 
---
- name: Dynamically Update /etc/hosts File
  hosts: all
  tasks:
    - name: Update /etc/hosts using Jinja2
      template:
        src: hosts.j2
        dest: /etc/hosts</code></pre><p>Then go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook local-dns.yml 

PLAY [Dynamically Update /etc/hosts File] *********************************************

TASK [Gathering Facts] ***************************
ok: [node4]
ok: [node2]
ok: [node1]
ok: [node3]

TASK [Update /etc/hosts using Jinja2] ***********************************************
changed: [node4]
changed: [node3]
changed: [node1]
changed: [node2]

PLAY RECAP **********************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    
node4                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0 </code></pre><p>Everything looks good so far; now run the following ad-hoc command to verify that <strong>/etc/hosts </strong>file is properly updated on <strong>node1</strong>:</p><pre><code>[elliot@control plays]$ ansible node1 -m command -a "cat /etc/hosts"
node1 | CHANGED | rc=0 &gt;&gt;
10.0.0.5  node1.linuxhandbook.local  node1
10.0.0.6  node2.linuxhandbook.local  node2
10.0.0.7  node3.linuxhandbook.local  node3
10.0.0.8  node4.linuxhandbook.local  node4</code></pre><p>Perfect! Looks properly formatted as you expected it to be.</p><p>I hope you now realize the power of Jinja2 templates in Ansible. Stay tuned for next tutorial where you will learn to protect sensitive information and files using Ansible Vault.</p>]]></content:encoded></item><item><title><![CDATA[How to Count Number of Files in a Directory in Linux]]></title><description><![CDATA[Here are several ways to count the number of files in a given directory in Linux.]]></description><link>https://linuxhandbook.com/count-number-files/</link><guid isPermaLink="false">5fa55adbf43da70001273a7c</guid><category><![CDATA[Tips]]></category><dc:creator><![CDATA[Abhishek Prakash]]></dc:creator><pubDate>Thu, 12 Nov 2020 07:19:52 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/count-number-of-files-in-directories-in-linux.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/count-number-of-files-in-directories-in-linux.png" alt="How to Count Number of Files in a Directory in Linux"><p>How do you know how many files are there is a directory?</p><p>In this quick tutorial, you'll learn various ways to count the number of files in a directory in Linux.</p><h2 id="method-1-use-ls-and-wc-command-for-counting-number-of-lines-in-directory">Method 1: Use ls and wc command for counting number of lines in directory</h2><p>The simplest and the most obvious option is to <a href="https://linuxhandbook.com/wc-command/">use the wc command for counting number of files</a>.</p><pre><code>ls | wc -l</code></pre><p>The above command will count all the files and directories but not the hidden ones. You can use <code>-A</code> option with the ls command to list hidden files but leaving out . and .. directories:</p><pre><code>ls -A | wc -l</code></pre><p>If you only want to count number of files, including hidden files, in the current directory, you can combine a few commands like this:</p><pre><code>ls -Ap | grep -v /$ | wc -l</code></pre><p>Let me explain what it does:</p><ul><li><code>-p</code> with ls adds <code>/</code> at the end of the directory names.</li><li><code>-A</code> with ls lists all the files and directories, including hidden files but excluding . and .. directories.</li><li><code>grep -v /$</code> only shows the lines that do NOT match ( <code>-v</code> option) lines that end with <code>/</code>.</li><li><code>wc -l</code> counts the number of lines.</li></ul><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/count-number-of-files-in-linux.png" class="kg-image" alt="How to Count Number of Files in a Directory in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/count-number-of-files-in-linux.png 600w, https://linuxhandbook.com/content/images/2020/11/count-number-of-files-in-linux.png 836w" sizes="(min-width: 720px) 720px"></figure><p>Basically, you use <code>ls</code> to list display all the files and directories (with / added to directory names). You then <a href="https://linuxhandbook.com/pipe-redirection/">use pipe redirection to parse this output</a> to the grep command. The grep command only displays the lines that do not have / at the end. The wc command then counts all such lines.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://linuxhandbook.com/list-only-directories/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">List Only Directories in Linux With ls and Other Commands</div><div class="kg-bookmark-description">Listing the contents of a directory is easy. But what if you want to list only the directories, not files and links?</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://linuxhandbook.com/assets/icon-192x192.png?v&#x3D;09790e946a" alt="How to Count Number of Files in a Directory in Linux"><span class="kg-bookmark-author">Abhishek Prakash</span><span class="kg-bookmark-publisher">Linux Handbook</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://linuxhandbook.com/content/images/2020/11/list-only-directories-1.png" alt="How to Count Number of Files in a Directory in Linux"></div></a></figure><h2 id="method-2-use-tree-command-for-counting-number-of-files-in-directory">Method 2: Use tree command for counting number of files in directory</h2><p>You can use the tree command for displaying the number of files in the present directory and all of its subdirectories.</p><pre><code>tree -a</code></pre><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/get-number-of-files-in-directory-using-tree-command.png" class="kg-image" alt="How to Count Number of Files in a Directory in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/get-number-of-files-in-directory-using-tree-command.png 600w, https://linuxhandbook.com/content/images/2020/11/get-number-of-files-in-directory-using-tree-command.png 736w" sizes="(min-width: 720px) 720px"></figure><p>As you can see, the last line of the output shows the number of directories and files, including the hidden ones thanks to option <code>-a</code>.</p><p>If you want to get the number of files in current directory only, exclude the subdirectories, you can set the level to 1 like this:</p><pre><code>tree -a -L 1</code></pre><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/get-number-of-files-in-current-directory-using-tree-command.png" class="kg-image" alt="How to Count Number of Files in a Directory in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/get-number-of-files-in-current-directory-using-tree-command.png 600w, https://linuxhandbook.com/content/images/2020/11/get-number-of-files-in-current-directory-using-tree-command.png 736w" sizes="(min-width: 720px) 720px"></figure><h2 id="method-3-use-find-command-to-count-number-of-files-in-a-directory">Method 3: Use find command to count number of files in a directory</h2><p>The evergreen find command is quite useful when it comes with dealing with files.</p><p>If you want to count the number of files in a directory, use the find command to get all the files first and then count it using the wc command.</p><pre><code>find directory_path -type f | wc -l</code></pre><p>With <code>-type f</code> you tell the find command to only look for files.</p><p>If you don't want the files from the subdirectories, limit the scope of find command at level 1, i.e. current directory.</p><pre><code>find . -maxdepth 1 -type f | wc -l</code></pre><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/count-files-in-directory-with-find-command.png" class="kg-image" alt="How to Count Number of Files in a Directory in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/count-files-in-directory-with-find-command.png 600w, https://linuxhandbook.com/content/images/2020/11/count-files-in-directory-with-find-command.png 766w" sizes="(min-width: 720px) 720px"></figure><p>There could be some other ways to count the number of lines in a directory in Linux. It's up to you how you want to go about.</p><p>I hope you find this helpful. Feel free to leave a question or suggestion in the comment section.</p>]]></content:encoded></item><item><title><![CDATA[Terraform vs Ansible: What's the difference and which one you should use?]]></title><description><![CDATA[Preparing your Infrastructure as Code solution? Ansible and Terraform are two of the most popular choices. But what's the difference?]]></description><link>https://linuxhandbook.com/terraform-vs-ansible/</link><guid isPermaLink="false">5fab7c19f43da70001273e41</guid><category><![CDATA[DevOps]]></category><dc:creator><![CDATA[Rakesh Jain]]></dc:creator><pubDate>Wed, 11 Nov 2020 08:47:11 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/ansible-vs-terraform-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/ansible-vs-terraform-1.jpg" alt="Terraform vs Ansible: What's the difference and which one you should use?"><p>The way DevOps as a culture is gaining momentum, tools like Ansible and Terraform witnessing a huge demand and popularity.</p><p>Both tools are considered as <strong><a href="https://www.ibm.com/cloud/learn/infrastructure-as-code">Infrastructure as Code</a> (IaC)</strong> solutions which helps in deploying code and infrastructure. While Ansible acts as a configuration management solution commonly abbreviated as “CM”, Terraform is a service orchestration or provisioning tool.</p><p>Note that there are overlaps and these terms are not necessarily mutually exclusive. This is what confuses people and this is why I am going to compare Ansible and Terraform.</p><p>I'll explain what are these tools used for, what are their pros and cons. This will help you decide whether you should use Ansible or Terraform in your projects.</p><h2 id="ansible-and-terraform-what-are-these-tools">Ansible and Terraform: What are these tools?</h2><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/ansible-vs-terraform.jpg" class="kg-image" alt="Terraform vs Ansible: What's the difference and which one you should use?" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/ansible-vs-terraform.jpg 600w, https://linuxhandbook.com/content/images/2020/11/ansible-vs-terraform.jpg 800w" sizes="(min-width: 720px) 720px"></figure><p>Let's first briefly take a look at what are these popular DevOps tools.</p><h3 id="what-is-ansible">What is Ansible?</h3><p><a href="https://www.ansible.com/">Ansible</a> is an IT automation tool. It can configure systems, deploy software, and perform more advanced IT tasks such as continuous deployments or zero downtime rolling updates.</p><h3 id="what-is-terraform">What is Terraform?</h3><p><a href="https://www.terraform.io">Terraform</a> is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.</p><p>Before highlighting the differences between these two tools let us first understand what Configuration Management and Orchestration is.</p><h3 id="configuration-management-vs-orchestration"><strong>Configuration Management vs. Orchestration</strong></h3><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://linuxhandbook.com/content/images/2020/11/Configuration-Management-Planning.jpg" class="kg-image" alt="Terraform vs Ansible: What's the difference and which one you should use?" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/Configuration-Management-Planning.jpg 600w, https://linuxhandbook.com/content/images/size/w1000/2020/11/Configuration-Management-Planning.jpg 1000w, https://linuxhandbook.com/content/images/2020/11/Configuration-Management-Planning.jpg 1002w" sizes="(min-width: 720px) 720px"><figcaption>Image Credit: <a href="https://www.plutora.com/blog/configuration-management">Plutora</a></figcaption></figure><p><a href="https://www.plutora.com/blog/configuration-management">Configuration management</a> is a set processes and procedures that ensures that the desired and consistent state of your infrastructure which includes servers and software's are always met. In other words, it's a way to make sure that a system performs as it's expected to as changes are made over time.</p><p>CM tools ensure that IT deployments are faster, incremental, repeatable, scalable, predictable, and maintains the desired state, which brings managed assets into an expected state.</p><p>Tools like Ansible are used for doing configuration management.</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/Orchestration.png" class="kg-image" alt="Terraform vs Ansible: What's the difference and which one you should use?" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/Orchestration.png 600w, https://linuxhandbook.com/content/images/2020/11/Orchestration.png 800w" sizes="(min-width: 720px) 720px"></figure><p>When it comes to <a href="https://www.redhat.com/en/topics/automation/what-is-orchestration">orchestration</a> you can use the orchestration tools to not only provision servers, but also databases, caches, load balancers, queues, monitoring, subnet configuration, firewall settings, routing rules, SSL certificates, and almost every other aspect of your infrastructure, mainly public cloud infrastructure.</p><p>Terraform is an orchestration tool. It is designed to provision the server instances themselves, leaving the job of configuring those servers to other tools.</p><h3 id="procedural-vs-declarative-language"><strong>Procedural vs. Declarative Language</strong></h3><p>DevOps tools can be categorized as Procedural or Declarative based on how they perform their actions when applied.</p><p><strong>Procedural</strong> describes an application that requires the exact steps to be laid out in the code whereas <strong>Declarative</strong> “declares” exactly what is needed, not the process by which the result is achieved.</p><p>Ansible uses procedural style where you write the code that specifies, step-by-step tasks in order to achieve desired end state.</p><p>Whereas tools like Terraform, AWS CloudFormation all are declarative in defining the process where you write code that specifies your desired end state. For example, if you needed 5 EC2 instances, that’s exactly how many you would have after the code has been executed.</p><h2 id="ansible-and-terraform-comparison">Ansible and Terraform: Comparison</h2><p>Let's see what are the advantages and disadvantages of using Terraform:</p><h3 id="pros-of-terraform">Pros of Terraform</h3><ul><li>Terraform maintains whole cloud infrastructure and make those resources available as code. That makes incremental change process very easy.</li><li>Modular design.</li><li>Simple and Easy-to-Learn</li><li>It maintains the state of the resources created. All the objects created by Terraform shall be recreated if deleted by any other process.</li><li>Seamless integration with CI/CD pipelines.</li><li>It allows import of existing resources to bring them in Terraform state.</li><li>When you run <code>terraform plan</code>, it shows the changes that are about to be applied to resources that already exist. This gives DevOps engineers an insight into the changes they're about to make, particularly if the changes are not as expected.</li></ul><h3 id="cons-of-terraform">Cons of Terraform</h3><ul><li>No rollbacks. You need to destroy everything and re-run of the tf script is required.</li><li>Error handling is not mature. As of Terraform <code>v.12.20</code> there are two new functions available for consumers <code>try()</code> and <code>can()</code>.</li><li>Terraform scripts creation isn't allowed directly from state files.</li><li>Terraform tool is still under development and going through many beta releases each month.</li><li>Not every item can be imported.  <code>terrafom import</code> command can only import one resource at a time. This means you can't yet point Terraform import to an entire collection of resources such as an AWS VPC and import all of it.</li></ul><h2 id="pros-and-cons-of-using-ansible">Pros and cons of using Ansible</h2><p>Now, let's take a look at the positives and negatives of Ansible.</p><h3 id="pros-of-ansible">Pros of Ansible</h3><ul><li>Simple and Easy-to-Learn</li><li>Agentless</li><li>Make the whole deployment process automated and developer friendly</li><li><a href="https://linuxhandbook.com/yaml-basics/">YAML</a> based simple and readable scripts (<a href="https://linuxhandbook.com/ansible-playbooks/">Ansible Playbooks</a>)</li><li>Huge module support</li><li>Availability of a central repository called Ansible galaxy to find and reuse Ansible content.</li></ul><h3 id="cons-of-ansible">Cons of Ansible</h3><ul><li>Lacks in UI offering. AWX which eventually evolved into Ansible tower still has a huge room for improvement.</li><li>No state maintenance. Ansible doesn’t keep track of dependencies. It just executes a sequential series of tasks, stops when it finishes, fails or encounters an error.</li><li>Not up to the mark when it comes to Windows OS support. Ansible is still in early stages to extend support for Windows.</li><li>Not that straightforward if you have to write complex scripts with extensive logics in playbooks.</li><li>Enterprise support isn't yet matured/reliable.</li><li>Lacks descriptive error messages when it comes to debugging complex playbooks.</li></ul><h2 id="terraform-or-ansible-which-one-is-better-for-you">Terraform or Ansible? Which one is better for you?</h2><p>Believe me, it's not a simple question to answer. Because it depends largely on your requirements.</p><p>In real world, within IT organizations, you never rely on one tool instead you use combination of different tools to achieve the desired results.</p><p>Both Ansible and Terraform tools do a lot of things pretty well. And my personal preference is to use Terraform for orchestration/provisioning and Ansible for configuration management.</p><p>Terraform performs at its best of capabilities when used for infrastructure orchestration (managing cloud resources) as this is what it was created for.</p><p>Ansible, on the other hand, is best suited and optimized for configuration management tasks (provisioning software and machines). Orchestration tasks also can be performed with it, but that is just part of what it does.</p><p>My suggestion - use what is best and originally created for the task you want to perform.</p><p>But that's not the rule of thumb either as you may find people who prefer to use one tool for everything and it works for them!</p><h3 id="conclusion">Conclusion</h3><p>Both tools have their own benefits as well as limitations when designing Infrastructure as Code environments for automation. And yes, the success totally depends on knowing which tools to use for which jobs.</p><p>I hope I have made a few things around Ansible and Terraform clearer for you. If you still have questions or suggestion, please let me in the comment section.</p>]]></content:encoded></item><item><title><![CDATA[RHCE Ansible Series #6: Decision Making in Ansible]]></title><description><![CDATA[This is the sixth chapter of RHCE Ansible EX 294 exam preparation series. Learn about using conditional statements in Ansible. ]]></description><link>https://linuxhandbook.com/decision-making-ansible/</link><guid isPermaLink="false">5fa8ee04f43da70001273c52</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[Ahmed Alkabary]]></dc:creator><pubDate>Mon, 09 Nov 2020 08:16:34 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/Decision-Making-Ansible.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/Decision-Making-Ansible.png" alt="RHCE Ansible Series #6: Decision Making in Ansible"><p>In this tutorial, you will learn how to add decision making skills to your Ansible playbooks. </p><p>You will learn to: </p><ul><li>Use <strong>when </strong>statements to run tasks conditionally.</li><li>Use <strong>block </strong>statements to implement exception handling. </li><li>Use Ansible <strong>handlers </strong>to trigger tasks upon change.</li></ul><p>Needless to say that you should be familiar with <a href="https://linuxhandbook.com/ansible-playbooks/">Ansible playbooks</a>, <a href="https://linuxhandbook.com/ansible-ad-hoc/">ad-hoc commands</a> and <a href="https://linuxhandbook.com/ansible-variables-facts-registers/">other Ansible basics</a> to understand this tutorial. You may follow the earlier chapter of this <a href="https://linuxhandbook.com/tag/ansible/">RHCE Ansible series</a>.</p><blockquote>This tutorial follow the same setup that was mentioned in <a href="https://linuxhandbook.com/rhce-ansible-installation/">the first chapter of this series</a>: 1 Red Hat control, 3 CentOS nodes and 1 Ubuntu node.</blockquote><h2 id="choosing-when-to-run-tasks">Choosing When to Run Tasks</h2><p>Let's start to put conditions on when to run a certain task with Ansible.</p><h3 id="using-when-with-facts">Using when with facts</h3><p>You can use <strong>when </strong>conditionals to run a task only when a certain condition is true. To demonstrate, create a new playbook named <strong>ubuntu-server.yml </strong>that has the following content:</p><pre><code>[elliot@control plays]$ cat ubuntu-server.yml 
---
- name: Using when with facts 
  hosts: all
  tasks:
    - name: Detect Ubuntu Servers
      debug:
        msg: "This is an Ubuntu Server."
      when:  ansible_facts['distribution'] == "Ubuntu"</code></pre><p>Now go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook ubuntu-servers.yml 

PLAY [Using when with facts] *******************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node1]
ok: [node3]
ok: [node2]

TASK [Detect Ubuntu Servers] ***************************************************
skipping: [node1]
skipping: [node2]
skipping: [node3]
ok: [node4] =&gt; {
    "msg": "This is an Ubuntu Server."
}

PLAY RECAP *********************************************************************
node1                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    

node2                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    
node3                      : ok=1    changed=0    unreachable=0    failed=0    skipped=1    
node4                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0   </code></pre><p>Notice how I used the Ansible fact <code>ansible_facts['distribution']</code> in the <strong>when </strong>condition to test which nodes are running Ubuntu. Also, notice that you don’t need to surround variables with curly brackets when using <strong>when </strong>conditionals.</p><p>In the playbook output, notice how <code>TASK [Detect Ubuntu Servers]</code> skipped the first three nodes as they are all running CentOS and only ran on node4 as it is running Ubuntu.</p><h3 id="using-when-with-registers">Using when with registers</h3><p>You can also use when conditionals with registered variables. For example, the following playbook <strong>centos-servers.yml</strong> will reveal which nodes are running CentOS:</p><pre><code>[elliot@control plays]$ cat centos-servers.yml 
---
- name: Using when with registers
  hosts: all
  tasks:
    - name: Save the contents of /etc/os-release
      command: cat /etc/os-release
      register: os_release

    - name: Detect CentOS Servers
      debug:
        msg: "Running CentOS ..."
      when: os_release.stdout.find('CentOS') != -1</code></pre><p>The playbook first starts by saving the contents of the <strong>/etc/os-release </strong>file into the <strong>os_release </strong>register. Then the second tasks displays the message “Running CentOS …” only if the word ‘<strong>CentOS</strong>’ is found in  <strong>os_release </strong>standard output.</p><p>Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook centos-servers.yml 

PLAY [Using when with registers] ***********************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node1]
ok: [node3]
ok: [node2]

TASK [Save the contents of /etc/os-release] ************************************
changed: [node4]
changed: [node1]
changed: [node2]
changed: [node3]

TASK [Detect CentOS Servers] ***************************************************
ok: [node1] =&gt; {
    "msg": "Running CentOS ..."
}
ok: [node2] =&gt; {
    "msg": "Running CentOS ..."
}
ok: [node3] =&gt; {
    "msg": "Running CentOS ..."
}
skipping: [node4]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node2                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0    
node4                      : ok=2    changed=1    unreachable=0    failed=0    skipped=1    </code></pre><p>Notice how <code>TASK [Detect CentOS Servers]</code> only ran on the first three nodes and skipped node4 (Ubuntu).</p><h3 id="testing-multiple-conditions-with-when">Testing multiple conditions with when</h3><p>You can also test multiple conditions at once by using the logical operators. For example, the following <strong>reboot-centos8.yml </strong>playbook uses the logical <strong>and </strong>operator to reboot servers that are running CentOS version 8:</p><pre><code>[elliot@control plays]$ cat reboot-centos8.yml 
---
- name: Reboot Servers
  hosts: all
  tasks:
    - name: Reboot CentOS 8 servers
      reboot: 
        msg: "Server is rebooting ..."
      when: ansible_facts['distribution'] == "CentOS" and ansible_facts['distribution_major_version'] == "8"</code></pre><p>You can also use the logical <strong>or </strong>operator to run a task if any of the conditions is true. For example, the following task would <a href="https://linuxhandbook.com/command-rebooting-linux/">reboot servers</a> that are running either <strong>CentOS </strong>or <strong>RedHat</strong>:</p><pre><code>  tasks:
    - name: Reboot CentOS and RedHat Servers 
      reboot: 
        msg: "Server is rebooting ..."
      when: ansible_facts['distribution'] == "CentOS" or ansible_facts['distribution'] == "RedHat"</code></pre><h3 id="using-when-with-loops">Using when with loops</h3><p>If you combine a <strong>when </strong>conditional statement with a <strong>loop</strong>, Ansible would test the condition for each item in the loop separately. </p><p>For example, the following <strong>print-even.yml </strong>playbook will print all the even numbers in the <strong>range(1,11):</strong></p><pre><code>[elliot@control plays]$ cat print-even.yml
---
- name: Print Some Numbers
  hosts: node1
  tasks:
    - name: Print Even Numbers
      debug:
        msg: Number {{ item }} is Even.
      loop: "{{ range(1,11) | list }}"
      when: item % 2 == 0</code></pre><p>Go ahead and run the playbook to see the list of all even numbers in the <strong>range(1,11):</strong></p><pre><code>[elliot@control plays]$ ansible-playbook print-even.yml

PLAY [Print Some Numbers] **********************************

TASK [Gathering Facts] ****************************
ok: [node1]

TASK [Print Even Numbers] ******************************
skipping: [node1] =&gt; (item=1) 
ok: [node1] =&gt; (item=2) =&gt; {
    "msg": "Number 2 is Even."
}
skipping: [node1] =&gt; (item=3) 
ok: [node1] =&gt; (item=4) =&gt; {
    "msg": "Number 4 is Even."
}
skipping: [node1] =&gt; (item=5) 
ok: [node1] =&gt; (item=6) =&gt; {
    "msg": "Number 6 is Even."
}
skipping: [node1] =&gt; (item=7) 
ok: [node1] =&gt; (item=8) =&gt; {
    "msg": "Number 8 is Even."
}
skipping: [node1] =&gt; (item=9) 
ok: [node1] =&gt; (item=10) =&gt; {
    "msg": "Number 10 is Even."
}

PLAY RECAP ***********************************
node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0</code></pre><h3 id="using-when-with-variables">Using when with variables</h3><p>You can also use <strong>when </strong>conditional statements with your own defined variables. Keep in mind that conditionals require boolean inputs; that is, a test must evaluate to true to trigger the condition and so, you need to use the <strong>bool </strong>filter with non-boolean variables.</p><p>To demonstrate, take a look at the following <strong>isfree.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat isfree.yml 
---
- name: 
  hosts: node1
  vars:
    weekend: true
    on_call: "no"
  tasks:
    - name: Run if "weekend" is true and "on_call" is false
      debug:
        msg: "You are free!"
      when: weekend and not on_call | bool</code></pre><p>Notice that I used the <strong>bool </strong>filter here to convert the <strong>on_call </strong>value to its boolean equivalent (no -&gt; false).</p><p>Also, you should be well aware that <strong>not false </strong>is <strong>true </strong>and so the whole condition will evaluate to true in this case; you are free!</p><p>You can also test to see whether a variable has been set or not; for example, the following task will only run if the <strong>car </strong>variable is defined:</p><pre><code>tasks:
    - name: Run only if you got a car
      debug:
        msg: "Let's go on a road trip ..."
      when: car is defined</code></pre><p>The following task uses the <strong>fail </strong>module to fail if the <strong>keys </strong>variable is undefined:</p><pre><code>tasks:
    - name: Fail if you got no keys
      fail:
        msg: "This play require some keys"
      when: keys is undefined</code></pre><h2 id="handling-exceptions-with-blocks">Handling Exceptions with Blocks</h2><p>Let's talk about handling exceptions.</p><h3 id="grouping-tasks-with-blocks">Grouping tasks with blocks</h3><p>You can use blocks to group related tasks together. To demonstrate, take a look at the following <strong>install-apache.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat install-apache.yml 
---
- name: Install and start Apache Play
  hosts: webservers
  tasks:
    - name: Install and start Apache
      block:
         - name: Install httpd
           yum:
             name: httpd
             state: latest

         - name: Start and enable httpd
           service:
             name: httpd
             state: started
             enabled: yes

    - name: This task is outside the block
      debug:
        msg: "I am outside the block now ..."</code></pre><p>The playbook runs on the <strong>webservers </strong>group hosts and has one block with the name Install and start Apache that includes two tasks:</p><ol><li>Install httpd</li><li>Start and enable httpd</li></ol><p>The first task <code>Install httpd</code> uses the <strong>yum </strong>module to install the httpd apache package. The second task <code>Start and enable httpd</code> uses the <strong>service </strong>module to start and enabled httpd to start on boot.</p><p>Notice that the playbook has a third task that doesn’t belong to the <code>Install and start Apache</code> block.</p><p>Now go ahead and run the playbook to install and start httpd on the <strong>webservers </strong>nodes:</p><pre><code>[elliot@control plays]$ ansible-playbook install-apache.yml 

PLAY [Install and start Apache Play] *******************************************

TASK [Gathering Facts] *********************************************************
ok: [node3]
ok: [node2]

TASK [Install httpd] ***********************************************************
changed: [node2]
changed: [node3]

TASK [Start and enable httpd] **************************************************
changed: [node3]
changed: [node2]

TASK [This task is outside the block] ******************************************
ok: [node2] =&gt; {
    "msg": "I am outside the block now ..."
}
ok: [node3] =&gt; {
    "msg": "I am outside the block now ..."
}

PLAY RECAP *********************************************************************
node2                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0     
node3                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0</code></pre><p>You can also follow up with an ad-hoc command to verify that <strong>httpd </strong>is indeed up and running:</p><pre><code>[elliot@control plays]$ ansible webservers -m command -a "systemctl status httpd"
node3 | CHANGED | rc=0 &gt;&gt;
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2020-11-03 19:35:13 UTC; 1min 37s ago
     Docs: man:httpd.service(8)
 Main PID: 47122 (httpd)
   Status: "Running, listening on: port 80"
    Tasks: 213 (limit: 11935)
   Memory: 25.1M
   CGroup: /system.slice/httpd.service
           ├─47122 /usr/sbin/httpd -DFOREGROUND
           ├─47123 /usr/sbin/httpd -DFOREGROUND
           ├─47124 /usr/sbin/httpd -DFOREGROUND
           ├─47125 /usr/sbin/httpd -DFOREGROUND
           └─47126 /usr/sbin/httpd -DFOREGROUND

Nov 03 19:35:13 node3 systemd[1]: Starting The Apache HTTP Server...
Nov 03 19:35:13 node3 systemd[1]: Started The Apache HTTP Server.
Nov 03 19:35:13 node3 httpd[47122]: Server configured, listening on: port 80
node2 | CHANGED | rc=0 &gt;&gt;
● httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2020-11-03 19:35:13 UTC; 1min 37s ago
     Docs: man:httpd.service(8)
 Main PID: 43695 (httpd)
   Status: "Running, listening on: port 80"
    Tasks: 213 (limit: 11935)
   Memory: 25.1M
   CGroup: /system.slice/httpd.service
           ├─43695 /usr/sbin/httpd -DFOREGROUND
           ├─43696 /usr/sbin/httpd -DFOREGROUND
           ├─43697 /usr/sbin/httpd -DFOREGROUND
           ├─43698 /usr/sbin/httpd -DFOREGROUND
           └─43699 /usr/sbin/httpd -DFOREGROUND

Nov 03 19:35:13 node2 systemd[1]: Starting The Apache HTTP Server...
Nov 03 19:35:13 node2 systemd[1]: Started The Apache HTTP Server.
Nov 03 19:35:13 node2 httpd[43695]: Server configured, listening on: port 80</code></pre><h3 id="handling-failure-with-blocks">Handling Failure with Blocks</h3><p>You can also use blocks to handle task errors by using the <strong>rescue </strong>and <strong>always </strong>sections. This is pretty much similar to exception handling in programming languages like the <strong>try-catch </strong>in Java or <strong>try-except </strong>in <a href="https://www.python.org/">Python</a>.</p><p>You can use the <strong>rescue </strong>section to include all the tasks that you want to run in case one or more tasks in the <strong>block </strong>has failed.</p><p>To demonstrate, let’s take a look at the following example:</p><pre><code>tasks:
  - name: Handling error example
    block:
      - name: run a command
        command: uptime

      - name: run a bad command
        command: blabla

      - name: This task will not run
        debug:
          msg: "I never run because the above task failed."

    rescue: 
      - name: Runs when the block failed
        debug:
          msg: "Block failed; let's try to fix it here ..."</code></pre><p>Notice how the second task in the block <code>run a bad command</code> generates an error and in turn the third task in the block never gets a chance to run. The tasks inside the <strong>rescue </strong>section will run because the second task in the block has failed.</p><p>You can also use <strong>ignore_errors: yes </strong>to ensure that Ansible continue executing the tasks in the playbook even if a task has failed:</p><pre><code>tasks:
  - name: Handling error example
    block:
      - name: run a command
        command: uptime

      - name: run a bad command
        command: blabla
        ignore_errors: yes

      - name: This task will run
        debug:
          msg: "I run because the above task errors were ignored."

    rescue: 
      - name: This will not run
        debug:
          msg: "Errors were ignored! ... not going to run."</code></pre><p>Notice that in this example, you ignored the errors in the second task <code>run a bad command</code> in the block and that’s why the third task was able to run. Also, the <strong>rescue </strong>section will not run as you ignored the error in the second task in the block.</p><p>You can also add an <strong>always </strong>section to a block. Tasks in the <strong>always </strong>section will always run regardless whether the block has failed or not.</p><p>To demonstrate, take a look at the following <strong>handle-errors.yml </strong>playbook that has all the three sections (<strong>block</strong>, <strong>rescue</strong>, <strong>always</strong>) of a block:</p><pre><code>[elliot@control plays]$ cat handle-errors.yml 
---
- name: Handling Errors with Blocks
  hosts: node1
  tasks:
    - name: Handling Errors Example
      block:
        - name: run a command
          command: uptime

        - name: run a bad command
          command: blabla

        - name: This task will not run
          debug:
            msg: "I never run because the task above has failed!"

      rescue:
        - name: Runs when the block fails
          debug:
            msg: "Block failed! let's try to fix it here ..."

      always:
        - name: This will always run
          debug:
            msg: "Whether the block has failed or not ... I will always run!"</code></pre><p>Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook handle-errors.yml 

PLAY [Handling Errors with Blocks] *********************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [run a command] ***********************************************************
changed: [node1]

TASK [run a bad command] *******************************************************
fatal: [node1]: FAILED! =&gt; {"changed": false, "cmd": "blabla", "msg": "[Errno 2] No such file or directory: b'blabla': b'blabla'", "rc": 2}

TASK [Runs when the block fails] ***********************************************
ok: [node1] =&gt; {
    "msg": "Block failed! let's try to fix it here ..."
}

TASK [This will always run] ****************************************************
ok: [node1] =&gt; {
    "msg": "Whether the block has failed or not ... I will always run!"
}

PLAY RECAP *********************************************************************
node1                      : ok=4    changed=1    unreachable=0    failed=0    skipped=0</code></pre><p>As you can see; the <strong>rescue </strong>section did run as the 2<sup>nd</sup>task in the block has failed and you didn’t ignore the errors. Also, the <strong>always </strong>section did (and will always) run.</p><h2 id="running-tasks-upon-change-with-handlers">Running Tasks upon Change with Handlers</h2><p>Let's see about changing handlers and running tasks.</p><h3 id="running-your-first-handler">Running your first handler</h3><p>You can use handlers to trigger tasks upon a change on your managed nodes. To demonstrate, take a look at the following <strong>handler-example.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat handler-example.yml 
---
- name: Simple Handler Example
  hosts: node1
  tasks:
    - name: Create engineers group
      group:
        name: engineers
      notify: add elliot

    - name: Another task in the play
      debug:
        msg: "I am just another task."

  handlers: 
    - name: add elliot
      user:
        name: elliot
        groups: engineers
        append: yes</code></pre><p>The first task <code>Create engineers group</code> creates the engineers group and also notifies the <code>add elliot</code> handler.</p><p>Let’s run the playbook to see what happens:</p><pre><code>[elliot@control plays]$ ansible-playbook handler-example.yml 

PLAY [Simple Handler Example] **************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Create engineers group] **************************************************
changed: [node1]

TASK [Another task in the play] ************************************************
ok: [node1] =&gt; {
    "msg": "I am just another task."
}

RUNNING HANDLER [add elliot] ***************************************************
changed: [node1]

PLAY RECAP *********************************************************************
node1                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0</code></pre><p>Notice that creating the engineers caused a change on <strong>node1 </strong>and as a result triggered the <code>add elliot</code> handler.</p><p>You can also run a quick ad-hoc command to verify that user <strong>elliot </strong>is indeed a member of the <strong>engineers </strong>group:</p><pre><code>[elliot@control plays]$ ansible node1 -m command -a "id elliot"
node1 | CHANGED | rc=0 &gt;&gt;
uid=1000(elliot) gid=1000(elliot) groups=1000(elliot),4(adm),190(systemd-journal),1004(engineers)
</code></pre><p>Ansible playbooks and modules are idempotent which means that if a change in configuration occurred on the managed nodes; it will not redo it again!</p><p>To fully understand the concept of Ansible’s idempotency; run the handler-example.yml playbook one more time:</p><pre><code>[elliot@control plays]$ ansible-playbook handler-example.yml 

PLAY [Simple Handler Example] **************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Create engineers group] **************************************************
ok: [node1]

TASK [Another task in the play] ************************************************
ok: [node1] =&gt; {
    "msg": "I am just another task."
}

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0  </code></pre><p>As you can see; the <code>Create engineers group</code> task didn’t not cause or report a change this time because the engineers group already exists on node1 and as a result; the add elliot handler did not run.</p><h3 id="controlling-when-to-report-a-change">Controlling when to report a change</h3><p>You can use the <strong>changed_when </strong>keyword to control when a task should report a change. To demonstrate, take a look at the following <strong>control-change.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat control-change.yml 
---
- name: Control Change
  hosts: node1
  tasks:
    - name: Run the date command
      command: date
      notify: handler1

    - name: Run the uptime command
      command: uptime

  handlers:
     - name: handler1
       debug:
         msg: "I can handle dates"</code></pre><p>Notice how the first task <code>Run the date command</code> triggers <strong>handler1. </strong>Now go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook control-change.yml 

PLAY [Control Change] **********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Run the date command] ****************************************************
changed: [node1]

TASK [Run the uptime command] **************************************************
changed: [node1]

RUNNING HANDLER [handler1] *****************************************************
ok: [node1] =&gt; {
    "msg": "I can handle dates"
}

PLAY RECAP *********************************************************************
node1                      : ok=4    changed=2    unreachable=0    failed=0    skipped=0</code></pre><p>Both tasks <code>Run the date command</code> and <code>Run the uptime command</code> reported changes and <strong>handler1 </strong>was triggered. You can argue that running a date and uptime commands don’t really change anything on the managed node and you are totally right!</p><p>Now let’s edit the playbook to stop the <code>Run the date command</code> task from reporting changes:</p><pre><code>[elliot@control plays]$ cat control-change.yml 
---
- name: Control Change
  hosts: node1
  tasks:
    - name: Run the date command
      command: date
      notify: handler1
      changed_when: false

    - name: Run the uptime command
      command: uptime

  handlers:
     - name: handler1
       debug:
         msg: "I can handle dates"</code></pre><p>Now run the playbook again:</p><pre><code>[elliot@control plays]$ ansible-playbook control-change.yml 

PLAY [Control Change] **********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Run the date command] ****************************************************
ok: [node1]

TASK [Run the uptime command] **************************************************
changed: [node1]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=1    unreachable=0    failed=0    skipped=0   </code></pre><p>As you can see, the <code>Run the date command</code> task didn’t report a change this time and as a result, <strong>handler1 </strong>was not triggered.</p><h3 id="configuring-services-with-handlers">Configuring services with handlers</h3><p>Handlers are especially useful when you are editing services configurations with Ansible. That’s because you only want to restart a service when there is a change in its service configuration.</p><p>To demonstrate, take a look at the following <strong>configure-ssh.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat configure-ssh.yml 
---
- name: Configure SSH 
  hosts: all
  tasks:
     - name: Edit SSH Configuration
       blockinfile:
         path: /etc/ssh/sshd_config
         block: |
            MaxAuthTries 4
            Banner /etc/motd
            X11Forwarding no
       notify: restart ssh

  handlers: 
    - name: restart ssh
      service:
        name: sshd
        state: restarted</code></pre><p>Notice I used the <strong>blockinfile </strong>module to insert multiple lines of text into the <strong>/etc/ssh/sshd_config </strong>configuration file. The <code>Edit SSH Configuration</code> task also triggers the <code>restart ssh</code> handler upon change.</p><p>Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook configure-ssh.yml 

PLAY [Configure SSH] ***********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node3]
ok: [node1]
ok: [node2]

TASK [Edit SSH Configuration] **************************************************
changed: [node4]
changed: [node2]
changed: [node3]
changed: [node1]

RUNNING HANDLER [restart ssh] **************************************************
changed: [node4]
changed: [node3]
changed: [node2]
changed: [node1]

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    
node2                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    
node3                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0    
node4                      : ok=3    changed=2    unreachable=0    failed=0    skipped=0  </code></pre><p>Everything looks good! Now let’s quickly take a look the last few lines in the <strong>/etc/ssh/sshd_config </strong>file:</p><pre><code>[elliot@control plays]$ ansible node1 -m command -a "tail -5 /etc/ssh/sshd_config"
node1 | CHANGED | rc=0 &gt;&gt;
# BEGIN ANSIBLE MANAGED BLOCK
MaxAuthTries 4
Banner /etc/motd
X11Forwarding no
# END ANSIBLE MANAGED BLOCK</code></pre><p>Amazing! Exactly as you expected it to be. Keep in mind that if you rerun the <strong>configure-ssh.yml </strong>playbook, Ansible will not edit (or append) the <strong>/etc/ssh/sshd_config </strong>file. You can try it for yourself.</p><p>I also recommend you take a look at the <strong>blockinfile </strong>and <strong>lineinfile </strong>documentation pages to understand the differences and the use of each module:</p><pre><code>[elliot@control plays]$ ansible-doc blockinfile
[elliot@control plays]$ ansible-doc lineinfile</code></pre><p>Alright! This takes us to the end of our <em><strong>Decision Making in Ansible </strong></em>tutorial. Stay tuned for next tutorial as you are going to learn <a href="https://linuxhandbook.com/jinja2-templates/">how to use <strong>Jinja2 Templates </strong>to manage files and configure services dynamically in Ansible</a>.</p>]]></content:encoded></item><item><title><![CDATA[How to List Only Directories in Linux]]></title><description><![CDATA[Listing the contents of a directory is easy. But what if you want to list only the directories, not files and links?]]></description><link>https://linuxhandbook.com/list-only-directories/</link><guid isPermaLink="false">5fa55b44f43da70001273a82</guid><category><![CDATA[Tips]]></category><dc:creator><![CDATA[Abhishek Prakash]]></dc:creator><pubDate>Sun, 08 Nov 2020 06:50:12 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/list-only-directories-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/list-only-directories-1.png" alt="How to List Only Directories in Linux"><p>The <a href="https://linuxhandbook.com/ls-command/">ls command in Linux is used for listing the contents</a> of any directory.</p><p>By default, it lists all the contents, be it a file or a directory or a link or a named pipe.</p><p>But what if you want to list only the directories? How do you that?</p><p>Like anything in Linux, there are several ways to accomplish the same task. Listing only the directories is no different:</p><ul><li>ls -d */</li><li>ls -l | grep '^d'</li><li>find . -maxdepth 1 -type d</li><li>echo */</li><li>tree -d -L 1</li></ul><p>Don't worry. I'll explain things in detail. Here's the content of the directory I am going to use in the examples here:</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/list-content-of-directory-linux.png" class="kg-image" alt="How to List Only Directories in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/list-content-of-directory-linux.png 600w, https://linuxhandbook.com/content/images/2020/11/list-content-of-directory-linux.png 714w"></figure><h2 id="use-ls-command-to-list-directories-only">Use ls command to list directories only</h2><p>It is always good to do it with the familiar ls command because this is the command you use for displaying the content of a directory.</p><p>To list only the <a href="https://www.computerhope.com/jargon/s/subdirec.htm">subdirectories</a>, use the <code>-d</code> option with ls command like this:</p><pre><code>ls -d */</code></pre><p>Here's the output it shows:</p><pre><code>[abhishek@localhost Documents]$ ls -d */
another_dir/  my_dir/
</code></pre><p>Why <code>*/</code>? Because without it, <code>ls -d</code> will only return the directory name. The <code>-d</code> option list directories not its contents (which includes file, directories etc).</p><p>The <code>*/</code> is a pattern. With <code>*</code>, you list all the content (including contents of the subdirectories) and the <code>/</code> restricts the pattern to directories.</p><p>This picture depicts the difference pretty well.</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/list-only-directories.png" class="kg-image" alt="How to List Only Directories in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/list-only-directories.png 600w, https://linuxhandbook.com/content/images/2020/11/list-only-directories.png 800w" sizes="(min-width: 720px) 720px"></figure><p></p><p>You may combine it with long listing option <code>-l</code> and most other options:</p><pre><code>[abhishek@localhost Documents]$ ls -ld */
drwxrwxr-x. 1 abhishek abhishek 16 Nov  7 18:22 another_dir/
drwxrwxr-x. 1 abhishek abhishek 44 Nov  7 18:22 my_dir/</code></pre><p>If you do not want the trailing slash (/) at the end of the directory names, you can use the cut command to cut it out:</p><pre><code>[abhishek@localhost Documents]$ ls -ld */ | cut -f1 -d'/'
drwxrwxr-x. 1 abhishek abhishek 16 Nov  7 18:22 another_dir
drwxrwxr-x. 1 abhishek abhishek 44 Nov  7 18:22 my_dir
</code></pre><h3 id="list-only-subdirectories-in-a-specific-directory">List only subdirectories in a specific directory</h3><p>The above command works in the current directory. What if you are not in the same directory?</p><p>In this situation, you can use <code>*/</code> at the end of the path of the directory with <code>ls -d</code>:</p><pre><code>ls -d Path/To/Dir/*/</code></pre><p>Here's an example where I move out of the Documents directory and then list only the directories inside Documents directory:</p><pre><code>[abhishek@localhost ~]$ ls -ld Documents/*/
drwxrwxr-x. 1 abhishek abhishek 16 Nov  7 18:22 Documents/another_dir/
drwxrwxr-x. 1 abhishek abhishek 44 Nov  7 18:22 Documents/my_dir/</code></pre><p>Did you notice that it doesn't list the hidden directory? That's one shortcoming of this method. You may use <code>ls -d .*/</code> to display hidden directories, but it only displays hidden directories. </p><h3 id="use-combination-of-ls-and-grep-command">Use combination of ls and grep command</h3><p>You can always rely on the <a href="https://linuxhandbook.com/grep-command-examples/">good old grep command for filtering the output for specific content</a>.</p><p>If you long list the contents, you can identify the directories because start with <code>d</code>.</p><p>You can use grep to filter the contents that start with d:</p><pre><code>ls -l | grep '^d'</code></pre><p>But this gives you a lot more fields than just the directory names:</p><pre><code>[abhishek@localhost Documents]$ ls -l | grep '^d'
drwxrwxr-x. 1 abhishek abhishek 16 Nov  7 18:22 another_dir
drwxrwxr-x. 1 abhishek abhishek 44 Nov  7 18:22 my_dir
</code></pre><h2 id="use-find-command-to-list-only-directories">Use find command to list only directories</h2><p>Here's how to use the find command to list only the subdirectories:</p><pre><code>find directory_path -maxdepth 1 -type d</code></pre><p>I hope you are familiar with the find command. I'll explain it nonetheless.</p><p>With <code>type d</code>, you ask the find command to only look for directories.</p><p>With <code>maxdepth 1</code> you ask the find command to keep the search at the current level only (and not go inside the subdirectories).</p><pre><code>[abhishek@localhost Documents]$ find . -maxdepth 1 -type d
.
./my_dir
./another_dir
./.my_hidden_dir
</code></pre><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/use-find-command-to-list-subdirectories-only-in-linux.png" class="kg-image" alt="How to List Only Directories in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/use-find-command-to-list-subdirectories-only-in-linux.png 600w, https://linuxhandbook.com/content/images/2020/11/use-find-command-to-list-subdirectories-only-in-linux.png 722w" sizes="(min-width: 720px) 720px"></figure><p>As you can see in the output above, it also shows the hidden directory. </p><h2 id="use-tree-command-to-list-only-directories">Use tree command to list only directories</h2><p>If your aim is to list only the directories, you may also use the tree command. </p><p>By default, the tree command gives you the complete directory structure. You can modify it to show only directories and only at the current level.</p><pre><code>tree -dai -L 1</code></pre><ul><li>d - look for directories only</li><li>a - look for hidden files and directories as well</li><li>i - remove the tree structure from the display</li><li>L 1 - don't go into the subdirectories</li></ul><p>Here's the output:</p><pre><code>abhishek@localhost Documents]$ tree -dai -L 1
.
another_dir
my_dir
.my_hidden_dir

3 directories
</code></pre><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/use-tree-command-to-list-subdirectories-only-in-linux.png" class="kg-image" alt="How to List Only Directories in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/use-tree-command-to-list-subdirectories-only-in-linux.png 600w, https://linuxhandbook.com/content/images/2020/11/use-tree-command-to-list-subdirectories-only-in-linux.png 722w" sizes="(min-width: 720px) 720px"></figure><h2 id="using-echo-command-for-listing-directories">Using echo command for listing directories</h2><p>The unlikely candidate? You'll be surprised to know that <a href="https://linuxhandbook.com/echo-command/">echo command in Linux</a> can also be used for displaying the contents of a directory. Try using <code>echo *</code> and see for yourself.</p><p>Similar to the ls command, you can also use the <code>*/</code> pattern to list only the directories in the current working directory.</p><pre><code>echo */</code></pre><p>Here's the output which is identical to what you got with the <code>ls -d</code> command:</p><figure class="kg-card kg-image-card"><img src="https://linuxhandbook.com/content/images/2020/11/use-echo-command-to-list-subdirectories-only-in-linux.png" class="kg-image" alt="How to List Only Directories in Linux" srcset="https://linuxhandbook.com/content/images/size/w600/2020/11/use-echo-command-to-list-subdirectories-only-in-linux.png 600w, https://linuxhandbook.com/content/images/2020/11/use-echo-command-to-list-subdirectories-only-in-linux.png 722w" sizes="(min-width: 720px) 720px"></figure><p>There could be more ways for listing only the directories, not files. In fact, the methods discussed here may have some ifs and buts based on what you are looking for.</p><p>If your aim is to just display the directories, most of the commands I discussed would work. If you want something more specific like only getting the directories name with slash etc, you'll have to do some formatting on your own.</p><p>I hope you find this Linux tip helpful. Questions and suggestions are always welcome.</p>]]></content:encoded></item><item><title><![CDATA[RHCE Ansible Series #5: Ansible Loops]]></title><description><![CDATA[This is the fifth chapter of RHCE Ansible EX 294 exam preparation series. And in this, you'll learn about using loops in Ansible. The tutorial will be available to public after a week. Become a free member to access it today.]]></description><link>https://linuxhandbook.com/ansible-loops/</link><guid isPermaLink="false">5fa4ee95f43da70001273971</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[Ahmed Alkabary]]></dc:creator><pubDate>Fri, 06 Nov 2020 10:38:41 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/ansible-loops.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/ansible-loops.png" alt="RHCE Ansible Series #5: Ansible Loops"><p>You may sometimes want to repeat a task multiple times. For example, you may want to create multiple users, start/stop multiple services, or change ownership on several files on your managed hosts. </p><p>In this tutorial, you will learn how to use Ansible loops to repeat a task multiple times without having to rewrite the whole task over and over again.</p><p>Before you look at loops in Ansible, I hope you have followed other chapters in this <a href="https://linuxhandbook.com/tag/ansible/">Ansible tutorial series</a>. You should know the concept of <a href="https://linuxhandbook.com/ansible-playbooks/">Ansible playbooks</a>, aware of the <a href="https://linuxhandbook.com/ansible-ad-hoc/">ad-hoc commands</a> and know the <a href="https://linuxhandbook.com/ansible-variables-facts-registers/">basic terminology associated with Ansible like list, dictionaries etc</a>.</p><p>Knowing the <a href="https://linuxhandbook.com/yaml-basics/">basics of YAML</a> is also appreciated.</p><h2 id="looping-over-lists">Looping over lists</h2><p>Ansible uses the keywords <strong>loop </strong>to iterate over the elements of a list. To demonstrate, let’s create a very simple playbook named <strong>print-list.yml </strong>that shows you how to print the elements in a list:</p><pre><code>[elliot@control plays]$ cat print-list.yml 
---
- name: print list
  hosts: node1
  vars:
    prime: [2,3,5,7,11]
  tasks:
    - name: Show first five prime numbers
      debug:
        msg: "{{ item }}"
      loop: "{{ prime }}"</code></pre><p>Notice that I use the <strong>item </strong>variable with Ansible loops. The task would run five times which is equal to the number of elements in the <strong>prime </strong>list. </p><p>On the first run, the <strong>item </strong>variable will be set to first element in the prime array (2). On the second run, the item variable will be set to the second element in the prime array (3) and so on.</p><p>Go ahead and run the playbook to see all the elements of the <strong>prime </strong>list displayed:</p><pre><code>[elliot@control plays]$ ansible-playbook print-list.yml 

PLAY [print list] **************************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Show first five prime numbers] *******************************************
ok: [node1] =&gt; (item=2) =&gt; {
    "msg": 2
}
ok: [node1] =&gt; (item=3) =&gt; {
    "msg": 3
}
ok: [node1] =&gt; (item=5) =&gt; {
    "msg": 5
}
ok: [node1] =&gt; (item=7) =&gt; {
    "msg": 7
}
ok: [node1] =&gt; (item=11) =&gt; {
    "msg": 11
}

PLAY RECAP *********************************************************************
node1                      : ok=2    changed=0    unreachable=0    failed=0</code></pre><p>Now you apply loops to a real life application. For example, you can create an <strong>add-users.yml </strong>playbook that would add multiple users on all the hosts in the <strong>dbservers </strong>group:</p><pre><code>[elliot@control plays]$ cat add-users.yml 
---
- name: Add multiple users
  hosts: dbservers
  vars:
    dbusers:
      - username: brad
        pass: pass1
      - username: david
        pass: pass2
      - username: jason
        pass: pass3
  tasks: 
    - name: Add users
      user:
        name: "{{ item.username }}"
        password: "{{ item.pass | password_hash('sha512') }}"
      loop: "{{ dbusers }}"</code></pre><p>I first created a <strong>dbusers </strong>list which is basically a list of hashes/dictionaries. I then used the <strong>user </strong>module along with a <strong>loop </strong>to add the users and set the passwords for all users in the <strong>dbusers </strong>list.</p><p>Notice that I also used the dotted notation <strong>item.username </strong>and i<strong>tem.pass </strong>to access the keys values inside the hashes/dictionaries of the <strong>dbusers </strong>list.</p><p>It is also worth noting that I used the <strong>password_hash('sha512') </strong>filter to encrypt the user passwords with the <strong>sha512 </strong>hashing algorithm as the <strong>user </strong>module wouldn’t allow setting unencrypted user passwords.</p><blockquote>RHCE Exam Tip: You will have access to the <a href="https://docs.ansible.com/">docs.ansible.com</a> page on your exam. It a very valuable resource, especially under the <em>“Frequently Asked Questions” </em>section; you will find numerous How-to questions with answers and explanations.</blockquote><p>Now let’s run the <strong>add-users.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook add-users.yml 

PLAY [Add multiple users] ******************************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]

TASK [Add users] ***************************************************************
changed: [node4] =&gt; (item={'username': 'brad', 'pass': 'pass1'})
changed: [node4] =&gt; (item={'username': 'david', 'pass': 'pass2'})
changed: [node4] =&gt; (item={'username': 'jason', 'pass': 'pass3'})

PLAY RECAP *********************************************************************
node4                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0 </code></pre><p>You can verify that the three users are added by following up with an <a href="https://linuxhandbook.com/ansible-ad-hoc/">Ansible ad-hoc command</a>:</p><pre><code>[elliot@control plays]$ ansible dbservers -m command -a "tail -3 /etc/passwd"
node4 | CHANGED | rc=0 &gt;&gt;
brad:x:1001:1004::/home/brad:/bin/bash
david:x:1002:1005::/home/david:/bin/bash
jason:x:1003:1006::/home/jason:/bin/bash</code></pre><h2 id="looping-over-dictionaries">Looping over dictionaries</h2><p>You can only use loop with lists. You will get an error if you try to loop over a dictionary. </p><p>For example, if you run the following <strong>print-dict.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat print-dict.yml 
---
- name: Print Dictionary
  hosts: node1
  vars:
    employee: 
      name: "Elliot Alderson"
      title: "Penetration Tester"
      company: "Linux Handbook"
  tasks:
    - name: Print employee dictionary
      debug:
        msg: "{{ item }}"
      loop: "{{ employee }}"</code></pre><p>You will get the following error:</p><pre><code>[elliot@control plays]$ ansible-playbook print-dict.yml 

PLAY [Print Dictionary] ********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Print employee dictionary] ***********************************************
fatal: [node1]: FAILED! =&gt; {"msg": "Invalid data passed to 'loop', it requires a list, got this instead: {'name': 'Elliot Alderson', 'title': 'Penetration Tester', 'company': 'Linux Handbook'}. Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup."}</code></pre><p>As you can see the error, it explicitly says that it requires a list.</p><p>To fix this error; you can use the <strong>dict2items </strong>filter to convert a dictionary to a list. So, in the <strong>print-dict.yml </strong>playbook; edit the line:</p><pre><code>loop: "{{ employee }}"</code></pre><p>and apply the dict2items filter as follows:</p><pre><code>loop: "{{ employee | dict2items }}"</code></pre><p>Then run the playbook again:</p><pre><code>[elliot@control plays]$ ansible-playbook print-dict.yml 

PLAY [Print Dictionary] ********************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Print employee dictionary] ***********************************************
ok: [node1] =&gt; (item={'key': 'name', 'value': 'Elliot Alderson'}) =&gt; {
    "msg": {
        "key": "name",
        "value": "Elliot Alderson"
    }
}
ok: [node1] =&gt; (item={'key': 'title', 'value': 'Penetration Tester'}) =&gt; {
    "msg": {
        "key": "title",
        "value": "Penetration Tester"
    }
}
ok: [node1] =&gt; (item={'key': 'company', 'value': 'Linux Handbook'}) =&gt; {
    "msg": {
        "key": "company",
        "value": "Linux Handbook"
    }
}

PLAY RECAP *********************************************************************
node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0</code></pre><p>Success! The key/value pairs of the <strong>employee </strong>dictionary were displayed.</p><h2 id="looping-over-a-range-of-numbers">Looping over a range of numbers</h2><p>You can use the <strong>range() </strong>function along with the list filter to loop over a range of numbers. </p><p>For example, the following task would print all the numbers from 0-9:</p><pre><code>- name: Range Loop
  debug:
    msg: "{{ item }}"
  loop: "{{ range(10) | list }}"</code></pre><p>You can also start your range at a number other than zero. For example, the following task would print all the numbers from 5-14:</p><pre><code>- name: Range Loop
  debug:
    msg: "{{ item }}"
  loop: "{{ range(5,15) | list }}"</code></pre><p>By default, the stride is set to 1. However, you can set a different stride.</p><p>For example, the following task would print all the even IP addresses in the 192.168.1.x subnet:</p><pre><code>- name: Range Loop
  debug:
    msg: 192.168.1.{{ item }}
  loop: "{{ range(0,256,2) | list }}"</code></pre><p>Where start=0, end&lt;256, and stride=2.</p><h2 id="looping-over-inventories">Looping over inventories</h2><p>You can use the Ansible built-in <strong>groups </strong>variable to loop over all your inventory hosts or just a subset of it. For instance, to loop over all your inventory hosts; you can use:</p><pre><code>loop: "{{ groups['all'] }}"</code></pre><p>If you want to loop over all the hosts in the <strong>webservers </strong>group, you can use:</p><pre><code>loop: "{{ groups['webservers'] }}"</code></pre><p>To see how this works in playbook; take a look at the following <strong>loop-inventory.yml </strong>playbook:</p><pre><code>[elliot@control plays]$ cat loop-inventory.yml 
---
- name: Loop over Inventory 
  hosts: node1
  tasks:
    - name: Ping all hosts
      command: ping -c 1 "{{ item }}"
      loop: "{{ groups['all'] }}"</code></pre><p>This playbook tests if <strong>node1 </strong>is able to ping all other hosts in your inventory. Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook loop-inventory.yml 

PLAY [Loop over Inventory] *****************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Ping all hosts] **********************************************************
changed: [node1] =&gt; (item=node1)
changed: [node1] =&gt; (item=node2)
changed: [node1] =&gt; (item=node3)
changed: [node1] =&gt; (item=node4)

PLAY RECAP *********************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0   </code></pre><p>If you get any errors; this would mean that your managed hosts are not able to ping (reach) each other.</p><h2 id="pausing-within-loops">Pausing within loops</h2><p>You may want to pause for a certain amount of time between each loop iteration. To do this, you can use the <strong>pause </strong>directive along with the <strong>loop_control </strong>keyword.</p><p>To demonstrate, let’s write a <strong>countdown.yml </strong>playbook that would simply do a ten seconds countdown before displaying the message “Happy Birthday!” on the screen:</p><pre><code>[elliot@control plays]$ cat countdown.yml 
---
- name: Happy Birthday Playbook
  hosts: node1
  tasks:
  - name: Ten seconds countdown
    debug:
      msg: "{{ 10 - item }} seconds remaining ..."
    loop: "{{ range(10) | list }}"
    loop_control: 
      pause: 1

  - name: Display Happy Birthday
    debug:
      msg: "Happy Birthday!"</code></pre><p>Go ahead and run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook countdown.yml 

PLAY [Happy Birthday Playbook] *************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Wait for ten seconds] ****************************************************
ok: [node1] =&gt; (item=0) =&gt; {
    "msg": "10 seconds remaining ..."
}
ok: [node1] =&gt; (item=1) =&gt; {
    "msg": "9 seconds remaining ..."
}
ok: [node1] =&gt; (item=2) =&gt; {
    "msg": "8 seconds remaining ..."
}
ok: [node1] =&gt; (item=3) =&gt; {
    "msg": "7 seconds remaining ..."
}
ok: [node1] =&gt; (item=4) =&gt; {
    "msg": "6 seconds remaining ..."
}
ok: [node1] =&gt; (item=5) =&gt; {
    "msg": "5 seconds remaining ..."
}
ok: [node1] =&gt; (item=6) =&gt; {
    "msg": "4 seconds remaining ..."
}
ok: [node1] =&gt; (item=7) =&gt; {
    "msg": "3 seconds remaining ..."
}
ok: [node1] =&gt; (item=8) =&gt; {
    "msg": "2 seconds remaining ..."
}
ok: [node1] =&gt; (item=9) =&gt; {
    "msg": "1 seconds remaining ..."
}

TASK [Display Happy Birthday] **************************************************
ok: [node1] =&gt; {
    "msg": "Happy Birthday!"
}

PLAY RECAP *********************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0   </code></pre><p>So, you get familiar with loops in Ansible. Stay tuned for next tutorial as you are going to learn how to add decision-making skills to your Ansible playbooks.</p><p><strong><em>If you have not already, <a href="https://linuxhandbook.com/membership/">consider becoming a pro member of Linux Handbook</a> for more ad-free, reading experience with independent publishers like us. You may always <a href="https://www.buymeacoffee.com/linuxhandbook">support us on Buy Me a Coffee</a></em></strong>.</p>]]></content:encoded></item><item><title><![CDATA[YAML Basics Every DevOps Engineer Must Know]]></title><description><![CDATA[As a DevOps engineer, you'll be dealing with YAML files a lot. It is always a good idea to understand the basic YAML syntax.]]></description><link>https://linuxhandbook.com/yaml-basics/</link><guid isPermaLink="false">5f9e614f5f66370001a55858</guid><category><![CDATA[Tips]]></category><category><![CDATA[DevOps]]></category><dc:creator><![CDATA[Rakesh Jain]]></dc:creator><pubDate>Thu, 05 Nov 2020 14:31:14 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/yaml-basics.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/yaml-basics.png" alt="YAML Basics Every DevOps Engineer Must Know"><p>YAML has gained a lot of popularity over the last few years as it became part of crucial DevOps tools, technologies and processes such as Ansible, <a href="https://kubernetes.io/">Kubernetes</a>, CI/CD pipelines and so on.</p><p>We have already covered lots of <a href="https://linuxhandbook.com/tag/ansible/">tutorials on Ansible</a> and <a href="https://linuxhandbook.com/tag/kubernetes/">Kubernetes</a>. I thought of covering YAML essentials so that you must be aware for a smoother working with your DevOps tools configuration.</p><h2 id="what-is-yaml">What is YAML?</h2><p><a href="https://yaml.org/">YAML</a> stands for <em>"YAML Ain't Markup Language"</em> originally was an acronym for 'Yet Another Markup Language'. YAML is a “data serialization” language and basically a human-readable structured data format.</p><p>It is designed to be read and write friendly. The object serialization feature of YAML presents itself as a practicable alternative to <a href="https://www.json.org/json-en.html">JSON</a>. YAML is a superset of JSON with the use of indentation-based scoping to denote the structure like Python.</p><p>Here's a sample YAML example:</p><pre><code>--- 
 Student-ID: 314159
 First-Name: Linus
 Last-Name: Torvalds

Phone-numbers:
    - 281.555.7777
    - 832.676.8888
    - 937.996.9999

Addresses:
    - Street: 123 Main St.
    - City: Houston
    - State: Tx
---</code></pre><h2 id="yaml-basic-rules-you-should-always-remember"><strong>YAML basic rules you should always remember</strong></h2><p>If you don't want to see repeated errors while parsing your YAML file, you must always keep the following in your mind while working on YAML:</p><ul><li>Tabs are NOT allowed in YAML. You should use space for indention. </li><li>Though the amount of space doesn't matter as long as the child node indentation is more than the parent, it is a good practice to keep the same number of spaces.</li><li>There must be space between different elements of YAML (explained later).</li><li>YAML is case-sensitive.</li><li>YAML file should end with extensions like <code>.yaml</code> or <code>.yml</code>.</li><li>YAML allows UTF-8, UTF-16 and UTF-32 encoding.</li></ul><p>Let's understand the YAML syntax now.</p><h2 id="elements-of-a-yaml-file-basic-syntax"><strong>Elements of a YAML file: Basic syntax</strong></h2><p>A YAML file is used to describe data. In a YAML file the content is all about a <strong>collection of key-value pairs</strong> where the value can be anything ranging from a string to a tree.</p><p>Let us understand it by an example. This is a Kubernetes service manifest file.</p><pre><code>kind: Service
metadata:  
  name: web-app-svc
spec:  
  type: NodePort  
  ports:  
  - port: 8080         #service port    
    targetPort: 8080   #Pod Port    
    nodePort: 30012  #Node Port from the range - 30000-32767  
  selector:    
    app: web-app</code></pre><p>It's self explainable that it's a set of key value pair elements: <code>Name: Value</code>.</p><p>As you can see from the file above, a YAML file is constructed of a number of different elements. Together, they can be used to describe a wide variety of structures.</p><h3 id="1-spaces-or-indentation">1. Spaces or indentation</h3><p>In YAML, you indent with whitespace, not tabs. And there MUST be a space between elements.</p><p>Correct specification:</p><pre><code>Kind: Service</code></pre><p>Incorrect specification:</p><pre><code>Kind:Service</code></pre><p>Because there is no space after the colon in the above statement!</p><h3 id="2-comments-in-yaml">2. Comments in YAML</h3><p>Comments can be defined by placing a hash in front of an item '<code>#</code>'. Comments can be made at the start of a line of anywhere in the line. </p><p>If you go through our YAML configuration file we have three inline comments such as "#service port" etc.</p><h3 id="3-scalar-key-value-">3. Scalar (key-value) </h3><p>Scalars are the strings and numbers that make up the data on the page. In simple terms they are the key value pairs.</p><pre><code>kind: Service
metadata:  
  name: web-app-svc</code></pre><h3 id="4-collections-lists">4. Collections &amp; Lists </h3><p>List and collection elements or members are the lines that begin at the same indentation level, starting with a dash followed by a space.</p><pre><code>- web-app-prod 
- prod-deployments 
- prom-monitored</code></pre><p>It is a basic list with each item in the list placed in its own line with an opening dash.</p><h3 id="5-nested-collections">5. Nested collections </h3><p>If you want to create a nested sequence with items and sub-items, you can do so by placing a single space before each dash in the sub-items.</p><pre><code>- 
 - web-app-prod 
 - prod-deployments 
 - prom-monitored
-  
 - web-app-test 
 - staging-deployments 
 - not-monitored</code></pre><h3 id="6-dictionaries">6. Dictionaries </h3><p>Dictionaries comprise a <code>key: value</code> format with contents indented.</p><pre><code>ports:    
- port: 8080         #service port    
  targetPort: 8080   #Pod Port    
  nodePort: 30012  #Node Port from the range - 30000-32767</code></pre><p>You can merge and mix-up collections of lists and dictionaries like this:</p><pre><code>ports:    
- port: 8080         #service port    
  targetPort: 8080   #Pod Port    
  nodePort:       
  - 30012       
  - 30013       
  - 30014</code></pre><p>These are very basic concepts of YAML but essential for a DevOps engineer. </p><blockquote>You don't need special editor for YAML. Your favorite text editor should already be supporting YAML or use a plugin if required.</blockquote><p>There are many things you can dig deeper and learn. For that, you may always refer to <a href="https://yaml.org/">YAML's official documentation</a>.</p><p>Want to be a better sysadmin or DevOps? Do become a Linux Handbook member today.</p>]]></content:encoded></item><item><title><![CDATA[RHCE Ansible Series #4: Ansible Variables, Facts and Registers]]></title><description><![CDATA[This is the fourth chapter of RHCE Ansible EX 294 exam preparation series and you'll learn about variables, facts and registers in this chapter. ]]></description><link>https://linuxhandbook.com/ansible-variables-facts-registers/</link><guid isPermaLink="false">5fa145ca5f66370001a55bbd</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[Ahmed Alkabary]]></dc:creator><pubDate>Tue, 03 Nov 2020 12:49:33 GMT</pubDate><media:content url="https://linuxhandbook.com/content/images/2020/11/Variables-Facts-Registers-ansible.png" medium="image"/><content:encoded><![CDATA[<img src="https://linuxhandbook.com/content/images/2020/11/Variables-Facts-Registers-ansible.png" alt="RHCE Ansible Series #4: Ansible Variables, Facts and Registers"><p>There will always be a lot of variances across your managed systems. For this reason, you need to learn how to work with Ansible variables.</p><p>In this tutorial, you will learn how to define and reference variables Ansible. You will also learn how to use Ansible facts to retrieve information on your managed nodes.</p><p>Furthermore, you will also learn how to use registers to capture task output.</p><p>If it is your first time here, please have a look at previous chapters in this series:</p><ul><li><a href="https://linuxhandbook.com/rhce-ansible-installation/">Ansible RHCE #1: Introduction and installation</a></li><li><a href="https://linuxhandbook.com/ansible-ad-hoc/">Ansible RHCE #1: Running ad-hoc commands</a></li><li><a href="https://linuxhandbook.com/ansible-playbooks/">Ansible RHCE #1: Understanding playbooks</a></li></ul><h2 id="part-1-working-with-variables-in-ansible">Part 1: Working with variables in Ansible</h2><p>Let's start with variables first. Keep in mind that all this is written in your YML file.</p><h3 id="defining-and-referencing-variables">Defining and referencing variables</h3><p>You can use the <strong>vars </strong>keyword to define variables directly inside a playbook. </p><p>For example, you can define a <strong>fav_color </strong>variable and set its value to yellow as follows:</p><pre><code>---
- name: Working with variables
  hosts: all
  vars:
    fav_color: yellow</code></pre><p>Now how do you use (reference) the <strong>fav_color </strong>variable? Ansible uses the <a href="https://palletsprojects.com/p/jinja/">Jinja2 templating system</a> to work with variables. There will be a dedicated tutorial that discusses Jinja2 in this series but for now you just need to know the very basics.</p><p>To get the value of the <strong>fav_color </strong>variable; you need to surround it by a pair of curly brackets as follows:</p><pre><code>My favorite color is {{ fav_color }}</code></pre><p>Notice that if your variable is the first element (or only element) in the line, then using quotes is mandatory as follows:</p><pre><code>"{{ fav_color }} is my favorite color."</code></pre><p>Now let’s write a playbook named <strong>variables-playbook.yml </strong>that puts all this together:</p><pre><code>[elliot@control plays]$ cat variables-playbook.yml 
---
- name: Working with variables
  hosts: node1
  vars:
    fav_color: yellow
  tasks:
    - name: Show me fav_color value
      debug:
        msg: My favorite color is {{ fav_color }}.</code></pre><p>I have used the <strong>debug </strong>module along with the <strong>msg </strong>module option to print the value of the <strong>fav_color </strong>variable.</p><p>Now run the playbook and you shall see your favorite color displayed as follows:</p><pre><code>[elliot@control plays]$ ansible-playbook variables-playbook.yml 

PLAY [Working with variables] **************************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Show me fav_color value] *************************************************
ok: [node1] =&gt; {
    "msg": "My favorite color is yellow."
}

PLAY RECAP *********************************************************************
node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><h3 id="creating-lists-and-dictionaries">Creating lists and dictionaries</h3><p>You can also use lists and dictionaries to define multivalued variables. For example, you may define a list named <strong>port_nums </strong>and set its value as follows:</p><pre><code>  vars:
    port_nums: [21,22,23,25,80,443]</code></pre><p>You could have also used the following way to define <strong>port_nums </strong>which is equivalent:</p><pre><code>vars:
    port_nums:
      - 21
      - 22
      - 23
      - 25
      - 80
      - 443</code></pre><p>You can print all the values in <strong>port_nums </strong>as follows:</p><pre><code>All the ports {{ port_nums }}</code></pre><p>You can also access a specific list element:</p><pre><code>First port is {{ port_nums[0] }}</code></pre><p>Notice that you use an index (position) to access list elements. </p><p>You can also define a dictionary named <strong>users </strong>as follows:</p><pre><code>vars:
    users:
      bob:
        username: bob
        uid: 1122
        shell: /bin/bash
      lisa:
        username: lisa
        uid: 2233
        shell: /bin/sh</code></pre><p>There are two different ways you can use to access dictionary elements:</p><ul><li>dict_name['key'] -&gt; <strong>users['bob']['shell'] </strong></li><li>dict_name.key -&gt; <strong>users.bob.shell </strong></li></ul><p>Notice that you use a key to access dictionary elements.</p><p>Now you can edit the <strong>variables-playbook.yml </strong>playbook to show lists and dictionaries in action:</p><pre><code>[elliot@control plays]$ cat variables-playbook.yml 
---
- name: Working with variables
  hosts: node1
  vars:
    port_nums: [21,22,23,25,80,443]
 
    users: 
      bob:
        username: bob
        uid: 1122
        shell: /bin/bash
      lisa:
        username: lisa
        uid: 2233
        shell: /bin/sh
  tasks:
    - name: Show 2nd item in port_nums
      debug:
        msg: SSH port is {{ port_nums[1] }}

    - name: Show the uid of bob
      debug:
        msg: UID of bob is {{ users.bob.uid }}</code></pre><p>You can now run the playbook to display the second element in <strong>port_nums </strong>and show bob’s uid:</p><pre><code>[elliot@control plays]$ ansible-playbook variables-playbook.yml 

PLAY [Working with variables] **************************************************

TASK [Show 2nd item in port_nums] **********************************************
ok: [node1] =&gt; {
    "msg": "SSH port is 22"
}

TASK [Show the uid of bob] *****************************************************
ok: [node1] =&gt; {
    "msg": "UID of bob is 1122"
}</code></pre><h3 id="including-external-variables">Including external variables</h3><p>Just like you can import (or include) tasks in a playbook. You can do the same thing with variables as well. That is, in a playbook, you can include variables defined in an external file.</p><p>To demonstrate, let’s create a file named <strong>myvars.yml </strong>that contains our <strong>port_nums </strong>list and <strong>users </strong>dictionary:</p><pre><code>[elliot@control plays]$ cat myvars.yml 
---
port_nums: [21,22,23,25,80,443]

users:
  bob:
    username: bob
    uid: 1122
    shell: /bin/bash
  lisa:
    username: lisa
    uid: 2233
    shell: /bin/sh</code></pre><p>Now you can use the <strong>vars_files </strong>keyword in your <strong>variables-playbook.yml </strong>to include the variables in <strong>myvars.yml </strong>as follows:</p><pre><code>[elliot@control plays]$ cat variables-playbook.yml 
---
- name: Working with variables
  hosts: node1
  vars_files: myvars.yml
  tasks:
    - name: Show 2nd item in port_nums
      debug:
        msg: SSH port is {{ port_nums[1] }}

    - name: Show the uid of bob
      debug:
        msg: UID of bob is {{ users.bob.uid }}</code></pre><p>Keep in mind that <strong>vars_files </strong>preprocesses and load the variables right at the start of the playbook. You can also use the <strong>include_vars </strong>module to dynamically load your variables in your playbook:</p><pre><code>[elliot@control plays]$ cat variables-playbook.yml 
---
- name: Working with variables
  hosts: node1
  tasks:
    - name: Load the variables
      include_vars: myvars.yml

    - name: Show 2nd item in port_nums
      debug:
        msg: SSH port is {{ port_nums[1] }}</code></pre><h3 id="getting-user-input">Getting user input</h3><p>You can use the <strong>vars_prompt </strong>keyword to prompt the user to set a variable’s value at runtime.</p><p>For example, the following <strong>greet.yml </strong>playbook asks the running user to enter his name and then displays a personalized greeting message:</p><pre><code>[elliot@control plays]$ cat greet.yml 
---
- name: Greet the user
  hosts: node1
  vars_prompt:
     - name: username
       prompt: What's your name?
       private: no

  tasks:
    - name: Greet the user
      debug:
        msg: Hello {{ username }}</code></pre><p>Notice I used <strong>private: no </strong>so that you can see your input on the screen as you type it; by default, it’s hidden.</p><p>Now run the playbook and enter your name:</p><pre><code>[elliot@control plays]$ ansible-playbook greet.yml 
What's your name?: Elliot

PLAY [Greet the user] **********************************************************

TASK [Greet the user] **********************************************************
ok: [node1] =&gt; {
    "msg": "Hello Elliot"
}</code></pre><h3 id="setting-host-and-group-variables">Setting host and group variables</h3><p>You can set variables that are specific to your managed hosts. By doing so, you can create much more efficient playbooks as you don’t need to write repeated tasks for different nodes or groups.</p><p>To demonstrate, edit your inventory file so that your managed nodes are grouped in the following three groups:</p><pre><code>[elliot@control plays]$ cat myhosts 
[proxy]
node1

[webservers]
node2
node3

[dbservers]
node4</code></pre><p>Now to create variables that are specific to your managed nodes; first, you need to create a directory named <strong>host_vars</strong>. Then inside <strong>host_vars</strong>, you can create variables files with filenames that corresponds to your node hostnames:</p><pre><code>[elliot@control plays]$ mkdir host_vars
[elliot@control plays]$ echo "message: I am a Proxy Server" &gt;&gt; host_vars/node1.yml
[elliot@control plays]$ echo "message: I am a Web Server" &gt;&gt; host_vars/node2.yml
[elliot@control plays]$ echo "message: I am a Web Server" &gt;&gt; host_vars/node3.yml
[elliot@control plays]$ echo "message: I am a Database Server" &gt;&gt; host_vars/node4.yml</code></pre><p>Now let’s create a playbook named <strong>motd.yml </strong>that demonstrates how <strong>host_vars </strong>work:</p><pre><code>[elliot@control plays]$ cat motd.yml 
---
- name: Set motd on all nodes
  hosts: all
  tasks:
    - name: Set motd = value of message variable.
      copy: 
        content: "{{ message }}"
        dest: /etc/motd</code></pre><p>I used the <strong>copy </strong>module to copy the contents of the <strong>message </strong>variable onto the <strong>/etc/motd </strong>file on all nodes. Now after running the playbook; you should see that the contents of <strong>/etc/motd </strong>has been updated on all nodes with the corresponding <strong>message </strong>value:</p><pre><code>[elliot@control plays]$ ansible all -m command -a "cat /etc/motd"
node1 | CHANGED | rc=0 &gt;&gt;
I am a Proxy Server
node2 | CHANGED | rc=0 &gt;&gt;
I am a Web Server
node3 | CHANGED | rc=0 &gt;&gt;
I am a Web Server
node4 | CHANGED | rc=0 &gt;&gt;
I am a Database Server</code></pre><p>Awesome! Similarly, you can use create a <strong>group_vars </strong>directory and then include all group related variables in a filename that corresponds to the group name as follows:</p><pre><code>[elliot@control plays]$ mkdir group_vars
[elliot@control plays]$ echo "pkg: squid" &gt;&gt; group_vars/proxy
[elliot@control plays]$ echo "pkg: httpd" &gt;&gt; group_vars/webservers
[elliot@control plays]$ echo "pkg: mariadb-server" &gt;&gt; group_vars/dbservers</code></pre><p>I will let you create a playbook that runs on all nodes; each node will install the package that is set in the node’s corresponding group <strong>pkg </strong>variable.</p><h3 id="understanding-variable-precedence">Understanding variable precedence</h3><p>As you have seen so far; Ansible variables can be set at different scopes (or levels).</p><p>If the same variable is set at different levels; the most specific level gets precedence.  For example, a variable that is set on a <strong>play level </strong>takes precedence over the same variable set on a <strong>host level </strong>(<strong>host_vars</strong>).</p><p>Furthermore, a variable that is set on the <strong>command line </strong>using the <strong>--extra-vars </strong>takes the highest precedence, that is, it will overwrite anything else.</p><p>To demonstrate, let’s create a playbook named <strong>variable-precedence.yml </strong>that contains the following content:</p><pre><code>[elliot@control plays]$ cat variable-precedence.yml 
---
- name: Understanding Variable Precedence
  hosts: node1
  vars:
    fav_distro: "Ubuntu"
  tasks:
    - name: Show value of fav_distro
      debug:
        msg: Favorite distro is {{ fav_distro }}</code></pre><p>Now let’s run the playbook while using the <strong>-e (--extra-vars</strong>) option to set the value of the <strong>fav_distro </strong>variable to<strong>“</strong>CentOS<strong>” </strong>from the command line:</p><pre><code>[elliot@control plays]$ ansible-playbook variable-precedence.yml -e "fav_distro=CentOS"

PLAY [Understanding Variable Precedence] ***************************************

TASK [Show value of fav_distro] ************************************************
ok: [node1] =&gt; {
    "msg": "Favorite distro is CentOS"
}</code></pre><p>Notice how the command line’s <strong>fav_distro </strong>value “CentOS” took precedence over the play’s <strong>fav_distro</strong> value “Ubuntu”.</p><h2 id="part-2-gathering-and-showing-facts">Part 2: Gathering and showing facts</h2><p>You can retrieve or discover variables that contain information about your managed hosts. These variables are called <strong>facts </strong>and Ansible uses the <strong>setup </strong>module to gather these facts. The IP address on one of your managed nodes is an example of a fact.</p><p>You can run the following ad-hoc command to gather and show all the facts on <strong>node1</strong>:</p><pre><code>[elliot@control plays]$ ansible node1 -m setup</code></pre><p>Here's the output:</p><pre><code>node1 | SUCCESS =&gt; {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "10.0.0.5"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::20d:3aff:fe0c:54aa"
        ],
        "ansible_apparmor": {
            "status": "disabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "06/02/2017",
        "ansible_bios_version": "090007",
        "ansible_cmdline": {
            "BOOT_IMAGE": "(hd0,gpt1)/vmlinuz-4.18.0-193.6.3.el8_2.x86_64",
            "console": "ttyS0,115200n8",
            "earlyprintk": "ttyS0,115200",
            "ro": true,
            "root": "UUID=6785aa9a-3d19-43ba-a189-f73916b0c827",
            "rootdelay": "300",
            "scsi_mod.use_blk_mq": "y"
        },
        "ansible_default_ipv4": {
            "address": "10.0.0.5",
            "alias": "eth0",
            "broadcast": "10.0.0.255",
            "gateway": "10.0.0.1",
            "interface": "eth0",
            "macaddress": "00:0d:3a:0c:54:aa",</code></pre><p>This is only a fraction of all the facts related to <strong>node1 </strong>that you are going to see displayed on your terminal. Notice how the facts are stored in dictionaries or lists and they all belong to the <strong>ansible_facts </strong>dictionary.</p><p>By default, the setup module is automatically called by playbooks to do the facts discovery. You may have noticed that facts discovery happens right at the start when you run any of your playbooks:</p><pre><code>[elliot@control plays]$ ansible-playbook motd.yml 

PLAY [Set motd on all nodes] ***************************************************

TASK [Gathering Facts] *********************************************************
ok: [node4]
ok: [node3]
ok: [node2]
ok: [node1]</code></pre><p>You can turn off facts gathering by setting <strong>gather_facts </strong>boolean to false right in your play header as follows:</p><pre><code>[elliot@control plays]$ cat motd.yml 
---
- name: Set motd on all nodes
  gather_facts: false 
  hosts: all
  tasks:
    - name: Set motd = value of message variable.
      copy: 
        content: "{{ message }}"
        dest: /etc/motd</code></pre><p>If you run the <strong>motd.yaml </strong>playbook again; it will skip facts gathering:</p><pre><code>[elliot@control plays]$ ansible-playbook motd.yml 

PLAY [Set motd on all nodes] ***************************************************

TASK [Set motd = value of message variable.] ********************************</code></pre><p>The same way you show a variable’s value; you can also use to show a fact’s value. The following <strong>show-facts.yml </strong>playbook displays the value of few facts on <strong>node1</strong>:</p><pre><code>[elliot@control plays]$ cat show-facts.yml 
---
- name: show some facts
  hosts: node1
  tasks:
    - name: display node1 ipv4 address
      debug:
        msg: IPv4 address is {{ ansible_facts.default_ipv4.address }}

    - name: display node1 fqdn
      debug:
        msg: FQDN is {{ ansible_facts.fqdn }} 

    - name: display node1 OS distribution
      debug:
        msg: OS Distro is {{ ansible_facts.distribution }}</code></pre><p>Now run the playbook to display the facts values:</p><pre><code>[elliot@control plays]$ ansible-playbook show-facts.yml 

PLAY [show some facts] ******

TASK [Gathering Facts] *******
ok: [node1]

TASK [display node1 ipv4 address] *******
ok: [node1] =&gt; {
    "msg": "IPv4 address is 10.0.0.5"
}

TASK [display node1 fqdn] ********
ok: [node1] =&gt; {
    "msg": "FQDN is node1.linuxhandbook.local"
}

TASK [display node1 OS distribution] ******
ok: [node1] =&gt; {
    "msg": "OS Distro is CentOS"
}

PLAY RECAP **********
node1          : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0</code></pre><h3 id="creating-customs-facts">Creating customs facts</h3><p>You may want to create your own custom facts. To do this, you can either use the <strong>set_fact </strong>module to add temporarily facts or the <strong>/etc/ansible/facts.d </strong>directory to add permanent facts on your managed nodes.</p><p>I am going to show you how to add permanent facts to your managed nodes. It’s a three steps process:</p><ol><li>Create a facts file on your control node.</li><li>Create the <strong>/etc/ansible/facts.d</strong>	directory on your managed node(s).</li><li>Copy your facts file (step 1) from the control node to your managed node(s).</li></ol><p>So, first, let’s create a <strong>cool.fact </strong>file on your control node that includes some cool facts:</p><pre><code>[elliot@control plays]$ cat cool.fact 
[fun]
kiwi=fruit
matrix=movie
octupus='8 legs'</code></pre><p>Notice that your facts filename must have the <strong>.fact </strong>extension.</p><p>For the second step, you are going to use the <strong>file </strong>module to create and the <strong>/etc/ansible/facts.d </strong>directory on the managed node(s). And lastly for the third step, you are going to use the <strong>copy </strong>module to copy cool.fact file from the control node to the managed node(s).</p><p>The following <strong>custom-facts.yml </strong>playbook combines step 2 and 3:</p><pre><code>[elliot@control plays]$ cat custom-facts.yml 
---
- name: Adding custom facts to node1
  hosts: node1
  tasks:
    - name: Create the facts.d directory
      file:
        path: /etc/ansible/facts.d
        owner: elliot
        mode: 775
        state: directory
        
    - name: Copy cool.fact to the facts.d directory
      copy:
        src: cool.fact
        dest: /etc/ansible/facts.d</code></pre><p>Now run the playbook:</p><pre><code>[elliot@control plays]$ ansible-playbook custom-facts.yml 

PLAY [Adding custom facts to node1] **********************************

TASK [Gathering Facts] ****************************
ok: [node1]

TASK [Create the facts.d directory] ******************************
changed: [node1]

TASK [Copy cool.fact to the facts.d directory] **********************
changed: [node1]</code></pre><p>The <strong>cool </strong>facts are now permanently part of <strong>node1 </strong>facts; you can verify with the following ad hoc command:</p><pre><code>[elliot@control plays]$ ansible node1 -m setup -a "filter=ansible_local"

"ansible_local": {
            "cool": {
                "fun": {
                    "kiwi": "fruit",
                    "matrix": "movie",
                    "octupus": "'8 legs'"
                }
            }
        }</code></pre><p>You can now display the octopus’s fact in a playbook as follows:</p><pre><code>An octopus has {{ ansible_local.cool.fun.octupus }}</code></pre><h2 id="part-3-capturing-output-with-registers-in-ansible">Part 3: Capturing output with registers in Ansible</h2><p>Some tasks will not show any output when running a playbook. For instance, running commands on your managed nodes using the <strong>command</strong>, <strong>shell</strong>, or <strong>raw </strong>modules will not display any output when running a playbook.</p><p>You can use a <strong>register </strong>to capture the output of a task and save it to a variable. This allows you to make use of a task output elsewhere in a playbook by simply addressing the registered variable.</p><p>The following <strong>register-playbook.yml </strong>shows you how to capture a task output in a registered variable and later display its content:</p><pre><code>[elliot@control plays]$ cat register-playbook.yml 
--- 
- name: Register Playbook
  hosts: proxy
  tasks:
    - name: Run a command
      command: uptime
      register: server_uptime

    - name: Inspect the server_uptime variable
      debug:
        var: server_uptime

    - name: Show the server uptime
      debug:
        msg: "{{ server_uptime.stdout }}"</code></pre><p>The playbook starts by running the uptime command on the proxy group hosts (node1) and registers the command output in the <strong>server_uptime </strong>variable.</p><p>Then, you use the <strong>debug </strong>module along with the var module option to inspect the <strong>server_uptime </strong>variable. Notice, that you don’t need to surround the variable with curly brackets here.</p><p>Finally, the last task in the playbook shows the output (stdout) of the registered variable <strong>server_uptime</strong>.</p><p>Run the playbook to see all this in action:</p><pre><code>[elliot@control plays]$ ansible-playbook register-playbook.yml 

PLAY [Register Playbook Showcase] **********************************************

TASK [Gathering Facts] *********************************************************
ok: [node1]

TASK [Run a command] ***********************************************************
changed: [node1]

TASK [Inspect the server_uptime variable] **************************************
ok: [node1] =&gt; {
    "server_uptime": {
        "changed": true,
        "cmd": [
            "uptime"
        ],
        "delta": "0:00:00.004221",
        "end": "2020-10-29 05:04:36.646712",
        "failed": false,
        "rc": 0,
        "start": "2020-10-29 05:04:36.642491",
        "stderr": "",
        "stderr_lines": [],
        "stdout": " 05:04:36 up 3 days,  6:56,  1 user,  load average: 0.24, 0.07, 0.02",
        "stdout_lines": [
            " 05:04:36 up 3 days,  6:56,  1 user,  load average: 0.24, 0.07, 0.02"
        ]
    }
}

TASK [Show the server uptime] **************************************************
ok: [node1] =&gt; {
    "msg": " 05:04:36 up 3 days,  6:56,  1 user,  load average: 0.24, 0.07, 0.02"
}

PLAY RECAP *********************************************************************
node1                      : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><p>Notice how the registered variable <strong>server_uptime </strong>is actually a dictionary that contains a lot of other keys beside the stdout key. It also contains other keys like <strong>rc </strong>(return code), <strong>start </strong>(time when command run), <strong>end </strong>(time when command finished), <strong>stderr </strong>(any errors), etc.</p><p>Alright! This takes us to the end of this tutorial. Stay tuned for next tutorial as you are going to learn to use loops in Ansible.</p>]]></content:encoded></item></channel></rss>