<?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[mowtomation]]></title><description><![CDATA[blog about IT automation]]></description><link>https://mowtomation.com/</link><image><url>https://mowtomation.com/favicon.png</url><title>mowtomation</title><link>https://mowtomation.com/</link></image><generator>Ghost 5.75</generator><lastBuildDate>Tue, 05 May 2026 11:12:04 GMT</lastBuildDate><atom:link href="https://mowtomation.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Ansible: Generic package manager]]></title><description><![CDATA[<p>Heyho! </p><p>In this short post I wanted to show you how to use the ansible package module. Have you ever ended up writing a bunch of when statements for different distributions just to use the right package manager? You can use the ansible package module instead, it will automatically choose</p>]]></description><link>https://mowtomation.com/ansible-generic-package-manger/</link><guid isPermaLink="false">6777dadb39151f0001f281fa</guid><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 03 Jan 2025 12:51:58 GMT</pubDate><content:encoded><![CDATA[<p>Heyho! </p><p>In this short post I wanted to show you how to use the ansible package module. Have you ever ended up writing a bunch of when statements for different distributions just to use the right package manager? You can use the ansible package module instead, it will automatically choose the right package manager for you:</p><pre><code class="language-yaml">- hosts: all
  tasks:
    - name: Install vim
      ansible.builtin.package:
        name: vim
        state: present
        update_cache: true</code></pre><p>As the module is used as &quot;proxy module&quot; and redirects all parameters you specify to it to the target module (apt, yum...) we can add &quot;update_cache&quot; to update the specific package manager cache.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/package_module.html"><div class="kg-bookmark-content"><div class="kg-bookmark-title">ansible.builtin.package module &#x2013; Generic OS package manager &#x2014; Ansible Community Documentation</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_Black.png" alt></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_White.png" alt></div></a></figure><p></p><p><em><strong>EDIT</strong></em>: <em>update_cache was not missing in docs - the package module interacts as proxy module which can pass all parameters to the target module.</em></p><p></p><p>Hope you find this useful!</p><p>Best regards</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible: Dynamic lists]]></title><description><![CDATA[<p>Hi there,</p><p>In the last few days I needed to use lists in ansible which are extended and manipulated at runtime. So I will leave a short code snipped here - maybe it is useful for you too:</p><pre><code class="language-yaml">- hosts: localhost
  connection: local
  vars:
    my_list: [ &quot;Hello&quot;, &quot;</code></pre>]]></description><link>https://mowtomation.com/ansible-dynamic-lists/</link><guid isPermaLink="false">6766c20fb8d27000016c141c</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Sat, 21 Dec 2024 13:32:54 GMT</pubDate><content:encoded><![CDATA[<p>Hi there,</p><p>In the last few days I needed to use lists in ansible which are extended and manipulated at runtime. So I will leave a short code snipped here - maybe it is useful for you too:</p><pre><code class="language-yaml">- hosts: localhost
  connection: local
  vars:
    my_list: [ &quot;Hello&quot;, &quot;World&quot;, &quot;this&quot;, &quot;is&quot;, &quot;just&quot;, &quot;a&quot;, &quot;list&quot;, &quot;of&quot;, &quot;strings&quot; ]
  tasks:
    - name: build list with all items with an &apos;i&apos; in it
      set_fact:
        my_dynamic_list: &quot;{{ my_dynamic_list|default([]) + [ item ] }}&quot;
      loop: &quot;{{ my_list }}&quot;
      when: &quot;&apos;i&apos; in item&quot;
      
    - name: print results
      debug:
        msg: &quot;{{ my_dynamic_list }}&quot;</code></pre><pre><code class="language-text">PLAY [localhost] ****************************************************************************

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

TASK [build list with all items an &apos;i&apos; in it] ********************************************************************************************************************************************************
skipping: [localhost] =&gt; (item=Hello) 
skipping: [localhost] =&gt; (item=World) 
ok: [localhost] =&gt; (item=this)
ok: [localhost] =&gt; (item=is)
skipping: [localhost] =&gt; (item=just) 
skipping: [localhost] =&gt; (item=a) 
ok: [localhost] =&gt; (item=list)
skipping: [localhost] =&gt; (item=of) 
ok: [localhost] =&gt; (item=strings)

TASK [print results] ********************************************************************************************************************************************************
ok: [localhost] =&gt; {
    &quot;msg&quot;: [
        &quot;this&quot;,
        &quot;is&quot;,
        &quot;list&quot;,
        &quot;strings&quot;
    ]
}

PLAY RECAP ********************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><h3 id="short-explanation">Short explanation</h3><p>First, a list of sample values &quot;my_list&quot; is defined. The first task then iterates through all elements of &quot;my_list&quot;. In addition, the task is given the condition</p><p><strong><em>if: &quot;&apos;i&apos; in item&quot;</em></strong></p><p>In other words, the task will only be executed if the item contains the letter &quot;i&quot;. If the condition is true, the item will be added to the target list. The Jinja2 filter &apos;default&apos;:</p><pre><code class="language-yaml">my_dynamic_list: &quot;{{ my_dynamic_list|default([]) + [ item ] }}&quot;</code></pre><p>basically intercepts the first run for which the list &quot;my_dynamic_list&quot; does not yet exist, and returns an empty list in this case. Since you can only append another list - and not a string - to a list, we define the string in a list with a single item:</p><p>... [ item ] ...</p><p>Maybe some of you will find this little code snippet useful :)</p><p>Regards!</p>]]></content:encoded></item><item><title><![CDATA[Ansible: Collections & FQCN]]></title><description><![CDATA[<p>Hey! </p><p>In our Ansible 101 guide, we learned about roles. But there is another thing in Ansible called collections. In this blog post I want to explain what collections are used for and what an FQCN (Fully Qualified Collection Name) is.</p><p>So what is a collection? Basically, it is a</p>]]></description><link>https://mowtomation.com/ansible-fully-qualified-module-names/</link><guid isPermaLink="false">6766a335b8d27000016c13d4</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Sat, 21 Dec 2024 13:20:23 GMT</pubDate><content:encoded><![CDATA[<p>Hey! </p><p>In our Ansible 101 guide, we learned about roles. But there is another thing in Ansible called collections. In this blog post I want to explain what collections are used for and what an FQCN (Fully Qualified Collection Name) is.</p><p>So what is a collection? Basically, it is a distribution format for shipping packaged Ansible content. It can contain:</p><ul><li>Modules</li><li>Plugins</li><li>Roles</li><li>Playbooks</li><li>Documentation</li><li>Dependencies (information of required other roles, collections...)</li></ul><h3 id="fqcn-fully-qualified-collection-name">FQCN (Fully Qualified Collection Name)</h3><p>You may have already noticed the way that Ansible modules are called, such as</p><pre><code class="language-yaml">- name: install apache2
  ansible.builtin.apt: # &lt;-- FQCN
    name: apache2
    state: present</code></pre><p>This is the FQCN for the apt module. The collection name is &quot;ansible.builtin&quot; and we are using the &quot;user&quot; module contained in that collection. The &quot;builtin&quot; is the default ansible internal collection containing the ansible &quot;core&quot;.</p><h3 id="community-collections">Community collections</h3><p>The cool thing about collections is that they can be built and shared by the Ansible community. There are many collections out there. For example the community.general collection:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.ansible.com/ansible/latest/collections/community/general/index.html"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Community.General &#x2014; Ansible Community Documentation</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_Black.png" alt></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_White.png" alt></div></a></figure><p> You will find really a lot of collections on ansible galaxy or GitHub:</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://galaxy.ansible.com/ui/collections/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Ansible Galaxy</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://galaxy.ansible.com/favicon.ico" alt></div></div></a></figure><h3 id="collection-structure">Collection Structure</h3><p>Like in a role (feel free to check out my blog post about roles <a href="https://mowtomation.com/ansible-101-014-roles/" rel="noreferrer">here</a>) a collection is sturctured by directories. The basic structure looks like this (source: <a href="https://docs.ansible.com/ansible/latest/dev_guide/developing_collections_structure.html" rel="noreferrer">Ansible docs</a>):</p><pre><code class="language-text">collection/
&#x251C;&#x2500;&#x2500; docs/
&#x251C;&#x2500;&#x2500; galaxy.yml
&#x251C;&#x2500;&#x2500; meta/
&#x2502;   &#x2514;&#x2500;&#x2500; runtime.yml
&#x251C;&#x2500;&#x2500; plugins/
&#x2502;   &#x251C;&#x2500;&#x2500; modules/
&#x2502;   &#x2502;   &#x2514;&#x2500;&#x2500; module1.py
&#x2502;   &#x251C;&#x2500;&#x2500; inventory/
&#x2502;   &#x2514;&#x2500;&#x2500; .../
&#x251C;&#x2500;&#x2500; README.md
&#x251C;&#x2500;&#x2500; roles/
&#x2502;   &#x251C;&#x2500;&#x2500; role1/
&#x2502;   &#x251C;&#x2500;&#x2500; role2/
&#x2502;   &#x2514;&#x2500;&#x2500; .../
&#x251C;&#x2500;&#x2500; playbooks/
&#x2502;   &#x251C;&#x2500;&#x2500; files/
&#x2502;   &#x251C;&#x2500;&#x2500; vars/
&#x2502;   &#x251C;&#x2500;&#x2500; templates/
&#x2502;   &#x2514;&#x2500;&#x2500; tasks/
&#x2514;&#x2500;&#x2500; tests/</code></pre><p></p><h2 id="example-call-the-terraform-module-from-community-collection">Example: Call the terraform module from community collection</h2><p>Let&apos;s say we want to use ansible to manage some terraform deployments. There is a terraform module in the community.general collection. So first we need to install that collection using ansible galaxy:</p><pre><code class="language-bash">ansible-galaxy collection install community.general
</code></pre><p>Then we can start using the installed collection in our playbooks (source: ansible docs):</p><pre><code class="language-yaml">- hosts: my_tf_host
  tasks:
    - name: Basic deploy of a service
      community.general.terraform:
        project_path: &apos;{{ project_dir }}&apos;
        state: present</code></pre><p>This is a very simple example, but it should show you how you can use collections. You can also create your own collections using the structure shown above!<br>That&apos;s it for now. The best way to learn this stuff is to try it out! Feedback is always appreciated!</p><p></p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #014 - Roles]]></title><description><![CDATA[<p>Hello everyone.</p><p>We are slowly approaching the end of the &quot;Ansible 101&quot;, the basics of Ansible. However, we still have one big topic to cover: Roles.</p><p>So far, we have been writing and running our Ansible code in playbooks. Now, if we want to write Ansible code that</p>]]></description><link>https://mowtomation.com/ansible-101-014-roles/</link><guid isPermaLink="false">67669af3b8d27000016c1338</guid><category><![CDATA[Ansible]]></category><category><![CDATA[Ansible101]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Sat, 21 Dec 2024 11:07:49 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone.</p><p>We are slowly approaching the end of the &quot;Ansible 101&quot;, the basics of Ansible. However, we still have one big topic to cover: Roles.</p><p>So far, we have been writing and running our Ansible code in playbooks. Now, if we want to write Ansible code that we want to use across projects - or even publish, for example - we quickly reach the limits of playbooks. This is where Ansible roles come in. They allow us to create reusable and parameterizable Ansible content. We can then integrate one - or more - playbooks into a role.</p><p>The structure of an Ansible role is represented by a directory structure. A typical role looks like this:</p><pre><code class="language-text"># playbooks
webservers.yml
roles/
    my-webserver-role/
        tasks/
          - main.yml
        handlers/
          - main.yml
        files/
        templates/
        vars/
          - main.yml
        defaults/
          - main.yml
        meta/
          - main.yml</code></pre><p>In this example we see a role called &quot;my-webserver-role&quot;. Let me briefly explain each subdirectory:</p><ul><li>tasks - Here are files where we define our tasks</li><li>handlers - Here are handlers defined if our role needs them</li><li>files - This is where we put files we want to use with the copy module, for example</li><li>templates - If we need Jinja2 templates, we put them here</li><li>vars - This is where we store role-internal variable definitions</li><li>defaults - Here we can define default values for variables, which can be overridden by the &quot;role user&quot;</li><li>mta - This is where we store meta information about the role itself</li></ul><p>An example of a more detailed web server role can be found here:</p><p><a href="https://github.com/dermow/ansible-role-httpd">https://github.com/dermow/ansible-role-httpd</a> </p><p>For our guide, however, I want to make the example a little simpler. Let&apos;s define the following tasks for the role:</p><pre><code class="language-text">Support limited to Ubuntu
Install the Apache web server
Providing a custom index.html
Starting and enabling the Apache service
</code></pre><p>First, it is important to note that by default, Ansible searches for roles in defined locations. These include &quot;./roles&quot; in your project directory. Let&apos;s start by creating the directory structure:</p><pre><code class="language-bash">mkdir ~/ansible-guide-roles
cd ~/ansible-guide-roles
mkdir -p ./roles/my-webserver-role
cd ./roles/my-webserver-role
mkdir tasks templates handlers vars defaults</code></pre><p>What&apos;s missing is an inventory - I&apos;ll use our test environment again. Of course, being the smart guy that I am, I took a snapshot beforehand and didn&apos;t have to reinstall it (chrm chrm).</p><figure class="kg-card kg-code-card"><pre><code class="language-text">[webservers]
ansible-guide-1 ansible_ssh_user=ansible ansible_host=192.168.0.11
ansible-guide-2 ansible_ssh_user=ansible ansible_host=192.168.0.12</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/inventory.txt</span></p></figcaption></figure><p>The next step is to create a file for our variable definitions:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
packages_to_install:
  - apache2
  - libapache2-mod-php</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/vars/main.yml</span></p></figcaption></figure><p>We have defined a list in which we specify which packages we want to install later. The next step is to create the corresponding task:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
- name: install packages
  become: true
  ansible.builtin.apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop: &quot;{{ packages_to_install }}&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/tasks/main.yml</span></p></figcaption></figure><p>Now we have a first version of the role that we can test. To do this, we will create a playbook with the following code:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
- hosts: webservers
  roles:
    - my-webserver-role</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/playbook.yml</span></p></figcaption></figure><p>Lets start the playbook:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt playbook.yml</code></pre><pre><code class="language-text">PLAY [webservers] ********************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [my-webserver-role : install packages] ********************************************************************************************************************************************************
changed: [ansible-guide-1] =&gt; (item=apache2)
changed: [ansible-guide-1] =&gt; (item=libapache2-mod-php)
changed: [ansible-guide-2] =&gt; (item=apache2)
changed: [ansible-guide-2] =&gt; (item=libapache2-mod-php)

PLAY RECAP ********************************************************************************************************************************************************
ansible-guide-1                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
ansible-guide-2                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>Great, it worked: The Apache web server and the PHP module should now be installed on both target machines. The next step is to make sure that the web server service is started and running:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
- name: install packages
  become: true
  ansible.builtin.apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop: &quot;{{ packages_to_install }}&quot;
  
- name: start and enable apache
  become: true
  ansible.builtin.systemd:
    name: apache2
    state: started
    enabled: true
    </code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/tasks/main.yml</span></p></figcaption></figure><p>Now we want to ship our own config with the role. To do this, I simply stole the &quot;default.conf&quot; from the default installation and removed the comments:</p><pre><code class="language-bash">cat /etc/apache2/sites-available/000-default.conf | grep -v &quot;#&quot;
&lt;VirtualHost *:80&gt;

	ServerAdmin webmaster@localhost
	DocumentRoot /var/www/html


	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

&lt;/VirtualHost&gt;
</code></pre><p>Let&apos;s say we only want the &quot;ServerAdmin&quot; to be configurable. So we create the appropriate template:</p><pre><code class="language-jinja2">&lt;VirtualHost *:80&gt;

	ServerAdmin {{ my_server_admin }}
	DocumentRoot /var/www/html


	ErrorLog ${APACHE_LOG_DIR}/error.log
	CustomLog ${APACHE_LOG_DIR}/access.log combined

&lt;/VirtualHost&gt;</code></pre><p>Now we just have to define the variable &quot;my_server_admin&quot;. Since this should be overridden by the &quot;user&quot;, it makes sense to do this under &quot;defaults&quot;:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">my_server_admin: me@example.com</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/defaults/main.yml</span></p></figcaption></figure><p>Now all you need are the tasks to deploy the template:</p><pre><code class="language-yaml">---
- name: install packages
  become: true
  ansible.builtin.apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop: &quot;{{ packages_to_install }}&quot;
  
- name: start and enable apache
  become: true
  ansible.builtin.systemd:
    name: apache2
    state: started
    enabled: true
    
- name: template web config
  become: true
  ansible.builtin.template:
    src: 000-default.conf
    dest: /etc/apache2/sites-available/000-default.conf
    </code></pre><p>Now, of course, it would be great if the web server could be restarted automatically when changes are made to the configuration. We already discussed a suitable tool for this in Part 4: Handler.</p><p>So we create a handler...</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
- name: restart-apache
  become: true
  ansible.builtin.systemd:
    name: &quot;apache2&quot;
    state: restarted
  </code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/handlers/main.yml</span></p></figcaption></figure><p>... And adapt our mission accordingly:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
- name: install packages
  become: true
  ansible.builtin.apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop: &quot;{{ packages_to_install }}&quot;
  
- name: start and enable apache
  become: true
  ansible.builtin.systemd:
    name: apache2
    state: started
    enabled: true
    
- name: template web config
  become: true
  ansible.builtin.template:
    src: 000-default.conf
    dest: /etc/apache2/sites-available/000-default.conf
  notify: restart-apache # &lt;---- HANDLER
    </code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/tasks/main.yml</span></p></figcaption></figure><p>And go:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt playbook.yml</code></pre><pre><code class="language-text">PLAY [webservers] ********************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [my-webserver-role : install packages] ********************************************************************************************************************************************************
ok: [ansible-guide-1] =&gt; (item=apache2)
ok: [ansible-guide-1] =&gt; (item=libapache2-mod-php)
ok: [ansible-guide-2] =&gt; (item=apache2)
ok: [ansible-guide-2] =&gt; (item=libapache2-mod-php)

TASK [my-webserver-role : start and enable apache] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [my-webserver-role : template web config] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

RUNNING HANDLER [my-webserver-role : restart-apache] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

PLAY RECAP ********************************************************************************************************************************************************
ansible-guide-1                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ansible-guide-2                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><p>That looks good :) All that remains is the last part of our to-do list: Provide our own index.html. With what we&apos;ve learned so far, this shouldn&apos;t be a problem: we simply create another template:</p><pre><code class="language-jinja2">&lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;{{ my_website_title }}&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    {{ my_website_content }}
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>So we created another template in which we want to use two new variables. We also create default values for them:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">my_server_admin: me@example.com
my_website_title: &quot;Ansible 101&quot;
my_website_content: &quot;Hello World&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/defaults/main.yml</span></p></figcaption></figure><p>Finally, the task to deliver the index.html:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
- name: install packages
  become: true
  ansible.builtin.apt:
    name: &quot;{{ item }}&quot;
    state: present
  loop: &quot;{{ packages_to_install }}&quot;
  
- name: start and enable apache
  become: true
  ansible.builtin.systemd:
    name: apache2
    state: started
    enabled: true
    
- name: template web config
  become: true
  ansible.builtin.template:
    src: 000-default.conf
    dest: /etc/apache2/sites-available/000-default.conf
  notify: restart-apache # &lt;---- HANDLER
  
- name: template index.html
  become: true
  ansible.builtin.template:
    src: index.html
    dest: /var/www/html/index.html
    </code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide-roles/roles/my-webserver-role/tasks/main.yml</span></p></figcaption></figure><p>But let&apos;s say we want to use our own value for our title instead of the default from the role:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt -e &quot;my_website_title=&apos;My fancy title&apos;&quot; playbook.yml</code></pre><pre><code class="language-text"> PLAY [webservers] ********************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [my-webserver-role : install packages] ********************************************************************************************************************************************************
ok: [ansible-guide-1] =&gt; (item=apache2)
ok: [ansible-guide-1] =&gt; (item=libapache2-mod-php)
ok: [ansible-guide-2] =&gt; (item=apache2)
ok: [ansible-guide-2] =&gt; (item=libapache2-mod-php)

TASK [my-webserver-role : start and enable apache] ********************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [my-webserver-role : template web config] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

TASK [my-webserver-role : template index.html] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

RUNNING HANDLER [my-webserver-role : restart-apache] ********************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

PLAY RECAP ********************************************************************************************************************************************************
ansible-guide-1                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ansible-guide-2                  : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><p>Done! Now a quick test to see if it all worked:</p><pre><code class="language-bash">curl 192.168.0.11</code></pre><pre><code class="language-text"> &lt;html&gt;
  &lt;head&gt;
    &lt;title&gt;My fancy title&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    Hello World
  &lt;/body&gt;
&lt;/html&gt;</code></pre><p>Note: In a real scenario, you should define the variables you want to override in your groupvars, hostvars, or in the playbook using the role instead of setting them in the ansible command.</p><h3 id="pro-tip">Pro tip</h3><p>Ready made rolls are available for many applications. Take a look here:</p><p><a href="https://galaxy.ansible.com/">https://galaxy.ansible.com/</a></p><h2 id="the-end">THE END</h2><p>This concludes the 101 Guide. You should now be able to continue learning on your own and just work with it in your own environment. I&apos;m not sure yet if I&apos;ll start an &quot;Advanced Guide&quot; in the same style, or if I&apos;ll just cover certain topics as they come up.</p><p>Of course I would be very happy to get some feedback. See you soon</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #013 - Ansible Vault]]></title><description><![CDATA[<p>Hello everyone.</p><p>In this part of the Ansible guide, I want to introduce you to the Ansible Vault. We can use it to encrypt data in Ansible. Many tasks require credentials that we do not want to store in plain text as a variable.</p><p>The Ansible Vault is included with</p>]]></description><link>https://mowtomation.com/ansible-101-013-ansible-vault/</link><guid isPermaLink="false">67669667b8d27000016c12f6</guid><category><![CDATA[Ansible101]]></category><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Sat, 21 Dec 2024 10:33:17 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone.</p><p>In this part of the Ansible guide, I want to introduce you to the Ansible Vault. We can use it to encrypt data in Ansible. Many tasks require credentials that we do not want to store in plain text as a variable.</p><p>The Ansible Vault is included with the installation of Ansible, so it does not need to be installed separately.<br>Creating a new vault<br>First we need to generate a password for the vault, I like to use the &quot;pwgen&quot; tool for this. I have the password written directly to a hidden file in my home directory.</p><pre><code class="language-bash">sudo apt install pwgen

# 1 Passwort mit 12 Zeichen erstellen
pwgen 12 1 &gt; ~/.vaultpw</code></pre><p>Now we can create a vault as follows. In this example, we store the vault in host vars:</p><pre><code class="language-bash">cd ~/ansible-guide
mkdir -p host_vars/ansible-guide-1

ansible-vault create host_vars/ansible-guide-1/vault.yml --vault-password-file ~/.vaultpw</code></pre><p>With the above command, we have created a new file and encrypted it with the password we created earlier. An editor should open immediately after the command.</p><p>The contents of this file can be arbitrary; in the simplest example, we can simply define variables here. Let&apos;s say we want to create a new user on our host and store his password in the vault:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">user_password: geheim1337</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/host_vars/ansible-guide-1/vault.yml</span></p></figcaption></figure><p>Save the file and you have successfully created your first Vault Secret :)</p><p>If we look at the contents of the file, it should look something like this:</p><pre><code class="language-text">$ANSIBLE_VAULT;1.1;AES256
32363637353431323335313831306531653461353135303736343138336238393861663962396139
6637636264613234326461316539636231353537666538370a663266346333643430313538646332
30663139653637313935646130366530363361646565313436633430363935613532313966323536
6632313462383464640a346538656131353038383338313337346365396131343336666466326566
38393836636437333939363330323033613535376661333330613934663066303639</code></pre><p>As we can see, the contents of the vault are cryptic and unreadable. So we can load the file into a source manager, for example, without storing the credentials in plain text.</p><p>In this case, we can use the secret as a normal variable:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  tasks:
    - name: create user
      ansible.builtin.user:
        name: me
        password: &quot;{%raw%}{{ user_password }}{%endraw%}&quot;
        state: present</code></pre><p>If we now try to run the playbook as usual, the result should look something like this:</p><pre><code class="language-text">PLAY [localhost] *********************************************************************************
ERROR! Attempting to decrypt but no vault secrets found</code></pre><p>That&apos;s right! We  need to enter the Vault password:</p><pre><code class="language-bash">ansible-playbook --vault-password-file ~/.vaultpw playbook.yml</code></pre><p>And it works. But how exactly? Ansible will search the variable directories (host_vars, group_vars) at startup as usual, find the encrypted vault, and decrypt it.</p><p>An Ansible vault does not have to be an encrypted YAML file of variables. In principle, we can use it to encrypt any file.</p><h2 id="example-storing-a-certificate-and-key-in-a-vault"><br>Example: Storing a certificate and key in a vault</h2><p>Let&apos;s assume we want to deliver an SSL certificate including a key to a web server.</p><p>We put both files in a subfolder called &quot;files&quot;:</p><figure class="kg-card kg-code-card"><pre><code class="language-text">-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIJnDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQI3mHrY1wSLzgCAggA
MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECPZFWT3K2GKaBIIJSHVxKQ3lvbwa
kh+ENQhmFQrxau5IZSeiw+D9QVFUCNc02AquLg4Pp0eBUnNtMzyGHl1w/yncdZwB
mw4ycH6Rfs0JJtP23nlcDJzTCqTeyHMQExkeQbVLADuPteoQFsjdvGLPbA5Sq5+w
WNAflSac61x3Hk9ND8y+2twISqyokixdgLWmlx72QvJ2znBA1BwRTkPzdwuvKVrA
Up5Zl82lOWzcDhRmQNxqHoH/P0h4oUfudt2RCTELX/OS/97PqMGIvu1BtcGdW+UO
DW+K+TIr/1JlfuqrUP7D3VTi5FP5nY8CgiQc5JfIY2s6K74VYfDZ1EwoUj8FJ1FX
QToErFfTm54tJ1yBpIHQjqXGGQGs1s3S3OnGztsTEZWNNgT5PzQXZN+pL4Bl0Zg5
0OubP1XJRrTjQLLpb2r+cYFnyw9vSUZ6iopA0h49nY8GatkUu39QzSg32wg4HJDh
oACRVKthGIjLyJYYeYDKc96cgKgpSEsuC5bCUzt0AmZbjgvdsBdNRq1R3dN/mZL6
Vr35I3hqHZ/HcomFYrLCy8LEMChiHMUsZA0nuWYiJXpj17so8k7gGA65U9iGjK5M
Me+mOSRIprlwW1bTHZ9OTq+IlWBrswHF0l+YXWPEfV2B9R0mBrt5Cmvf29cbjl4G
xgMDH5daXW2c0y4kDcZJ33Vw7wk7Dv7efKQqUUFKhXlSoI8gZ4zCFiflL3ZunzCe
6Uz8jscifY07U9MwEeBDTh1SG6Hc8bJJ31509D+zDrb5bwBXQHr6QojSxyHd/2WT
a4QM8Qqtk7PfLbUXIP2S1h69e6oGLBFH77VSLzzAnPuPW0/uCILabp4ekaeOJ99x
Q9wk1JrcKZk8TEom6o2hKVZ8goM3fxXvRulstoowl1MmTIlNf67iUn5/j42jnIkO
Bmvsv5J2VERyxm4ZR7Qz/kpANUkZPYunSRE1VeHWzK/dvkajnoHfrA1u/wL+C4QS
bB4GwE8O8RFS9mkLwvfRl8dHKhvoFil7hml/5MFIGWHJ40PJ+4xRFMMOq74+gnqr
eOlhOF1NG8+4dNVQhrltHdNrgXX18GXtG8qbGONrCgPGRFniB1V8T/ZV84Y7S2yt
kfTCNWpTTr2JoFo36jsyNQ+DOAZh8CryVxgTlsyG9g0m7GZL/6Smyl1RB20TV0UU
ZQAPAy6WawGTPZmJ4RJu5ijtseCu9wmHZqTPd1ORTnadAIWS7HVTAcVtfL1aH4eo
LRxBz1utryvIiV1nNibmDDNzNbCpt7UfMAyWNDeGkDIb6BQXvvixjjWmOs5KzLkr
jcIMbECAJrKyk/hzGGQBDD5NHL3S+Q2QlMgnkfZ9Gjbr69XcNMieMi2LKslzYKmw
X/t91y6eKSgFw+BF3ZmhzG1jH/vAccXG2IBzweIuUXeLigDk+3Rp+TEKxHa88sVm
2nNpL8M7jdoyYdkaejx9UQ==
-----END ENCRYPTED PRIVATE KEY-----</code></pre><figcaption><p><span style="white-space: pre-wrap;">./files/key.pem</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-text">-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIUFiGHe0Hh1w5uxMsAITAC0loQ044wDQYJKoZIhvcNAQEL
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTA5MjQxMTA5MjFaFw0yMjA5
MjQxMTA5MjFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDHOgqyCi80NikeXGX101mjWUeJfejBw87KS1VXxtOk
L1tIAMFi36FYF+dGZ95e0hWBRJiE7hbMxLN21rt+P3v3EMOXVgLKkED1ME0W5EbJ
wUOcJ1Dh/MV9dF1WUakgopNU8CgJLGgS/VBdncUwWnsUjHOxf0j6XZ7dqGaresrp
3xRy69NBeqzXwjQCh58OpXo0lv6zZVeHxGh6mHkBPUTkbrk0/2YuObvvxF6DsiRu
zpN/s3GRZ927iMxViEj5DDEVMKpXJze2ScFml5V99zpSJvw/mOb2ieKmvT7atAZW
vI/O/fM/lO9RkhZwAaoV3Wn+MXlfXFbP9sDV441ENgU0sjkRJv8eyclkeEIM9wg6
XSX8LraFfaszWCtSphTLB+tX01FOGrkOm3lpf8OGEMr4RXVnjEvB/k3JMNrmzpSf
R5LjtAqaoD4rg3wAVnyLlWEOOtEcAZFBoVSY5TMDA9yb1akGiNBvj1WNryO0bcUI
yQ4xENJ/q/yJ7pbtqbuccL1UIWhcvQrvI9OTuQCNNfD+KWBZat7aEP79c7hQuZqA
1GN9ZFZv7xBXDjmV6GZwRy+wjvFHAmW0qCzXwpS0+ibHtWD2qBbulpZD+K0WdfVu
22tEFUOP5Lni7mJVqqIjL77yLswnldxZoF3qXHkEbQexds8FgqiSPm19uY/mW4L6
IQIDAQABo1MwUTAdBgNVHQ4EFgQUPHQPwK0qKdiP5C78p2urUn0IGA8wHwYDVR0j
BBgwFoAUPHQPwK0qKdiP5C78p2urUn0IGA8wDwYDVR0TAQH/BAUwAwEB/zANBgkq
hkiG9w0BAQsFAAOCAgEAf1WDaODUcxatC10Aa/iG1BSQeFwwIibHsP8uoR4rhvOc
32FPivbk6q/Fv5WgeBkZvpjTKglMJBV8EtbDO8aAH3/7cEe8ycchSgOvX64RgzZY
e6a2HJwQ8KIPGlKcgU57CHu6aaEW3BFzvw6j0GdQHKTw8eiYKuYW8bO/4fKRxr+F
w/bV2Cg4yyor87CSgLLawS+4yPzMCFlIOr8AMswV9VIqIlem3mWyf13WS0JAEQwe
dohfa2KGeccqTudTS9NfEJRoSt7ufXHS937gtVgimCJIdt08p5eSfznvhtHVAntl
yF0Irp1DxC+IyalWielLTxwSVsyyEu8oL4zX46C6yGG4rfZv7T6rBJOonoYEmRyv
fyFfhOI8N3CaxOk8S0FTe06HsR9caAfmGE3Qz8yDRR7qgZvaufYPgO7PjGzzvPWV
c4cevlKuGuW2IzHkzq1kQThNF74lbrBTa1QAaC6JbTBuFFwyZYAE7r4+zHx+1yFr
DY5haeOZaikakxwHaaTzGwCeQMufbIJ4kfu1zxNDvOlZVtfA7DyGcnSd763/nS5E
Kl4fE8YzHWPvQJNIpx27REXE4dEs1/soMZFWokHsjg2HmYlL4M8fH9PU8sBl/MLb
8UwAg369islBzup+yn1RrLl5iRITSkHyfuHyRv8Sgs6P29MuDraV91o23J8JAiU=
-----END CERTIFICATE-----</code></pre><figcaption><p><span style="white-space: pre-wrap;">./files/certificate.pem</span></p></figcaption></figure><p>Now, we don&apos;t want to have both files lying around in plain text. So we encrypt them using the Ansible Vault:</p><pre><code class="language-bash">ansible-vault encrypt files/*.pem --vault-password-file ~/.vaultpw</code></pre><pre><code class="language-text">Encryption successful</code></pre><p>In our playbook, we can use the Copy module as usual to copy both files. Because Ansible recognizes that these are vaults, it decrypts both files.</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">- hosts: webserver-1
  tasks:
    - name: copy certificate and key
      ansible.builtin.copy:
        src: &quot;files/{%raw%}{{ item }}{%endraw%}&quot;
        dest: /etc/ssl/certs
      loop:
        - cert.pem
        - key.pem</code></pre><figcaption><p><span style="white-space: pre-wrap;">copy-certs.yml</span></p></figcaption></figure><p>Run the playbook:</p><pre><code class="language-bash">ansible-playbook playbook.yml --vault-password-file ~/.vaultpw</code></pre><p>Done! Both files should be on the server in decrypted form &#x1F604;</p><h2 id="encrypt-single-strings">Encrypt single strings</h2><p>Instead of encrypting entire files, we can simply encrypt variable values:</p><pre><code class="language-bash">ansible-vault encrypt_string &quot;hello world&quot; --vault-password-file ~/.vaultpw</code></pre><pre><code class="language-text">!vault |
          $ANSIBLE_VAULT;1.1;AES256
          64613036333366373032623562333639646565303830653335366361373533663835666135343861
  3864316335646165633439656662666537653538623662320a346432353261636466353964633365
          61636463306537313137373630323633376465663938633130633637623866326639613235323262
          3665643535633739640a366162363664356337363062636535343463323263653934623434626664
          3233
Encryption successful</code></pre><p>Within Ansible, we can then define the string as a variable like this:</p><pre><code class="language-yaml">my_encrypted_var: !vault |
          $ANSIBLE_VAULT;1.1;AES256
  64613036333366373032623562333639646565303830653335366361373533663835666135343861
          3864316335646165633439656662666537653538623662320a346432353261636466353964633365
          61636463306537313137373630323633376465663938633130633637623866326639613235323262
          3665643535633739640a366162363664356337363062636535343463323263653934623434626664
          3233</code></pre><p>This brings us to the end of the article. I hope I was able to teach some of you something new :)</p><p>As always, I would love to hear your feedback!</p><p>See you soon,</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #012 - Loops 2]]></title><description><![CDATA[<p>Hello everyone! After a long  break, I finally got back to blogging. As promised, here is the next part of the Ansible Starter Guide, which will give you more information about using loops.</p><h3 id="dictionaries">Dictionaries</h3><p>But before we return to our loops, I would like to introduce you to dictionaries, which</p>]]></description><link>https://mowtomation.com/ansible-101-012-loops-2/</link><guid isPermaLink="false">6765f5e5b8d27000016c127e</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 23:20:02 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone! After a long  break, I finally got back to blogging. As promised, here is the next part of the Ansible Starter Guide, which will give you more information about using loops.</p><h3 id="dictionaries">Dictionaries</h3><p>But before we return to our loops, I would like to introduce you to dictionaries, which were only briefly mentioned in Part 5. Simply put, dictionaries are lists whose elements can have multiple fields. A simple example:</p><pre><code class="language-yaml">people:
  - firstName: Ronald
    surName: Weasley
    age: 21
  - firstName: Gilderoy
    surName: Lockhart
    age: 40
  - firstName: Albus
    surName: Dumbledore
    age: 99
</code></pre><p>As in a simple &quot;list&quot;, the hyphen marks the beginning of a new item. In this example, each of the two items has the fields &quot;name&quot;, &quot;last name&quot;, and &quot;age&quot;.</p><p>We can also use dictionaries in loops. For example, if we wanted to print the information defined in our example, the playbook would look like this:</p><pre><code class="language-yaml">- hosts: localhost
  vars:
    people:
      - firstName: Gilderoy
        surName: Lockhard
        age: 43
      - firstName: Minerva
        surName: McGonnagal
        age: 67
      - firstName: Albus
        surName: Dumbledore
        age: 99
       
  tasks:
    - name: list people
      ansible.builtin.debug:
        msg: &quot;First Name: {{ item.firstName }}, Surname: {{ item.surName }}, Age: {{ item.age }}&quot;
      loop: &quot;{{ people }}&quot;</code></pre><pre><code class="language-text">PLAY [localhost] ***********************************************************************************
TASK [Gathering Facts] ***********************************************************************************
ok: [localhost]

TASK [list people] ***********************************************************************************
ok: [localhost] =&gt; (item={&apos;firstName&apos;: &apos;Gilderoy&apos;, &apos;surName&apos;: &apos;Lockhard&apos;, &apos;age&apos;: 43}) =&gt; {
    &quot;msg&quot;: &quot;First Name: Gilderoy, surName: Lockhard, Age: 43&quot;
}
ok: [localhost] =&gt; (item={&apos;firstName&apos;: &apos;Minerva&apos;, &apos;surName&apos;: &apos;McGonnagal&apos;, &apos;age&apos;: 67}) =&gt; {
    &quot;msg&quot;: &quot;First Name: Minerva, surName: McGonnagal, Age: 67&quot;
}
ok: [localhost] =&gt; (item={&apos;firstName&apos;: &apos;Albus&apos;, &apos;surName&apos;: &apos;Dumbledore&apos;, &apos;age&apos;: 99}) =&gt; {
    &quot;msg&quot;: &quot;First Name: Albus, surName: Dumbledore, Age: 99&quot;
}

PLAY RECAP ***********************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0</code></pre><p>If we use a dictionary as a loop variable, we can access the individual fields within the task with &quot;{{ item.FIELDNAME }}}&quot;.</p><h3 id="loops-and-conditionals">Loops and conditionals</h3><p>If we use a loop and a condition in the same task, the condition will be validated for each loop item.</p><pre><code class="language-yaml">- hosts: localhost
  vars:
    people:
      - firstName: Gilderoy
        surName: Lockhard
        age: 43
      - firstName: Minerva
        surName: McGonnagal
        age: 67
      - firstName: Albus
        surName: Dumbledore
        age: 99

   tasks:
    - name: list people oder than 90
      ansible.builtin.debug:
        msg: &quot;{{ item.firstName }} is older than 90&quot;
      loop: &quot;{{ people }}&quot;
      when: &quot;item.age &gt; 90&quot;</code></pre><pre><code class="language-text">PLAY [localhost] **************************************************************************************************************************************************************************************************

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

TASK [list people oder than 90] ***********************************************************************************************************************************************************************************
skipping: [localhost] =&gt; (item={&apos;firstName&apos;: &apos;Gilderoy&apos;, &apos;surName&apos;: &apos;Lockhard&apos;, &apos;age&apos;: 43}) 
skipping: [localhost] =&gt; (item={&apos;firstName&apos;: &apos;Minerva&apos;, &apos;surName&apos;: &apos;McGonnagal&apos;, &apos;age&apos;: 67}) 
ok: [localhost] =&gt; (item={&apos;firstName&apos;: &apos;Albus&apos;, &apos;surName&apos;: &apos;Dumbledore&apos;, &apos;age&apos;: 99}) =&gt; {
    &quot;msg&quot;: &quot;Albus: is older than 90&quot;
}

PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>As you can see, two characters (Gilderoy, Minerva) have been skipped because they do not meet the condition (age &gt; 90). Only good old Albus is over 90 :)<br></p><h2 id="loop-control">Loop control</h2><p>In some cases it is necessary to control the behavior of loops. For example, you can set the name of the index variable (default is &quot;item&quot;). This is interesting, for example, if we want to use a loop inside another loop.</p><h3 id="changing-the-name-of-the-index-variable"><br>Changing the name of the index variable</h3><p><br>A loop inside another loop? Huh? Who would do that? In some cases, it may even make sense. For example, here we have a playbook that integrates a task file for multiple elements:</p><pre><code class="language-yaml">- hosts: all
  tasks:
    - name: include tasks for every list item
      include: my-tasks.yml
      loop:
        - &quot;A&quot;
        - &quot;B&quot;
        - &quot;C&quot;
</code></pre><p>For example, the my-tasks.yml file might look like this in its simplest form:</p><pre><code class="language-yaml">- name: print item
  ansible.builtin.debug:
    msg: &quot;{{ item }}&quot;
</code></pre><p>During execution, the my-tasks file is included for each item listed in the loop in the playbook. The content of the loop variable is output to the my-tasks.yml file.</p><p>However, if we now want to use another task with a loop in my-tasks.yml, we have a small problem: the &quot;item&quot; variable is already used by the &quot;outer&quot; loop. This can be solved with loop_control:</p><pre><code class="language-yaml">- name: print item
  ansible.builtin.debug:
    msg: &quot;{{ item }}&quot;
    
 - name: another loop
   ansible.builtin.debug:
     msg: &quot;{{ item_2 }}&quot;
   loop:
     - &quot;D&quot;
     - &quot;E&quot;
     - &quot;F&quot;
   loop_control:
     index_var: item_2</code></pre><h3 id="pause-between-items">Pause between items</h3><p>We can alos use the loop control to specify, for example how long ansible should pause between processing each element:</p><pre><code class="language-yaml">- hosts: localhost
  tasks:
    - name: include tasks for every list item
      ansible.builtin.debug: 
        msg: &quot;{{ item }}&quot;
      loop:
        - &quot;A&quot;
        - &quot;B&quot;
        - &quot;C&quot;
      loop_control:
        pause: 5</code></pre><p><br>So that&apos;s it for now. See you then!</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #011 - Check Mode]]></title><description><![CDATA[<p>Hello dear readers.</p><p>I was just reading through the previous posts in the Ansible guide and realized that I forgot to mention two important Ansible features. One is check mode, which allows us to run playbooks in test mode. Changes are only shown in the output, not actually executed. Second,</p>]]></description><link>https://mowtomation.com/ansible-101-011-check-mode/</link><guid isPermaLink="false">6765f2ccb8d27000016c1253</guid><category><![CDATA[Ansible101]]></category><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 22:54:59 GMT</pubDate><content:encoded><![CDATA[<p>Hello dear readers.</p><p>I was just reading through the previous posts in the Ansible guide and realized that I forgot to mention two important Ansible features. One is check mode, which allows us to run playbooks in test mode. Changes are only shown in the output, not actually executed. Second, there is diff mode, which shows changes in &quot;before and after&quot; mode in the output.</p><h3 id="check-mode">Check Mode</h3><p>To use the check mode, we simply need to append a &quot;--check&quot; to our command. Here is an example from our setup-postgres.yml playbook from the last part:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt setup-postgres.yml --check</code></pre><pre><code class="language-text">PLAY [db] ***********************************************************************************
TASK [Gathering Facts] ***********************************************************************************
ok: [ansible-guide-3]
ok: [ansible-guide-4]

TASK [install postgresql on debian based systems (Ubuntu)] **************************************************************************************************************
changed: [ansible-guide-3]
skipping: [ansible-guide-4]

TASK [install postgresql on Redhat based systems (CentOS)] **************************************************************************************************************
skipping: [ansible-guide-3]
changed: [ansible-guide-4]

PLAY RECAP **************************************************************************************************************************************************************
ansible-guide-3                  : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0 
ansible-guide-4                  : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0 </code></pre><p>The output is identical to that without check mode, but the changes have not been made on the hosts.</p><p>In principle, this works with any playbook, but there are a few special features:<br>Dependencies</p><p>If a task depends on a previous one, for example, because a package is installed and then the corresponding service is started, the following task in check mode will probably fail because the corresponding package has not actually been installed.<br></p><h3 id="shell-commands">Shell commands</h3><p>The command and shell modules will return the status &quot;changed&quot; as in normal mode, but the actual effect cannot be checked because the result of the shell command is independent of Ansible.</p><p>For such cases, we can skip individual tasks in check mode:</p><pre><code class="language-yaml"> - name: Skip this task in check mode
   ansible.builtin.lineinfile:
      line: &quot;important config&quot;
      dest: /path/to/myconfig.conf
      state: present
    check_mode: no</code></pre><p>With the task parameter &quot;check_mode: no&quot;, we tell Ansible not to run the corresponding task in check mode.</p><h3 id="diff-mode"><br>Diff Mode</h3><p>Diff mode is independent of check mode, but can be used in conjunction with it. In diff mode, Ansible displays all changes from the original state. Here is a small example using the &quot;lineinfile&quot; module:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  tasks:
    - name: add entry to /etc/hosts
      ansible.builtin.lineinfile:
         path: /etc/hosts
         line: &quot;192.168.0.3  ansible-guide-3&quot;
         regexp: &quot;^192.168.0.3&quot;
      become: true</code></pre><p>We will now run the playbook in check and diff mode:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt lineinfile_diff.yml --check --diff</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ******************************************************************************************************************

TASK [Gathering Facts] ************************************************************************************************************
cok: [ansible-guide-1]

TASK [add entry to /etc/hosts] ****************************************************************************************************
--- before: /etc/hosts (content)
+++ after: /etc/hosts (content)
@@ -8,3 +8,4 @@
 ff00::0 ip6-mcastprefix
 ff02::1 ip6-allnodes
 ff02::2 ip6-allrouters
+192.168.0.3  ansible-guide-3

changed: [ansible-guide-1]

PLAY RECAP ************************************************************************************************************************
ansible-guide-1                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><p>If the result is OK for us, we can run the command again, this time omitting the &quot;--check&quot;.</p><p>That&apos;s all there is to it! In the next part we will continue with part 2 of the loops!</p><p>Regards</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #010 - Loops 1]]></title><description><![CDATA[<p>Hello everyone! In this part of the Ansible tutorial, we&apos;re going to look at loops. Since there is a lot to say about this topic, I will split it into (at least) two parts. In this part we will start with the basics and simple use of loops,</p>]]></description><link>https://mowtomation.com/ansible-101-010-loops-1/</link><guid isPermaLink="false">6765efc8b8d27000016c121f</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 22:39:25 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone! In this part of the Ansible tutorial, we&apos;re going to look at loops. Since there is a lot to say about this topic, I will split it into (at least) two parts. In this part we will start with the basics and simple use of loops, in the following parts I will show you some more details and special features.</p><p>We use a loop whenever we want to repeat a certain task several times. A classic example would be setting file permissions on a list of files and directories. With ten different paths, we would need ten different tasks, which is quite a lot of code. Loops allow us to do this in a single task definition. Loops also allow us to execute tasks on lists that we create at runtime.</p><p>Let&apos;s have a look at a task defined with a simple loop:</p><pre><code class="language-yaml">- hosts: all
  tasks:
    - name: task with loop
      ansible.builtin.debug: 
        msg: &quot;I am: {{ item }}&quot;
      loop:
        - &quot;Loop-Item 1&quot;
        - &quot;Loop-Item 2&quot;
        - &quot;Item 3&quot;
        - &quot;Another Item&quot;</code></pre><p>There are two things of particular importance here. Firstly, the new task level parameter &quot;loop&quot;. Here we can either specify a list of values directly (as in the example) or use a variable. More on this in a moment. Secondly, we use the variable &quot;{{ item }}&quot; in the message. This is always automatically available when we define a loop for our task and then contains the appropriate value from the list for each run.</p><p>The output of the above example would then look something like this:</p><pre><code class="language-text">PLAY [localhost] ********************************************************************************************************************************************************

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

TASK [task with loop] ****************************************************************************************************************************************************
ok: [localhost] =&gt; (item=Loop-Item 1) =&gt; {
    &quot;msg&quot;: &quot;I am: Loop-Item 1&quot;
}
ok: [localhost] =&gt; (item=Loop-Item 2) =&gt; {
    &quot;msg&quot;: &quot;I am: Loop-Item 2&quot;
}
ok: [localhost] =&gt; (item=Item 3) =&gt; {
    &quot;msg&quot;: &quot;I am: Item 3&quot;
}
ok: [localhost] =&gt; (item=Noch ein Item) =&gt; {
    &quot;msg&quot;: &quot;I am: Another Item&quot;
}

PLAY RECAP **************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  </code></pre><p>So we have defined a task that runs once per item under &quot;loop&quot;. And each time with the appropriate value from the loop in the &apos;item&apos; variable.</p><p>Let&apos;s look at this with a practical example. Let&apos;s say we want to create several directories on our web servers:</p><pre><code>/var/www/html/my_icons
/var/www/html/my_documents
/var/www/html/my_other_stuff
</code></pre><p>In this example, we do not want to define the 3 values directly under the &quot;loop&quot; parameter, but rather define them in a variable beforehand. This allows us to vary the content using the host and group variables, for example.</p><p>So we first create a variable in the groupvars for &quot;webservers&quot;:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
dirs_to_create: 
  - &quot;/var/www/html/my_icons&quot;
  - &quot;/var/www/html/my_documents&quot;
  - &quot;/var/www/html/my_other_stuff&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/group_vars/webservers/main.yml</span></p></figcaption></figure><p>The &apos;dirs_to_create&apos; variable is of type &apos;list&apos; and is now available to all hosts in the &apos;webservers&apos; group. Now let&apos;s create an appropriate playbook:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">---
- hosts: webservers
  tasks:
    - name: create directories
      ansible.builtin.file:
        path: &quot;{{ item }}&quot;
        state: directory
      loop: &quot;{{ dirs_to_create }}&quot;
      become: true</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/loops1.yml</span></p></figcaption></figure><p>So we create a task with the &quot;file&quot; module. We can use this to manage files and directories. In this case, instead of specifying the list directly under &quot;loop&quot;, we specify the variable &quot;dirs_to_create&quot;. Ansible recognises that this is a list and applies &quot;loop&quot; to it. The list contains all the paths we want to create. So we specify the loop variable &quot;item&quot; in the module parameter &quot;path&quot; and use &quot;state: directory&quot; to specify that Ansible should create a directory.</p><p>Then we run the playbook:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt loops1.yml</code></pre><pre><code class="language-text">PLAY [webservers] ********************************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [create directories] ***********************************************************************************************************************************************
changed: [ansible-guide-1] =&gt; (item=/var/www/html/my_icons)
changed: [ansible-guide-1] =&gt; (item=/var/www/html/my_documents)
changed: [ansible-guide-1] =&gt; (item=/var/www/html/my_other_stuff)
changed: [ansible-guide-2] =&gt; (item=/var/www/html/my_icons)
changed: [ansible-guide-2] =&gt; (item=/var/www/html/my_documents)
changed: [ansible-guide-2] =&gt; (item=/var/www/html/my_other_stuff)

PLAY RECAP **************************************************************************************************************************************************************
ansible-guide-1                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-guide-2                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0</code></pre><p>For the sake of completeness, we check that the directories were actually created on the server:</p><pre><code class="language-bash">ssh ansible-guide-1
ls -rtl /var/www/html</code></pre><pre><code class="language-text">mow@notebook:~/blog$ ls -rtl /var/www/html/
insgesamt 16
-rw-r--r-- 1 root root   22 Feb 21 10:33 index.html
drwxr-xr-x 2 root root 4096 Mai  4 17:02 my_icons
drwxr-xr-x 2 root root 4096 Mai  4 17:02 my_documents
drwxr-xr-x 2 root root 4096 Mai  4 17:02 my_other_stuff</code></pre><p>Looks good! Now you have the basis for further work with loops. Of course, you can use them not only in the &quot;file&quot; and &quot;debug&quot; modules, but with almost any module!</p><p>That&apos;s it for this part. In the next post, I&apos;ll show you some special features and advanced uses for loops.</p><p>As always, I look forward to any kind of constructive feedback!</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #009 - Conditionals 2]]></title><description><![CDATA[<p>Hello everyone. As promised, I would like to show you some examples of how we can use conditionals in Ansible. For this purpose, I have added another host to my test setup - this time with a CentOS operating system:</p><p><strong>ansible-guide-4</strong></p><pre><code>OS: CentOS 8
IP: 192.168.0.14
</code></pre><p>Our</p>]]></description><link>https://mowtomation.com/ansible-101-2/</link><guid isPermaLink="false">67659345b8d27000016c11f6</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 16:03:54 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone. As promised, I would like to show you some examples of how we can use conditionals in Ansible. For this purpose, I have added another host to my test setup - this time with a CentOS operating system:</p><p><strong>ansible-guide-4</strong></p><pre><code>OS: CentOS 8
IP: 192.168.0.14
</code></pre><p>Our new inventory looks like this:</p><pre><code class="language-text">[webservers]
ansible-guide-1 ansible_ssh_user=ansible ansible_host=192.168.0.11
ansible-guide-2 ansible_ssh_user=ansible ansible_host=192.168.0.12

[db]
ansible-guide-3 ansible_ssh_user=ansible ansible_host=192.168.0.13
ansible-guide-4 ansible_ssh_user=ansible ansible_host=192.168.0.14</code></pre><p>Now we want to manage 4 hosts with Ansible. Three of them running Ubuntu and one running CentOS. Our two web servers are the same. However, we use 2 different operating systems for the database servers.</p><h2 id="example-1-installing-packages-on-different-distributions">Example 1: Installing packages on different distributions</h2><p>Let&apos;s assume we want to install the PostgreSQL server on both database servers. In the previous examples we used the &quot;apt&quot; module. This will still work on our Ubuntu server. However, CentOS uses a tool called &quot;yum&quot; to manage packages. Fortunately, Ansible comes with a module for this out of the box:</p><p><a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/yum_module.html">https://docs.ansible.com/ansible/latest/collections/ansible/builtin/yum_module.html</a></p><p>But how do we get Ansible to use the right module for each host? We do it with (... drum roll...) conditionals! In other words, we use two tools we already know. First, we need facts, because they contain information about the host operating system. On the other hand, we need conditionals to make the tasks depend on the facts.</p><pre><code class="language-yaml">- hosts: db
  gather_facts: true
  tasks:
    - name: install postgresql on debian based systems (Ubuntu)
      become: true
      ansible.builtin.apt:
        name: postgresql
        update_cache: true
      when: &quot;ansible_os_family == &apos;Debian&apos;&quot;
      
    - name: install postgresql on Redhat based systems (CentOS)
      become: true
      ansible.builtin.yum:
        name: postgresql
      when: &quot;ansible_os_family == &apos;RedHat&apos;&quot;</code></pre><p>We use the fact &apos;ansible_os_family&apos; which contains the base type of the distribution used. Ubuntu is based on a Debian operating system, while CentOS is based on a RedHat operating system. Other possible values here would be &quot;Suse&quot; or &quot;Gentoo&quot;, for example.</p><p>Let&apos;s run our playbook:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt setup-postgres.yml</code></pre><p>The output should look like this:</p><pre><code class="language-text">PLAY [db] ********************************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************************************
ok: [ansible-guide-3]
ok: [ansible-guide-4]

TASK [install postgresql on debian based systems (Ubuntu)] **************************************************************************************************************
changed: [ansible-guide-3]
skipping: [ansible-guide-4]

TASK [install postgresql on Redhat based systems (CentOS)] **************************************************************************************************************
skipping: [ansible-guide-3]
changed: [ansible-guide-4]

PLAY RECAP **************************************************************************************************************************************************************
ansible-guide-3                  : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0 
ansible-guide-4                  : ok=2    changed=1    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0 </code></pre><p>As we can see, the host for which the condition did not apply has now been skipped for both installation tasks. So we can make tasks dependent on the operating system used. Especially for environments with different distributions, this makes our life much easier!<br>Example 2: Restricting a task to a specific host</p><p>Sometimes we want to run a task in the playbook on a specific host only. Let&apos;s say we want the second web server to be our development and test system, so we want to create another user for the developer.</p><p>To do this, I&apos;m going to expand the playbook from Part 4, where we installed and started the web server:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml"> - name: webserver install 
   hosts: webservers
   tasks: 
     - name: install apache2
       become: true
       ansible.builtin.apt:
         name: apache2
         state: present
         update_cache: yes
       become: true

    - name: enable and start apache2 systemd service
      become: true
      ansible.builtin.systemd:
        name: apache2
        enabled: true
        state: started
    
    - name: add dev user on second webserver
      become: true
      ansible.builtin.user: 
        name: herbert
        state: present
      when: &quot;inventory_hostname == &apos;ansible-guide-2&apos;&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">webserver.yml</span></p></figcaption></figure><p>Here we use the Ansible variable &quot;inventory_hostname&quot;, which exists in every run (even with fact gathering disabled) and contains the current host name as defined in the inventory. Important: This does not have to be the actual hostname of the system. This is contained in the fact (gathering must be enabled) &quot;ansible_hostname&quot;.</p><p>In the example playbook above, the task will only run if the name of the current host is exactly &quot;ansible-guide-2&quot;.</p><h2 id="example-3-run-a-task-only-if-the-host-is-a-member-of-a-particular-group">Example 3: Run a task only if the host is a member of a particular group<br></h2><p>If we want to run a play for a specific group, we define that using the &quot;hosts&quot; parameter in the playbook. But even if we run a play on all hosts, for example, we can make the execution of individual tasks dependent on group membership if we need to.</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml"> - name: webserver install 
   hosts: all
   tasks: 
     - name: add default user to all systems
       become: true
       ansible.builtin.user:
         name: technik
         state: present
       become: true
    
    - name: add dbadmin user on db servers
      become: true
      ansible.builtin.user: 
        name: dbadmin
        state: present
      become: true
      when: &quot;&apos;db&apos; in group_names&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">all-hosts.yml</span></p></figcaption></figure><p>In this example, we run the game on all the hosts in our inventory. The user &apos;technik&apos; will be created on all systems. For the user &apos;dbadmin&apos; we have defined a condition that checks if the string &apos;db&apos; is in the &apos;group_names&apos; list provided by Ansible. This is a list that exists for each host in the current run and contains the names of all groups that the host belongs to.</p><h2 id="conclusion">Conclusion</h2><p>By combining facts and conditions, we can make our playbooks very flexible, allowing us to use Ansible in heterogeneous infrastructures, for example. With Ansible, there are many ways to get to the same goal, so it is important to get a feel for which way is best for your particular use case. For example, do I use one playbook and work with conditions, or do I break the tasks into separate playbooks?</p><p>In the rest of this guide, I will try to include best practices and my own experiences whenever possible.</p><p>In the next part, we will look at another control structure in Ansible called loops. Hopefully the gap between posts will be a little shorter this time.</p><p>Till then!</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #008 - Conditionals]]></title><description><![CDATA[<p>Hello everyone! After a long break, I&apos;m back with part 8 of the Ansible Starter Guide. In this post, I want to show you what conditionals are and how we can use them in playbooks. I decided to split this topic into two articles because the blog post</p>]]></description><link>https://mowtomation.com/ansible-101-008-conditionals/</link><guid isPermaLink="false">6765441fb8d27000016c116a</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 14:44:07 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone! After a long break, I&apos;m back with part 8 of the Ansible Starter Guide. In this post, I want to show you what conditionals are and how we can use them in playbooks. I decided to split this topic into two articles because the blog post would otherwise be too long. So I&apos;ll try to show you the basic function of conditionals here, and in part 2 we&apos;ll go through practical examples together.</p><p>If you don&apos;t understand some of the points or if I didn&apos;t express myself clearly, please drop me an e-mail. I&apos;ll try to clear it up :)<br>What are conditionals?</p><p>Conditionals are conditions to which we can link the execution of tasks. For example, we can run a task only if the target host has a certain operating system installed. When we use conditionals, we use variables and facts to define them.<br>A first example</p><p>Let&apos;s start with a very simple example.</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">- hosts: ansible-guide-1
  vars:
    my_number: 6
  tasks:
    - name: task with condition
      ansible.builtin.debug:
        msg: &quot;Variable is greater then 5!!&quot;
      when: &quot;my_number &gt; 5&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">simple-conditional.yml</span></p></figcaption></figure><p>Conditionals are defined in the When parameter. This is a task level parameter. The when parameter can then contain a single condition or a list of conditions. The conditions are checked before the task is executed. If they match, the task is executed; if they do not, it is skipped and given the status &quot;skipped&quot;.</p><p>What does this mean for the example playbook above? We defined a variable &quot;<strong>my_number</strong>&quot; above that has a value of 6. Then we defined a task that has the following condition:</p><p>&quot;when: my_number &lt; 5&quot;</p><p>So we want to run the task only if the value of the &quot;my_number&quot; variable is greater than 5. In this case, we want the task to run. Now let&apos;s run the playbook:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt simple-conditional.yml</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [task mit condition] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;Variable is greater than 5!!&quot;
}

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>As you can see, the task ran normally. Now let&apos;s define another task:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  vars:
    my_number: 6
  tasks:
    - name: task with condition
      ansible.builtin.debug:
        msg: &quot;Variable is greater than 5!!&quot;
      when: &quot;my_number &gt; 5&quot;

    - name: another task with condition
      ansible.builtin.debug:
        msg: &quot;Variable is 8!!&quot;
      when: &quot;my_number == 8&quot;</code></pre><pre><code class="language-bash">ansible-playbook -i inventory.txt simple-conditional.yml</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [task mit condition] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;Variable is greater than 5!!&quot;
}

TASK [weiterer task mit condition] ****************************************************************************
skipping: [ansible-guide-1]

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>What happened now? Ansible ran our first task because its condition is true. In the second task, it checked the condition and determined that the value of the variable &quot;my_number&quot; is not 8, so the condition does not apply, and skipped the task. We can see this in the &quot;skipping&quot; output: [ansible-guide-1]&quot;</p><h2 id="operators-and-jinja2"><br>Operators and Jinja2</h2><p>We use different operators for the two conditionals: &apos;&gt;&apos; and &apos;==&apos;. Conditionals in Ansible are based on the Jinja2 templating language. So we can use all the operators available in that language. An overview can be found here:</p><p><a href="https://jinja.palletsprojects.com/en/3.0.x/templates/">https://jinja.palletsprojects.com/en/3.0.x/templates/</a></p><p>We will be working with Jinja2 a lot more when we get to the more advanced parts of the guide.</p><h3 id="multiple-conditionals"><br>Multiple conditionals</h3><p>As mentioned above, the &quot;when&quot; parameter can contain either a single condition or a list. It is important to note that conditions specified in lists are always in an &quot;AND&quot; relationship with each other. In short, this means that each list item under &quot;when&quot; must be true for the task to be executed.</p><p>Let me give you a quick example:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  vars:
    my_number: 5
    my_text: Hello World
    my_other_text: Nothing.
  tasks:
    - name: task with condition
      ansible.builtin.debug:
        msg: &quot;This task will be executed if all conditions are true&quot;
      when:
        - &quot;my_number == 5&quot;
        - &quot;&apos;Hello&apos; in my_text&quot;
        - &quot;my_other_text == &apos;Nothing.&apos;&quot;</code></pre><pre><code class="language-bash">ansible-playbook -i inventory.txt multi-conditionals.yml</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [task with condition] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;This task will be executed if all conditions are true&quot;
}

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><p>As we can see, all three conditions are true and the task is executed. Let&apos;s take a quick look at the three conditions:</p><ul><li>&quot;my_number == 5&quot;.</li></ul><p>This is the same principle as the first two examples. The test is &quot;if my_number is 5&quot;.</p><ul><li>&quot;&apos;Hello&apos; in my_text&quot;</li></ul><p>Here we use the &quot;in&quot; operator to check if the substring &quot;Hello&quot; is in the string my_text. The &quot;in&quot; operator checks whether there is a subset of elements in a list. Since a string is nothing more than a list of characters, we can use this here.</p><ul><li>&quot;my_other_text == &apos;Blubb.&apos;&quot;</li></ul><p>The == operator can also be applied to strings. So we check &quot;Is the variable my_other_text equal to &apos;Blubb. This is also true.</p><h3 id="and-operator"><br>AND OPERATOR</h3><p>In the example above, we define conditionals in a list, which implies an AND relationship. However, we can also define AND and OR operators in a single line. We could also define the task from &quot;multi-conditionals.yml&quot; this way:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">- hosts: ansible-guide-1
  vars:
    my_number: 5
    my_text: Hello World
    my_other_text: Nothing.
  tasks:
    - name: task with condition
      ansible.builtin.debug:
        msg: &quot;This task will be executed if all conditions are true&quot;
      when:
        - &quot;my_number == 5 and &apos;Hello&apos; in my_text and my_other_text == &apos;Nothing.&apos;&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">and-onelne.yml</span></p></figcaption></figure><p>If we run this now, we should get the same result as above.</p><h3 id="and-and-or-combined">AND and OR combined</h3><p>Things get a little more complicated when we want to combine AND and OR:<br>and-or.yml</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  vars:
    my_number: 5
    my_text: Hello World
    my_other_text: Nothing.
  tasks:
    - name: task with condition
      ansible.builtin.debug:
        msg: &quot;This task will be executed if all conditions are true&quot;
      when:
        - &quot;my_text == &apos;Hello World&apos;&quot;
        - &quot;my_number == 5 or my_number &gt; 3&quot;</code></pre><p>So we have defined two conditionals in a list. As we have learned, both list items must be true at the end for the task to be performed. The above example can be rewritten as follows:</p><p>If my_text is &apos;Hello world&apos; AND my_number is 5 or greater than 3.</p><p>If we now run the playbook, the task will be executed:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt and-or.yml</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansbible-guide-1]

TASK [task mit condition] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;This task will be executed if all conditions are true&quot;
}

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>As expected, the task runs. Now let&apos;s verify this by setting the value of my_number to 2:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  vars:
    my_number: 2
    my_text: Hello World
    my_other_text: Nothing.
  tasks:
    - name: task mit condition
      ansible.builtin.debug:
        msg: &quot;This task will be executed if all conditions are true&quot;
      when:
        - &quot;my_text == &apos;Hello World&quot;
        - &quot;my_number == 5 or my_number &gt; 3&quot;</code></pre><pre><code class="language-bash">ansible-playbook -i inventory.txt and-or.yml</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [task with condition] ****************************************************************************
skipping: [ansible-guide-1]

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0</code></pre><h3 id="try-it-out">Try it out!</h3><p>The best thing to do is to try it out for yourself and vary the variable values as you wish. </p><p>Now that we&apos;ve looked at how conditionals work in this part, I&apos;d like to go through some practical use cases with you in the next part.</p><p>I hope you enjoyed it again and look forward to your feedback!</p><p>Regards.</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #007 - Return values]]></title><description><![CDATA[<p>Hello, </p><p>the Ansible Starter 101 continues. We have already reached part 7, which deals with task return values.</p><p>Now you&apos;re probably asking yourself: What the hell are return values and why do I need them?</p><p>Briefly summarized: When a task is executed, it produces a return value that</p>]]></description><link>https://mowtomation.com/ansible-101-return-values/</link><guid isPermaLink="false">67653906b8d27000016c113c</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 09:39:38 GMT</pubDate><content:encoded><![CDATA[<p>Hello, </p><p>the Ansible Starter 101 continues. We have already reached part 7, which deals with task return values.</p><p>Now you&apos;re probably asking yourself: What the hell are return values and why do I need them?</p><p>Briefly summarized: When a task is executed, it produces a return value that contains information about the task result. We always need return values when we want to continue working in a task with the result of a previous task.</p><h2 id="an-example"><br>An example</h2><p>As always, I will illustrate this with a small example. Let&apos;s say we want to check if a certain file exists on the remote system. We can check this with the &quot;stat&quot; module:</p><pre><code class="language-yaml">- name: Example for return values
  hosts: webservers
  tasks:
    - name: check if hosts file is there
      ansible.builtin.stat:
        path: /etc/hosts
</code></pre><p>When we now run this playbook, the result is initially very unspectacular:</p><pre><code class="language-text">PLAY [Example for return values] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [check if file is there] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
ansible-guide-2                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>As we can see, both tasks worked and returned the status &quot;ok&quot;. And what do we get? Nothing so far. The &quot;stat&quot; module only collects statistics on a file (or path) and would return &quot;ok&quot; even if the file did not exist.</p><p>If we want to use the information collected for this file, we need to use the return value of the task. To record this, there is the &quot;register&quot; task parameter:</p><pre><code class="language-yaml">- name: Example for return Values
  hosts: webservers
  tasks:
    - name: check if hosts file is there
      ansible.builtin.stat:
        path: /etc/hosts
      register: my_var_with_return_value
</code></pre><p>This tells Ansible to store the return value of the &quot;check if hosts file is there&quot; task in a variable called &quot;my_var_with_return_value&quot;. Now, the return value of each module contains different fields. However, we can find them in the official Ansible documentation for each module. For the stat module it is here:</p><p><a href="https://docs.ansible.com/ansible/devel/collections/ansible/builtin/stat_module.html">https://docs.ansible.com/ansible/devel/collections/ansible/builtin/stat_module.html</a></p><p>The return value is stored in a dictionary. We have not covered this in detail yet, but it should be enough for now. To check if our file exists, we do the following:</p><pre><code class="language-yaml">- name: Example for return Values
  hosts: webservers
  tasks:
    - name: check if hosts file is there
      ansible.builtin.stat:
        path: /etc/hosts
      register: my_var_with_return_value

    - name: debug output
      ansible.builtin.debug:
        msg: &quot;File exists is: {{ my_var_with_return_value.stat.exists }}&quot;
</code></pre><p>If we run this playbook, we will get the following output:</p><pre><code class="language-text">PLAY [Example for return values] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [check if hosts file is there] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [debug output] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;File exists is: True&quot;
}
ok: [ansible-guide-2] =&gt; {
    &quot;msg&quot;: &quot;File exists is: True&quot;
}

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
ansible-guide-2                  : ok=3    changed=0    unreachable=0    failed=0   2skipped=0    rescued=0    ignored=0 </code></pre><p>The file &#x201C;/etc/hosts&#x201D; therefore exists on both of our test web servers.</p><h3 id="another-example">Another example</h3><p>In the same way, we could display the output of a shell command that we run through the &quot;command&quot; module:</p><pre><code class="language-yaml">- name: Example for return values
  hosts: ansible-guide-1
  tasks:
    - name: get os version
      ansible.builtin.command: &quot;uname -a&quot;
      register: my_var_with_return_value

    - name: debug output
      ansible.builtin.debug:
        msg: &quot;{{ my_var_with_return_value.stdout }}&quot;
</code></pre><pre><code class="language-text">PLAY [Beispiel mit Return Values] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [get os version] ****************************************************************************
changed: [ansible-guide-1]

TASK [debug output] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;&quot;msg&quot;: &quot;NAME=\&quot;Ubuntu\&quot;\nVERSION=\&quot;20.04.2 LTS (Focal Fossa)\&quot;\nID=ubuntu\nID_LIKE=debian\nPRETTY_NAME=\&quot;Ubuntu 20.04.2 LTS\&quot;\nVERSION_ID=\&quot;20.04\&quot;\nHOME_URL=\&quot;https://www.ubuntu.com/\&quot;\nSUPPORT_URL=\&quot;https://help.ubuntu.com/\&quot;\nBUG_REPORT_URL=\&quot;https://bugs.launchpad.net/ubuntu/\&quot;\nPRIVACY_POLICY_URL=\&quot;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\&quot;\nVERSION_CODENAME=focal\nUBUNTU_CODENAME=focal&quot;
}

PLAY RECAP ****************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>So we run another task and store the return value in a variable using &quot;register&quot;. Then we look at the structure of the return value:</p><p><a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html">https://docs.ansible.com/ansible/latest/collections/ansible/builtin/command_module.html</a></p><p>Under &quot;Return Values&quot; we see that there is the field &quot;stdout&quot;, i.e. the standard output of the called shell command. We will output this field again with the &quot;debug&quot; module.</p><h3 id="standard-fields"><br>Standard fields</h3><p>In addition to the module-specific fields, there are some standard fields in every return value dictionary. Here are some examples:</p><p><strong>Modified</strong>: Contains information as a Boolean (true/false) whether the task has returned the status &quot;changed&quot;.</p><p><strong>skipped</strong>: Contains information about whether the task was skipped.</p><p><strong>failed</strong>: Hopefully false. Contains information about whether the task failed.</p><p>The full list can be found here:</p><p><a href="https://docs.ansible.com/ansible/2.5/reference_appendices/common_return_values.html">https://docs.ansible.com/ansible/2.5/reference_appendices/common_return_values.html</a><br></p><h3 id="what-comes-next">What comes next</h3><p>In the next part I would like to show you conditionals, which are conditions we can link to the execution of our tasks.</p><p>Best wishes and see you soon!</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #006 - Facts]]></title><description><![CDATA[<p>Hello everyone. This is part 6 of the Ansible 101 guide. In this part I would like to take a look at the so-called facts with you. These are not difficult to understand, but can make our lives easier for many tasks with Ansible.<br>What are facts?</p><p>You may have</p>]]></description><link>https://mowtomation.com/ansible-101-facts/</link><guid isPermaLink="false">67653550b8d27000016c10f6</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 09:28:29 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone. This is part 6 of the Ansible 101 guide. In this part I would like to take a look at the so-called facts with you. These are not difficult to understand, but can make our lives easier for many tasks with Ansible.<br>What are facts?</p><p>You may have noticed this part at the beginning of every playbook run:</p><pre><code class="language-text">TASK [Gathering Facts] *****************************************************************************
ok: [ansible-guide-1]
</code></pre><p>This is a task that Ansible performs by default when fact collection is enabled. Facts are variables that Ansible automatically sets at the start of a playbook. They contain all sorts of information about the current play, its target systems and the current environment.</p><p>Here are a few examples:</p><p><strong>ansible_hostname:</strong> contains the hostname of the current host as determined by Ansible</p><p><strong>inventory_hostname:</strong> contains the hostname of the current host as defined in the inventory</p><p><strong>ansible_default_ipv4.address:</strong> Contains the first primary IPv4 address found by Ansible.</p><p><strong>ansible_distribution:</strong> Contains the name of the target host&apos;s OS distribution, e.g. &apos;Ubuntu&apos;, &apos;Debian&apos; or &apos;Suse&apos;.</p><p>We can display all available facts about a system with a small ad-hoc command:</p><pre><code class="language-bash">ansible ansible-guide-1 -i inventory.txt -m setup</code></pre><p>The output is a very large block in JSON (JavaScript Object Notation) format, which I will not include here for the sake of clarity. Try it out for yourself!</p><h3 id="enabledisable-fact-gathering">Enable/Disable Fact Gathering</h3><p>By default fact gathering is enabled for each playbook. However it takes its time and can be disabled if you are not using any facts:</p><pre><code class="language-yaml">- hosts: all
  gather_facts: no   # &lt;-- disable fact gathering
  tasks:
    - name: task 1
      ansible.builtin.debug:
        msg: &quot;fact gathering is off&quot;</code></pre><p>My recommendation is to activate the fact collection only when we really need to access it. This can significantly reduce the runtime of a playbook.<br></p><h3 id="accessing-facts-in-a-playbook">Accessing facts in a playbook</h3><p>Since facts are ultimately variables, accessing them is identical. For example, we can display the current distribution and OS family:</p><pre><code class="language-yaml">- hosts: all
  tasks:
    - name: print current distribution
      ansible.builtin.debug:
        msg: &quot;My distribution is {{ ansible_distribution }}

    - name: print current os family
      ansible.builtin.debug:
        msg: &quot;My os family is {{ ansible_os_family }}&quot;</code></pre><p>In addition to the facts provided by Ansible, we can also define our own. This is useful whenever we want to set our own variables at runtime.</p><p>In addition to the facts provided by Ansible, we can also define our own. This is always useful when we want to set our own variables at runtime.</p><p>We use the set_fact module to do this:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  gather_facts: true
  tasks:
    - name: set facts
      ansible.builtin.set_fact:
        my_custom_fact_1: &quot;First fact set at runtime&quot;
        my_custom_fact_2: &quot;Second fact set at runtime&quot;
        another_custom_fact: &quot;Hello World&quot;

    - name: Output
      ansible.builtin.debug:
        msg: &quot;{{ my_custom_fact_1 }}&quot;</code></pre><p>We can use the &quot;set_fact&quot; module to manually define one or more facts. When defining, we can also access facts or variables again:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  gather_facts: true
  tasks:
    - name: set facts
      ansible.builtin.set_fact:
        my_custom_fact: &quot;My Distribution: {% raw %}{{ ansible_distribution }}{% endraw %}&quot;

    - name: Output
      ansible.builtin.debug:
        msg: &quot;{{ my_custom_fact }}&quot;</code></pre><pre><code class="language-text">PLAY [localhost] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [set facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [Output] ****************************************************************************
ok: [ok: [ansible-guide-1]] =&gt; {
    &quot;msg&quot;: &quot;My Distirubtion: Ubuntu&quot;
}

PLAY RECAP ****************************************************************************
ok: [ansible-guide-1]                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
</code></pre><p>The facts defined with the &quot;set_fact&quot; module are only available AFTER the task has been executed. This means that we cannot access facts defined in the same task:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  tasks:
    - name: This is NOT working!
      ansible.builtin.set_fact:
        fact_1: &quot;I am fact_1&quot;
        fact_2: &quot;fact_1: {{ fact_1 }}&quot;</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [This is NOT working!] ****************************************************************************
fatal: [ansible-guide-1]: FAILED! =&gt; {&quot;msg&quot;: &quot;The task includes an option with an undefined variable. The error was: &apos;fact_1&apos; is undefined\n\nThe error appears to be in &apos;/home/sseibold/blog/facts.yml&apos;: line 5, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n  tasks:\n    - name: Das hier funktioniert NICHT!\n      ^ here\n&quot;}

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0</code></pre><p>The task fails because the fact_1 variable is not yet available. This is only the case after the task is run. So the playbook should look like this:</p><pre><code class="language-yaml">- hosts: ansible-guide-1
  tasks:
    - name: This works!
      ansible.builtin.set_fact:
        fact_1: &quot;I am fact_1&quot;

    - name: fact_2 in new Task
      ansible.builtin.set_fact:
        fact_2: &quot;fact_1: {% raw %}{{ fact_1 }}&quot;

    - name: Output
      ansible.builtin.debug:
        msg: &quot;{% raw %}{{ fact_2 }}{% endraw %}&quot;</code></pre><pre><code class="language-text">PLAY [ansible-guide-1] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]

TASK [Das hier funktioniert] ****************************************************************************
ok: [ansible-guide-1]

TASK [fact_2 in neuem Task definieren] ****************************************************************************
ok: [ansible-guide-1]

TASK [Ausgabe] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;fact_1 lautet: ich bin fact_1&quot;
}

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  </code></pre><p>Now that the fact &quot;fact_1&quot; has been defined in a previous task, we can access it in the next task.</p><h2 id="conclusion"><br>Conclusion</h2><p>This brings us to the end of this short chapter in which we have learned what facts are and how to access them. We will be using facts a lot in practical examples throughout this guide.</p><h3 id="what-happens-next"><br>What happens next?</h3><p>In the next part of this series, we will look at return values. As always, I hope this guide has been helpful to you so far.</p><p>I would love to hear your feedback. Just leave a comment here or send me an email!</p><p>See you soon!</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #005 - Variables]]></title><description><![CDATA[<p>Hello and welcome back to the Ansible Starter Guide. In this fifth part of the series, I want to show you how variables work and how we can use them to deal with differences between different systems.</p><p>The simplest use of variables is to store multiple values (such as file</p>]]></description><link>https://mowtomation.com/ansible-101-variables/</link><guid isPermaLink="false">67650f58b8d27000016c1078</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Fri, 20 Dec 2024 08:35:34 GMT</pubDate><content:encoded><![CDATA[<p>Hello and welcome back to the Ansible Starter Guide. In this fifth part of the series, I want to show you how variables work and how we can use them to deal with differences between different systems.</p><p>The simplest use of variables is to store multiple values (such as file paths) in one variable.</p><p>Let&apos;s start with an example. Let&apos;s say we want to serve an index.html and a style.css to our web server. For the sake of clarity, I&apos;m going to put the whole thing in its own playbook. You can also just extend the webservers.yml from the last part.</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">- name: Play without variables
  hosts: webservers
  tasks:
    - name: copy index.html
      ansible.builtin.copy: 
        src: files/index.hmlt
        dest: /var/www/html/index.html
      become: true
      
   - name: copy style.css
     ansible.builtin.copy:
       src: files/style.css
       dest: /var/www/html/style.css
     become: true</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/playbook-without-vars.yml</span></p></figcaption></figure><p>The further we build our playbook, the more we will need to use the &quot;/var/www/html&quot; path. It would be great if we could just put this in a variable. So let&apos;s do that:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">- name: Play with variable
  hosts: webservers
  vars:
    my_docroot: /var/www/html 
  tasks:
    - name: copy index.html
      ansible.builtin.copy: 
        src: files/index.hmlt
        dest: &quot;{{ my_docroot }}/index.html&quot;
      become: true
      
   - name: copy style.css
     ansible.builtin.copy:
       src: files/style.css
       dest: &quot;{{ my_docroot }}/style.css&quot;
     become: true</code></pre><figcaption><p><b><strong style="white-space: pre-wrap;">~/ansible-guide/playbook-with-vars.yml</strong></b></p></figcaption></figure><p>This is the simplest definition of a variable. We have simply defined the variable &quot;my_docroot&quot; in the playbook and can now access it throughout the whole &quot;Playing with Variables&quot; piece. To use variables we use the following format:</p><p> <strong>&quot;{{ variable_name }}&quot;</strong></p><p>It is important to note that we must enclose the entire string in quotes (&quot;). Variables can also be defined at task level. This would look like this:</p><pre><code class="language-yaml">- name: Play mit Variablen auf Taskebene
  hosts: webservers
  tasks:
    - name: copy index.html
      ansible.builtin.copy: 
        src: files/index.html
        dest: &quot;{{ my_docroot }}/index.html&quot;
      become: true
      vars:
        my_docroot: /var/www/html
      
   - name: copy style.css
     ansible.builtin.copy:
       src: files/style.css
       dest: &quot;{{ my_docroot }}/style.css&quot;
     become: true
     vars:     
       my_docroot: /var/www/html</code></pre><p>However, this only makes sense in very special cases, when only one specific task actually requires that variable.</p><p>In the examples above, we use a variable of type &quot;string&quot;, which is a simple string of characters. However, there are other types of variables that we will discuss in more detail later in this guide.</p><h2 id="variable-types">Variable types</h2><pre><code class="language-yaml"># Strings 
my_string: Hello World!

# Numbers
number_of_files: 8

# Float - we always use this when floating point numbers are involved
my_float: 2.5

# Lists - Lists containing one or more entries
files_to_copy:
  - /path/to/file1.txt
  - /path/to/file2.txt

# Dictionary - List with structured data
files_to_copy:
  - name: first file
    path: /path/to/file1.txt
    copy_to: /new/file/path1.txt
  - name: second file
    path: /path/to/file2.txt
    copy_to: /new/file/path2.txt

# Boolean - True ore False
enable_service: true
overwrite_config: false
    </code></pre><h2 id="host-and-group-variables">Host and Group Variables</h2><p>The variable defined in the first example now applies to all hosts with the same value. But what do we do if we want to define different values for each host? Let&apos;s return to our example scenario. Let&apos;s assume that the content of index.html of our two web servers should be different for each host, so that the first one outputs &quot;I am webserver1&quot; and the second one outputs &quot;I am webserver2&quot;.</p><p>To do this, we use host variables (host_vars). To use them, we create a new directory in our example setup:</p><pre><code class="language-bash">cd ~/ansible-guide
mkdir -p host_vars/ansible-guide-1
mkdir -p host_vars/ansible-guide-2</code></pre><p>In each of the created directories we add a file &quot;main.yml&quot;.</p><figure class="kg-card kg-code-card"><pre><code class="language-text">my_welcome_text: I am webserver 1</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/host_vars/ansible-guide-1/main.yml</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-yaml">my_welcome_text: I am webserver 2</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/host_vars/ansible-guide-2/main.yml</span></p></figcaption></figure><p>When we start our playbook, Ansible will automatically read all the files under &quot;host_vars&quot; and assign the variable values to the appropriate hosts. It is important that the names of the directories match the names of the hosts in our inventory.</p><p>To test this, we need to make a small adjustment to our web server playbook. Using the &quot;copy&quot; module, we can define the desired contents of the target file directly, instead of using a source file. It will then look like this:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">- name: webserver setup
  hosts: webservers
  tasks:
    - name: Apache2 Setup
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true
      become: true

    - name: start and enable apache2
      ansible.builtin.systemd:
        name: apache2
        state: started
        enabled: true
      become: true

    - name: copy index.html
      ansible.builtin.copy:
        dest: /var/www/html/index.html
        content: &quot;{{ my_welcome_text }}&quot;
      become: true

    - name: copy apache2.conf
      ansible.builtin.copy:
        src: files/apache2.conf
        dest: /etc/apache2/apache2.conf
      become: true</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/webservers.yml</span></p></figcaption></figure><p>Notice the change in the &quot;copy index.html&quot; task. Instead of defining a source file for the copy module, we enter the desired content of the target file directly, using our variable &quot;my_welcome_text&quot;.</p><p>Then let&apos;s run the playbook:</p><pre><code class="language-bash">cd ~/ansible-guide
ansible-playbook -i inventory.txt webservers.yml</code></pre><pre><code class="language-text">PLAY [webserver setup] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [Apache2 Setup] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [start and enable apache2] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [copy index.html] ****************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

PLAY RECAP ****************************************************************************
ansible-guide-1          : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-guide-2          : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0</code></pre><p>The &quot;copy index.html&quot; task now has the status &quot;modified&quot; on both hosts, and has thus adopted the new content for index.html. We can now check again with curl or the browser to see if we have achieved the desired effect:</p><pre><code class="language-bash">curl http://192.168.0.11
&lt;h1&gt;I am webserver1!&lt;/h1&gt;

curl http://192.168.0.12
&lt;h1&gt;I am webserver2!&lt;/h1&gt;</code></pre><p>BAM! Looks good! So we successfully used host-specific variables.</p><p>The whole thing works with groups as well. For this we use group_vars. We also create a new playbook for this and use the useful debug module to test the variables.</p><p>In part 4 of this guide, we created an inventory with two different groups, &quot;webservers&quot; and &quot;db&quot;. So we will create a playbook that uses exactly those two.</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">- name: group_vars showcase
  hosts:
    - webservers
    - db
  tasks:
    - name: Debug Ausgabe
      debug:
        msg: &quot;{{ test_text }}&quot;</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/groupvars-test.yml</span></p></figcaption></figure><p>The debug module is very useful when we are creating playbooks and want to test them in between. For example, we can use it to display the values of variables. In our case, we define a parameter for the module:</p><p>msg: Any string that will be output during the play.</p><p>Now we just need to define the variable &quot;test_text&quot;. Differently for each group. We create another directory on the same level as &quot;host_vars&quot;:</p><pre><code class="language-bash">mkdir ~/ansible-guide/group_vars
mkdir ~/ansible-guide/group_vars/webservers
mkdir ~/ansible-guide/group_vars/db</code></pre><p>In both directories we add a &quot;main.yml&quot;:</p><figure class="kg-card kg-code-card"><pre><code class="language-yaml">test_text: I am a host in the group &apos;webservers&apos;!</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/group_vars/webservers/main.yml</span></p></figcaption></figure><figure class="kg-card kg-code-card"><pre><code class="language-yaml">test_text: I am a host in the group &apos;db&apos;</code></pre><figcaption><p><span style="white-space: pre-wrap;">~/ansible-guide/group_vars/db/main.yml</span></p></figcaption></figure><p>So we have done the same as with the host_vars, only this time at group level. Now let&apos;s run the new playbook:</p><pre><code class="language-bash">cd ~/ansible-guide
ansible-playbook -i inventory.txt groupvars-test.yml</code></pre><pre><code class="language-text">ASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]
ok: [ansible-guide-3]

TASK [Debug Ausgabe] ****************************************************************************
ok: [ansible-guide-1] =&gt; {
    &quot;msg&quot;: &quot;I am a host in the group &apos;webservers&apos;!&quot;
}
ok: [ansible-guide-2] =&gt; {
    &quot;msg&quot;: &quot;I am a host in the group &apos;webservers&apos;!&quot;
}
ok: [ansible-guide-3] =&gt; {
    &quot;msg&quot;: &quot;I am a host in the group &apos;db&apos;!&quot;
}
PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-guide-2                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-guide-3                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0</code></pre><p>As we can see above, each of the three hosts now pulls the group-specific variable &quot;test_text&quot; and outputs it accordingly.<br></p><h2 id="summary">Summary</h2><p>In this chapter you have learned what variables are and how we can define them in different places. In the next chapter I will introduce you to facts. These are very similar to variables, but there are some differences!</p><p>See you then!</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #004 - Playbooks, tasks and handlers]]></title><description><![CDATA[<p>Welcome back to the Ansible 101 Guide. In this part of the tutorial, I will try to show you how to use playbooks. I will also show you what tasks and handlers are and how we use them. </p><h3 id="playbooks">Playbooks</h3><p>We can use playbooks to define reusable Ansible code. A playbook</p>]]></description><link>https://mowtomation.com/ansible-101-playbooks-tasks-and-handlers/</link><guid isPermaLink="false">67618590b8d27000016c0fca</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Wed, 18 Dec 2024 21:33:46 GMT</pubDate><content:encoded><![CDATA[<p>Welcome back to the Ansible 101 Guide. In this part of the tutorial, I will try to show you how to use playbooks. I will also show you what tasks and handlers are and how we use them. </p><h3 id="playbooks">Playbooks</h3><p>We can use playbooks to define reusable Ansible code. A playbook consists of one or more plays. For example, we could use a playbook called &quot;webserver.yml&quot; to manage our web servers.</p><p>Let&apos;s try to learn the structure of a playbook using the following example:</p><pre><code class="language-yaml">- name: webserver install 
  hosts: webservers
  tasks: 
    - name: install apache2
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: yes
      become: true

    - name: enable and start apache2 systemd service
      ansible.builtin.systemd:
        name: apache2
        enabled: true
        state: started</code></pre><p>Let&apos;s investigate this line by line!</p><h3 id="line-1name">Line 1 - name</h3><p>A descriptive name for the play can be assigned in the &#x2018;name&#x2019; field in line 1. This parameter is optional, but I recommend naming each play as meaningfully as possible.</p><h3 id="line-2hosts">Line 2 - hosts</h3><p>In line 2, we define the target systems for this play with the &#x2018;hosts&#x2019; field. We have several options here:</p><p><strong>Group(s)</strong><br>We can define one or more groups from our inventory as a target:</p><pre><code class="language-yaml">- name: Play for hosts in one group
  hosts: Group1
  tasks:
    - name: Example Task 1
      ansible.builtin.apt:
        name: apache2
        state: present</code></pre><pre><code class="language-yaml">- name: Play for two groups
  hosts: 
    - Group1
    - Group2
  tasks:
    - name: Example Task 1
      ansible.builtin.apt:
        name: apache2
        state: present</code></pre><p><strong>Hosts</strong></p><p>Alternatively, we can also specify the hosts from the inventory directly as the target:</p><pre><code class="language-yaml">- name: Play for one host
  hosts: webserver1
  tasks:
    - name: Example Task 1
      ansible.builtin.apt:
        name: apache2
        state: present</code></pre><pre><code class="language-yaml">- name: Play for two hosts
  hosts: 
    - webserver1
    - webserver2
  tasks:
    - name: Example Task 1
      ansible.builtin.apt:
        name: apache2
        state: present</code></pre><p>There are a few other options in this section that we can use to influence the target list. For example, we can exclude individual hosts or groups from the target definition. More on this in a later article.</p><h3 id="line-3begin-of-task-definitions">Line 3 - Begin of task definitions</h3><p>This is where the list of individual tasks for this play begins. Lets see how a task is build in Ansible.</p><h3 id="lines-4-to-9task-definition">Lines 4 to 9 - Task definition</h3><p>This brings us to the structure of a task. Note the indentation of each element. We distinguish between parameters that belong to the task and parameters that belong to the module.</p><p>The &apos;name&apos; field (line 4) is optional, as with Play, but highly recommended, as it will also appear later in the output. After all, we want to know what is happening. Line 5 is the name of the module we want to use. In this case we want to use the &apos;apt&apos; module to install a package. Everything that appears one indent further down is a module-level parameter, i.e. it refers to the module, not the task itself. This is where many people get confused.</p><p>Task parameters are parameters that affect the task itself, i.e. they can be defined for each task. Module parameters, on the other hand, are different for each module. For example, the Copy module needs information about the source and destination paths, while the &apos;apt&apos; module needs the name of the package to be installed.</p><p>In our example, we use the &apos;apt&apos; module to install Apache2 with the following parameters</p><ul><li>name: name of the package to install</li><li>state: target state:<ul><li>present = installed</li><li>absent = uninstalled</li><li>latest = always keep on latest version</li></ul></li><li>update_cache: forces to package cache to be updated beforehand</li></ul><p>Line 9 contains the definition of another task level parameter (note the indentation). With &apos;become: true&apos; we basically specify that superuser privileges are required to run this task. We will need this parameter very often, so I will devote a separate part of this series to it.</p><h3 id="a-note-on-module-parameters">A note on module parameters</h3><p>It is important to note that there are some module parameters that are optional and some that must be specified. However, the Ansible documentation is very helpful here. For our example, we can find the information on these pages:</p><p><strong>Documentation for apt module</strong></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html"><div class="kg-bookmark-content"><div class="kg-bookmark-title">ansible.builtin.apt module &#x2013; Manages apt-packages &#x2014; Ansible Community Documentation</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_Black.png" alt></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_White.png" alt></div></a></figure><p><strong>Documentation for systemd module</strong></p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/systemd_module.html"><div class="kg-bookmark-content"><div class="kg-bookmark-title">ansible.builtin.systemd module &#x2014; Ansible Community Documentation</div><div class="kg-bookmark-description"></div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_Black.png" alt></div></div><div class="kg-bookmark-thumbnail"><img src="https://docs.ansible.com/ansible/latest/_static/images/Ansible-Mark-RGB_White.png" alt></div></a></figure><h2 id="example">Example </h2><p>Let us now apply what we have learned to our example scenario. Let&apos;s assume that our first two hosts are web servers and the third is a database host.</p><p>We will therefore adapt our inventory.txt accordingly:</p><p><strong>~/ansible-guide/inventory.txt</strong></p><pre><code class="language-text">[webservers]
ansible-guide-1 ansible_ssh_user=ansible ansible_host=192.168.0.11
ansible-guide-2 ansible_ssh_user=ansible ansible_host=192.168.0.12

[db]
ansible-guide-3 ansible_ssh_user=ansible ansible_host=192.168.0.13</code></pre><p>I would now like to divide the setup into two playbooks. Depending on your own preferences and use case, we could also define two plays within one playbook.</p><p><strong>Playbook 1: ~/ansible-guide/webservers.yml</strong></p><pre><code class="language-yaml">- name: webserver setup
  hosts: webservers
  tasks:
    - name: Apache2 Setup
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true
      become: true

    - name: start and enable apache2
      ansible.builtin.systemd:
        name: apache2
        state: started
        enabled: true
      become: true</code></pre><p>Now we can run the playbooks:</p><pre><code class="language-bash">cd ~/ansible-guide
ansible-playbook -i inventory.txt webservers.yml</code></pre><pre><code class="language-text">PLAY [webserver setup] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [Apache2 Setup] ****************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

TASK [start and enable apache2] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]


PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
ansible-guide-2                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

</code></pre><p>It is interesting to note that the task &apos;start and enable apache2&apos; returns the status &apos;ok&apos; and not &apos;changed&apos; as expected. This is because enabling and starting the service is already defined in the installation package. So there is nothing for Ansible to do at this point.</p><p>Now we can quickly test that our installation has worked by entering one of the IP addresses in a browser. We should now see the default Apache web server page.</p><p><strong>Playbook 2: ~/ansible-guide/database.yml</strong></p><pre><code class="language-yaml">- name: database setup
  hosts: db
  tasks:
    - name: MySQL Setup
      ansible.builtin.apt:
        name: mysql-server
        state: present
        update_cache: true
      become: true

    - name: start and enable MySQL
      ansible.builtin.systemd:
        name: mysql
        state: started
        enabled: true
      become: true</code></pre><p>We now do the same with the database server playbook:</p><pre><code class="language-bash">cd ~/ansible-guide
ansible-playbook -i inventory.txt database.yml</code></pre><pre><code class="language-text">PLAY [database setup] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-3]

TASK [MySQL Setup] ****************************************************************************
changed: [ansible-guide-3]

TASK [start and enable MySQL] ****************************************************************************
ok: [ansible-guide-3]

PLAY RECAP ****************************************************************************
ansible-guide-3                 : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   </code></pre><h3 id="deploy-customized-indexhtml">Deploy customized index.html</h3><p>We have now done a standard installation of Apache2 and MySQL using Ansible. In the next step, I will show you how we can deploy our own index.html file, for example.</p><p>To do this, we will first create the file locally on the Ansible controller:</p><pre><code class="language-bash">cd ~/ansible-guide
mkdir files
echo &quot;&lt;h1&gt;My own index.html!&lt;/h1&gt;&quot; &gt; files/index.html </code></pre><p>This results in the following file content:</p><pre><code class="language-text">&lt;h1&gt;My own index.html!&lt;/h1&gt;</code></pre><p>Now we add a task to our webservers.yml playbook:</p><pre><code class="language-yaml">- name: webserver setup
  hosts: webservers
  tasks:
    - name: Apache2 Setup
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true
      become: true

    - name: start and enable apache2
      ansible.builtin.systemd:
        name: apache2
        state: started
        enabled: true
      become: true

    - name: copy index.html
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      become: true</code></pre><p>We are using the Ansible &apos;copy&apos; module here. This can be used to copy files from the Ansible controller to the target systems. We give the module 2 parameters:</p><pre><code class="language-text">src: Source file path on the Ansible controller
dest: destination path on the remote hosts
</code></pre><p>We also tell Ansible again with &apos;become: true&apos; that root permissions are required for this process.</p><p>We now run the updated playbook:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt webservers.yml</code></pre><pre><code class="language-text">LAY [webserver setup] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [Apache2 Setup] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [start and enable apache2] ****************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [copy index.html] ****************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

PLAY RECAP ****************************************************************************
ansible-guide-1                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ansible-guide-2                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

</code></pre><p>Let&apos;s check that with curl:</p><pre><code class="language-apt install -y curl">apt install -y curl
curl http://192.168.0.11</code></pre><pre><code>&lt;h1&gt;My own index.html!&lt;/h1&gt;
</code></pre><h3 id="handlers">Handlers</h3><p>We have now installed our web server and a database. We have also uploaded our own content to our web server. Finally , I would like to show you how we can also manage the Apache configuration file using Ansible. We want to notify the<br>web server when changes are made to it (and only then!). For this<br>handlers.</p><p>So we store the Apache configuration on the<br>Ansible controller. To do this, I simply copied the contents of /etc/apache2 from one of the test hosts<br>I simply copied the contents of /etc/apache2/apache2.conf from one of the test hosts and trimmed the<br>comment lines a little to make the whole thing a little more<br>clearer.</p><p>We put it back in our files subdirectory:</p><h5 id="ansible-guidefilesapache2conf">~/ansible-guide/files/apache2.conf</h5><pre><code class="language-text">DefaultRuntimeDir ${APACHE_RUN_DIR}
PidFile ${APACHE_PID_FILE}

Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}

HostnameLookups Off

ErrorLog ${APACHE_LOG_DIR}/error.log
LogLevel warn

IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf

Include ports.conf

&lt;Directory /&gt;
	Options FollowSymLinks
	AllowOverride None
	Require all denied
&lt;/Directory&gt;

&lt;Directory /usr/share&gt;
	AllowOverride None
	Require all granted
&lt;/Directory&gt;

&lt;Directory /var/www/&gt;
	Options Indexes FollowSymLinks
	AllowOverride None
	Require all granted
&lt;/Directory&gt;

AccessFileName .htaccess

&lt;FilesMatch &quot;^\.ht&quot;&gt;
	Require all denied
&lt;/FilesMatch&gt;

LogFormat &quot;%v:%p %h %l %u %t \&quot;%r\&quot; %&gt;s %O \&quot;%{Referer}i\&quot; \&quot;%{User-Agent}i\&quot;&quot; vhost_combined
LogFormat &quot;%h %l %u %t \&quot;%r\&quot; %&gt;s %O \&quot;%{Referer}i\&quot; \&quot;%{User-Agent}i\&quot;&quot; combined
LogFormat &quot;%h %l %u %t \&quot;%r\&quot; %&gt;s %O&quot; common
LogFormat &quot;%{Referer}i -&gt; %U&quot; referer
LogFormat &quot;%{User-agent}i&quot; agent

IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf</code></pre><p>We extend our playbook with another task:</p><p><strong>~/ansible-guide/webservers.yml</strong></p><pre><code class="language-yaml">- name: webserver setup
  hosts: webservers
  tasks:
    - name: Apache2 Setup
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true
      become: true

    - name: start and enable apache2
      ansible.builtin.systemd:
        name: apache2
        state: started
        enabled: true
      become: true

    - name: copy index.html
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      become: true

    - name: copy apache2.conf
      ansible.builtin.copy:
        src: files/apache2.conf
        dest: /etc/apache2/apache2.conf
      become: true</code></pre><p>If we were to run the whole thing again now, Ansible would copy both apache2.conf and index.html to the target systems. However, we also want to implement the automatic restart of the Apache service when changes are made. So we will add a handler and call it directly in the new task. I have added comments to the most important lines:</p><pre><code class="language-yaml">- name: webserver setup
  hosts: webservers
  handlers:                     
    - name: restart-apache      
      ansible.builtin.systemd:
        name: apache2
        state: restarted
      become: true
  tasks:
    - name: Apache2 Setup
      ansible.builtin.apt:
        name: apache2
        state: present
        update_cache: true
      become: true

    - name: start and enable apache2
      ansible.builtin.systemd:
        name: apache2
        state: started
        enabled: true
      become: true

    - name: copy index.html
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      become: true

    - name: copy apache2.conf
      ansible.builtin.copy:
        src: files/apache2.conf
        dest: /etc/apache2/apache2.conf
      notify: restart-apache                 
      become: true</code></pre><p>We have now created another list on the same level as &quot;Tasks&quot;. This contains our handler definitions. A handler has exactly the same structure as a task, but is not automatically executed when the playbook is called. This only happens if at least two things are specified:</p><ul><li>A task has the name of our handler - here &quot;restart-apache&quot; - defined in the &quot;notify&quot; parameter.</li><li>At least one task that fulfills point 1 returns the status &quot;changed&quot;. Only then will the handlers be executed.</li></ul><p>Now we run the playbook again:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt webserver.yml</code></pre><pre><code class="language-bash">PLAY [webserver setup] ***********************************************************************************

TASK [Gathering Facts] ***********************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [Apache2 Setup] ***********************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [start and enable apache2] ***********************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [copy index.html] ***********************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]

TASK [copy apache2.conf] ***********************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

RUNNING HANDLER [restart-apache] ***********************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]

PLAY RECAP ***********************************************************************************
ansible-guide-1                  : ok=6    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 
ansible-guide-2                  : ok=6    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 </code></pre><p>The apache2.conf is now also managed by Ansible and the service will be restarted if necessary! Try this out a few more times by changing some parameters in the configuration and running the playbook.</p><h3 id="important-note-about-handlers">Important note about handlers</h3><p>As you can see in the output above, handlers are always executed at the end of the play and not immediately after the calling task. This ensures that, for example, the Apache restart is only executed once, even if multiple tasks make changes and call the handler.</p><p>If you want to explicitly restart at this point, you must define the restart as a task.</p><p>That&apos;s it for now!</p><p>In the next part I will show you how to put values into variables and where you can define them. I will also introduce you to some other useful modules!</p><p>From my own experience, I can strongly recommend that you just try it all out for yourself and try to apply what you have learned to your own use cases. It&apos;s the best way to learn :)</p><p>As always, I welcome feedback and suggestions for improvement. Feel free to use the comment function here or send an email to: <strong>dermow@posteo.de.</strong></p><p>Have fun trying it out!</p><p>Mow</p>]]></content:encoded></item><item><title><![CDATA[Ansible 101: #003 - Getting started]]></title><description><![CDATA[<p>Hello everyone! We&apos;re finally getting  started with Ansible. Lets start with a practical setup.</p><h3 id="my-example-setup"><br>My example setup</h3><p>To illustrate this part with practical examples, I have set up a small test scenario. This will remain basically the same for the rest of the guide, but will be expanded</p>]]></description><link>https://mowtomation.com/ansible-101-getting-started/</link><guid isPermaLink="false">675b4440b8d27000016c0f74</guid><category><![CDATA[Ansible]]></category><dc:creator><![CDATA[mow]]></dc:creator><pubDate>Thu, 12 Dec 2024 20:56:49 GMT</pubDate><content:encoded><![CDATA[<p>Hello everyone! We&apos;re finally getting  started with Ansible. Lets start with a practical setup.</p><h3 id="my-example-setup"><br>My example setup</h3><p>To illustrate this part with practical examples, I have set up a small test scenario. This will remain basically the same for the rest of the guide, but will be expanded to include additional hosts.</p><p>If you feel like it, feel free to create a similar scenario and just try out the examples.</p><p>Let&apos;s start with this:</p><pre><code class="language-text">My laptop running Ubuntu Desktop 20.04 LTS and Ansible version 2.9.6 as the Ansible controller.

Three VMs running Ubuntu Server 20.04 LTS as targets:
    ansible-guide-1 (192.168.0.11)
    ansible-guide-2 (192.168.0.12)
    ansible-guide-3 (192.168.0.13)
</code></pre><p>The target user for all three hosts is &apos;ansible&apos;. This user has full sudo privileges, i.e. is not a root user itself, but can execute commands with root privileges using the &apos;sudo&apos; command, short for &apos;super user do&apos;. Ansible also uses this mechanism. But more on that in a moment.</p><h3 id="setting-up-ssh-public-key-authentication">Setting up SSH public key authentication</h3><p>In theory, Ansible can do password authentication, but this is not really useful and is also very time consuming for a certain number of target hosts. The easiest and most secure option is to use SSH keys. We create a key pair consisting of a private and a public key, and store the public part (public key) on the target systems.</p><p>We store the private key securely on our client.</p><p>Assuming that SSH key authentication has not yet been set up, but is done using a password, the following steps need to be taken:<br></p><h3 id="create-a-key-pair-on-the-local-client">Create a key pair on the local client</h3><pre><code class="language-bash">ssh-keygen -t rsa -b 4096</code></pre><p>the output will look similar to this:</p><pre><code class="language-text">Generating a public/private rsa key pair.
Specify the file to store the key in (/home/mow/.ssh/id_rsa): /home/mow/.ssh/id_rsa
Enter the passphrase (empty for no passphrase):
Enter the same passphrase again:
Your identification is stored in /home/mow/.ssh/id_rsa
Your public key is stored in /home/mow/.ssh/id_rsa.pub
The key fingerprint is
SHA256:uIRqKu6ySijEPaHeNrkNJrs/PmzTH6T+0+WB9F+GKek mow@ubuntu1
The random image of the key is
+---[RSA 4096]----+
| |
| |
| . |
|. o .. . . |
| + o. o.S o . o |
|+ ..o.o. . * o o |
|o++B..... + + o |
|=o=**. ... E . |
|X*=++ooo. |
-&#x2014;[SHA256]--&#x2014;+</code></pre><p>What have we done? We have created a new SSH key pair. This is of the RSA type (-b rsa) and 4096 bytes long (-b 4096).</p><p>Important: For keys used in a production environment, I strongly recommend using a passphrase, as a potential attacker cannot do anything with the private key alone. However, this is not absolutely necessary for this guide.</p><p>Now that we are the proud owners of a freshly printed SSH key pair, we want to store the public part of it, i.e. the public key, on the target systems. A useful tool for this is &apos;ssh-copy-id&apos;.</p><p>We run the following command on our Ansible controller</p><pre><code class="language-bash">ssh-copy-id -i ~/.ssh/id_rsa ansible@ansible-guide-1</code></pre><p>We will then be prompted for the password of the target user. The public key is then stored on the target system. Of course, we repeat this process for the rest of the systems.</p><p>If everything has worked, we should be able to connect to the target systems without a password:</p><p>ssh ansible@ansible-guide-1<br>ssh ansible@ansible-guide-2<br>ssh ansible@ansible-guide-3</p><p>And now we&apos;re ready to start using Ansible!</p><h3 id="creating-a-project-directory-and-inventory">Creating a project directory and inventory</h3><p>Let&apos;s start with a project directory and an inventory. Although we could start with the default inventory (/etc/ansible/hosts) and any directory, I always recommend using a separate directory for each Ansible project.</p><p>So first we create the project directory:</p><h3 id="create-a-directory-in-your-home">Create a directory in your home</h3><pre><code class="language-bash">mkdir ~/ansible-guide</code></pre><p>Then create a file in ~/ansible-guide/inventory.txt with the following content</p><pre><code class="language-text">[guide]
ansible-guide-1 ansible_ssh_user=ansible ansible_host=192.168.0.11
ansible-guide-2 ansible_ssh_user=ansible ansible_host=192.168.0.12
ansible-guide-3 ansible_ssh_user=ansible ansible_host=192.168.0.13</code></pre><p>In this case we have created an inventory with a &apos;guide&apos; group. This contains our 3 test hosts. With &apos;ansible_ssh_user=ansible&apos; we specify the user to use for the SSH connection.</p><p>If the hostnames cannot be resolved to IPs via DNS, we use &apos;ansible_host=***&apos; to specify the target IPs of the hosts.</p><p>This can all be done a bit more easily, but should be enough for now. After all, we still want to go into detail about inventories!</p><h3 id="adhoc-commands">AdHoc commands</h3><p>A term I unfortunately forgot to mention in the first article. So I&apos;ll make up for it here:</p><p>AdHoc commands can be used to perform specific tasks with Ansible at the command line level. They are very easy to use and are especially useful when you need to do something quickly on multiple hosts at once.</p><p>Although I always recommend using reusable playbooks for smaller tasks, the AdHoc commands are very useful for initial Ansible testing.</p><p>So let&apos;s see if we have our SSH connection configured correctly. Ansible has a ping module for this:</p><pre><code class="language-text"># Syntax
# ansible &lt;hosts&gt; -i &lt;inventory&gt; -m &lt;modul&gt;
cd ~/ansible-guide

ansible guide -i inventory.txt -m ping</code></pre><p>If all went well, the output should look something like this</p><pre><code class="language-text">ansible-guide-1 | SUCCESS =&gt; {
ansible_facts&quot;: {
&apos;discovered_interpreter_python&quot;: &apos;/usr/bin/python&apos;
},
&apos;changed&apos;: false,
&apos;ping&apos;: &apos;pong&apos;
}
ansible-guide-2 | SUCCESS =&gt; {
&apos;ansible_facts&apos;: {
&apos;discovered_interpreter_python&apos;: &apos;/usr/bin/python&apos;
},
&apos;changed&apos;: false,
&apos;ping&apos;: &apos;pong&apos;
}
ansible-guide-3 | SUCCESS =&gt; {
&apos;ansible_facts&apos;: {
&apos;discovered_interpreter_python&apos;: &apos;/usr/bin/python&apos;
},
&apos;changed&apos;: false,
&apos;ping&apos;: &apos;pong&apos;
}</code></pre><p>Perfect, we can now manage our three systems with Ansible!</p><p>With another ad hoc command, we could now display information about the operating system, for example:</p><pre><code class="language-text"># Syntax ansible &lt;hosts&gt; -i &lt;inventory&gt; -m &lt;modul&gt; -a &lt;parameter&gt;

ansible guide -i inventory.txt -m command -a &quot;cat /etc/os-release&quot;</code></pre><p>So we run Ansible again with our &apos;guide&apos; group, this time using the &apos;command&apos; module and giving it the parameter &apos;cat /etc/os-release&apos;.</p><p>The output should look something like this:</p><pre><code class="language-text">ansible-guide-1 | CHANGED | rc=0 &gt;&gt;
NAME=&quot;Ubuntu&quot;
VERSION=&quot;20.04.1 LTS (Focal Fossa)&quot;
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME=&quot;Ubuntu 20.04.1 LTS&quot;
VERSION_ID=&quot;20.04&quot;
HOME_URL=&quot;https://www.ubuntu.com/&quot;
SUPPORT_URL=&quot;https://help.ubuntu.com/&quot;
BUG_REPORT_URL=&quot;https://bugs.launchpad.net/ubuntu/&quot;
PRIVACY_POLICY_URL=&quot;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy&quot;
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

ansible-guide-2 | CHANGED | rc=0 &gt;&gt;
NAME=&quot;Ubuntu&quot;
VERSION=&quot;20.04.1 LTS (Focal Fossa)&quot;
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME=&quot;Ubuntu 20.04.1 LTS&quot;
VERSION_ID=&quot;20.04&quot;
HOME_URL=&quot;https://www.ubuntu.com/&quot;
SUPPORT_URL=&quot;https://help.ubuntu.com/&quot;
BUG_REPORT_URL=&quot;https://bugs.launchpad.net/ubuntu/&quot;
PRIVACY_POLICY_URL=&quot;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy&quot;
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

ansible-guide-3 | CHANGED | rc=0 &gt;&gt;
NAME=&quot;Ubuntu&quot;
VERSION=&quot;20.04.1 LTS (Focal Fossa)&quot;
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME=&quot;Ubuntu 20.04.1 LTS&quot;
VERSION_ID=&quot;20.04&quot;
HOME_URL=&quot;https://www.ubuntu.com/&quot;
SUPPORT_URL=&quot;https://help.ubuntu.com/&quot;
BUG_REPORT_URL=&quot;https://bugs.launchpad.net/ubuntu/&quot;
PRIVACY_POLICY_URL=&quot;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy&quot;
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal</code></pre><h3 id="our-first-playbook">Our first playbook!</h3><p>Now it&apos;s time to create our first playbook. Let&apos;s say we want to install the &apos;htop&apos; package on all three systems. I will only say a few words about the contents here, as we will go into more detail about playbooks in the next articles.</p><p>Our simple playbook looks like this</p><pre><code class="language-yml"># /home/mow/ansible-guide/playbook-htop.yml
- hosts: guide
  tasks:                     
    - name: install htop 
      become: true 
      ansible.builtin.apt: 
        name: htop 
        state: present
</code></pre><p>To run the whole thing, we need another command:</p><pre><code class="language-bash">ansible-playbook -i inventory.txt playbook-htop.yml</code></pre><p>And this is what the output looks like:</p><pre><code class="language-text">PLAY [guide] **************************************************************************************************************************************************************************************************

TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [ansible-guide-1]
ok: [ansible-guide-2]
ok: [ansible-guide-3]

TASK [install htop] ***********************************************************************************************************************************************************************************************
changed: [ansible-guide-1]
changed: [ansible-guide-2]
changed: [ansible-guide-3]
]
PLAY RECAP ********************************************************************************************************************************************************************************************************
ansible-guide-1                  : ok=2    changed=1    unreachable=0 failed=0    skipped=0    rescued=0    ignored=0
ansible-guide-2                  : ok=2    changed=1    unreachable=0 failed=0    skipped=0    rescued=0    ignored=0 
ansible-guide-3                  : ok=2    changed=1    unreachable=0  failed=0    skipped=0    rescued=0    ignored=0</code></pre><h3 id="task-states">Task states</h3><p>To conclude this section, I would like to briefly discuss some of the states a task can take. You may have noticed that some of our tasks return &apos;ok&apos; and others return &apos;changed&apos;.</p><p>A task will always return &apos;ok&apos; if Ansible did not need to make any changes to create the target state. Of course, the opposite is true for the &apos;changed&apos; state.</p><p>But wait a minute! What bullshit is this guy talking? In the example, we did a &apos;cat /etc/os-relase&apos;, so we are just printing the contents of a file. So why does the task say &apos;modified&apos;?</p><p>This is because the &apos;command&apos; module does nothing more than blatantly execute a shell command. Since Ansible has no way of &apos;knowing&apos; what this does in turn (it could be a script or something else), it automatically assumes a change.</p><p>Hence, an important tip that you will hear from me many times:</p><p>Use the command (or shell) module ONLY IF YOU REALLY HAVE TO! Whenever possible, we should try to use modules where Ansible can track changes. This way we can always track the state of our environment, even with more complex playbooks.</p><p>There are other states besides ok and modified, but we will look at these in more detail in due course.</p><p></p><p>Thats it for now!</p><p>Mow</p>]]></content:encoded></item></channel></rss>