Skip to content

X386 – Ubuntu & Python

Ubuntu and Python Documentation

  • Home
  • About
  • Contact
  • License
  • Privacy Policy

Ansible On Ubuntu

Posted on 28/11/2020 - 07/05/2021 by exforge

AnsibleOnUbuntu – Ansible Tutorial for Ubuntu Linux

Copyright (C) 2020 Exforge exforge@x386.xyz

# This document is free text: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# This document is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

0. Specs

# 0.0. Servers managed from a workstation
#   This tutorial aims to bring you to a moderate level using Ansible. (Almost) All 
#      examples are tested and verified as working. There might be slight mistakes and
#      you can think of them as small challenges. 
#   This tutorial is about using Ansible on Ubuntu (and Debian) servers, but I believe
#      you can apply most of the examples to other servers like Centos.
#   I am not an expert of Ansible. Actually I prepared this tutorial while I was 
#     learning it. 
#
# 0.1. Workstation: lahana -> Ubuntu 20.04 LTS Desktop
# 0.2. Servers:
#        Local Virtual Servers:
#          test201, test202, test203 -> Ubuntu 20.04 LTS Server
#          test2010                  -> Ubuntu 20.10 Server
#          debian10                  -> Debian 10 Server
#        
# 0.3. Resources:
# Book: 978-1-4842-1660-6 Ansible From Beginner to Pro by Michael Heap
# Book: 978-1-78899-756-0 Mastering Ubuntu Server Second Edition by Jay LaCroix
https://docs.ansible.com/ansible/
https://www.howtoforge.com/ansible-guide-ad-hoc-command/
https://www.golinuxcloud.com/ansible-tutorial/
 

1. Installation and Main Configuration

# 1.1. Install ansible on workstation
sudo apt update
sudo apt install ansible
#
# 1.2. Create ansible user on all the servers and on the workstation
# !!!Run on workstation and on all servers!!!
#  Create user ansible and give it a password 
sudo useradd -d /home/ansible -m ansible -s /bin/bash
sudo passwd ansible
#  add it to the sudo group
sudo usermod -aG sudo ansible
#  make sure it is added
getent group sudo
#
# 1.3. Copy workstation's ansible user's ssh key to servers
# !!!Run only on workstation!!!
#   Change to ansible user
sudo su ansible
# Create SSH keys, leave passfield empty
ssh-keygen
# Copy ansible users SSH key to the servers
ssh-copy-id -i ~/.ssh/id_rsa.pub test201
ssh-copy-id -i ~/.ssh/id_rsa.pub test202
ssh-copy-id -i ~/.ssh/id_rsa.pub test203
ssh-copy-id -i ~/.ssh/id_rsa.pub test2010
ssh-copy-id -i ~/.ssh/id_rsa.pub debian10
#  Now we can ssh to servers with ansible user without password
#
# 1.4. On all servers, configure ansible user to sudo without password
# !!!Run on all servers!!!
#    create /etc/sudoers.d/ansible file
sudo nano /etc/sudoers.d/ansible
#  put the following line on it
#__________________
ansible ALL=(ALL) NOPASSWD: ALL
#__________________
#  make the file owned by root
sudo chown root:root /etc/sudoers.d/ansible
#   change the permissions of file 
sudo chmod 440 /etc/sudoers.d/ansible
# All the preliminary work is completed
# From now on all commands will be run on workstation
 

2. Configuration

# 2.1. Configuration File
# Ansible looks for the configuration file in the following order:
#  - File specified by the ANSIBLE_CONFIG environment variable
#  - ./ansible.cfg (ansible.cfg in the current directory)
#  - ~/.ansible.cfg (.ansible.cfg in your home directory)
#  - /etc/ansible/ansible.cfg
# My choice is to use the 3. option
# First change to user ansible (If you haven't done already)
sudo su ansible
nano /home/ansible/.ansible.cfg
#_________________________
[defaults]
inventory = .hosts
remote_user = ansible
roles_path = /home/ansible/ansible/playbooks
forks = 5
#_________________________
# We stated as:
#   our hosts file will be /home/ansible/.hosts
#   the remote user to use on servers is ansible
#   look to /etc/ansible/roles for extra roles
#   maximum 5 parallel tasks between the workstation and servers
# There are numerous thing to be configured, you may check them
#   at file /etc/ansible/ansible.cfg
#
# 2.2. Making a home for Ansible files
# I prefer placing all ansible files on /home/ansible/ansible
mkdir /home/ansible/ansible
# And a subdirectory for playbook (explained later)
mkdir /home/ansible/ansible/playbooks
#
# 2.3. Inventory File
# Create a clean inventory file
touch /home/ansible/.hosts
# Change ownership and permissions for ansible user
sudo chown ansible /home/ansible/.hosts
sudo chmod 600 /home/ansible/.hosts
# Populate the file with server IPs or names
nano /home/ansible/.hosts
#________________________
[ubuntu20]
test201.x386.xyz
test202.x386.xyz
test203.x386.xyz
test2010.x386.xyz
#
[debian]
debian10.x386.xyz
#
[internet]
hostens
hostens2
#
[local]
ubuntu
#________________________
# As in our sample, you can group hosts
#
# 2.4. A simple test
# Ping all our servers
ansible all -m ping
#   with full verbose
ansible all -m ping -vvvv
 

3. More on Inventory

# 3.1. it is possible to use a different inventory for each ansible or ansible-playbook
#   command:
ansible all –i /path/to/inventory –m ping
#
# 3.2. to use a different ssh port
host1.example.com:50822
#
# 3.3. Using ranges in host file names
host[1:3].example.com
host[a:d][a:z].example.com
#
# 3.4. Using options for user name, ssh port, ssh private key
alpha.example.com ansible_user=bob ansible_port=50022
bravo.example.com ansible_user=mary ansible_ssh_private_key_file=/path/to/mary.key
frontend.example.com ansible_port=50022
yellow.example.com ansible_host=192.168.33.10
#
# 3.5. If you want to use more than 1 inventory file you can put all your inventories 
#   in a directory and specify the directory as the inventory file. Below is a simple 
#   example.
sudo su ansible
mkdir /home/ansible/ansible/inventory
nano /home/ansible/ansible/inventory/inventory1
#__________________________________
test201
#__________________________________
nano /home/ansible/ansible/inventory/inventory2
#__________________________________
test203
#__________________________________
ansible all -i /home/ansible/ansible/inventory -m ping
#
# 3.6. If you want to use a dynamic host file, you can use a program which outputs the 
#   inventory. #   Then you can give your program as inventory file. Below is a very 
#   simple example.
sudo su ansible
nano /home/ansible/ansible/inventory.py
#______________________________________
#!/usr/bin/env python3
print("test201")
print("test202")
#______________________________________
chmod +x /home/ansible/ansible/inventory.py
ansible all -i /home/ansible/ansible/inventory.py -m ping
#
# 3.7. Needless to say; you can combine dynamic and static inventories, by combining 
#   methods in 3.5. and 3.6.
#
# 3.8. Groups of Groups
# You can create master groups to include other groups. Master groups require
#   children keyword.
# In my inventory, if I want to combine all local servers (virtual or not) I would
#   modify my inventory file as follows:
#________________________
[ubuntu20]
test201.x386.xyz
test202.x386.xyz
test203.x386.xyz
test2010.x386.xyz
#
[debian]
debian10.x386.xyz
#
[internet]
hostens
hostens2
#
[local]
ubuntu
#
[localservers:children]
ubuntu20
debian
local
#________________________
#
# 3.9. Inventory Variables
# You can define variables in inventory file. They might be host or group based.
#   For group based variables; var keyword is used. 
# Below, using my inventory file, I created a variable named role for a group and 
#   a host. 
[ubuntu20]
test201.x386.xyz
test202.x386.xyz
test203.x386.xyz
test2010.x386.xyz
#
[debian]
debian10.x386.xyz
#
[internet]
hostens role=webserver
hostens2
#
[local]
ubuntu
#
[ubuntu20:vars]
role="dbserver"
#________________________
# That way, using ansible, you can install apache to servers with webserver role and
#   install mariadb to servers with role dbserver.
 

4. Ansible Ad Hoc Commands

# You can run Ansible commands in 2 ways, 1 is direct (adhoc), 2 through playbooks. 
#   In the next section we will work on our first playbook. 
# Ad hoc commands might be suitable for one time tasks. For recurring tasks it would 
#   be wise to use playbooks.
# 4.1. Ping host(s)
# Actually we ran our 1st command at 2.4. Ping all hosts in default inventory.
ansible all -m ping
# We can specify another inventory
ansible all -i /home/ansible/ansible/inventory.py
ansible all -i /home/ansible/ansible/inventory -m ping
#
# 4.2. Run a shell command on hosts
ansible all -m shell -a "ls -al"
# -m can be ommitted
ansible test201 -a "ls -al"
# List of open ports on the servers
ansible all -m shell -a 'netstat -plntu' --become
#
# 4.3. File and Directory Operations
# Copy a file to servers
ansible all -m copy -a "src=/etc/apache2/apache2.conf dest=/tmp/apache2.conf"
# Create a directory on server
ansible all -m file -a "dest=/tmp/test mode=777 owner=ansible group=ansible state=directory"
# Delete a file or directory on server
ansible all -m file -a "dest=/home/ansible/test state=absent"
# Copy a file from server
ansible ubuntu20 -m fetch -a "src=/var/www/html/index.html dest=/home/ansible/backup flat=yes"
#
# 4.4. Reboot Servers
# Reboot all servers
ansible all -a "/sbin/reboot"
# Reboot all servers in 10 parallel forks (default is 5)
ansible all -a "/sbin/reboot" -f 10
# Reboot all servers with sudo privilege
ansible all -a "/sbin/reboot" --become
# Reboot all servers with sudo privilege, manually enter sudo password
ansible all -a "/sbin/reboot" --become --ask-become-pass
#
# 4.5. User Management
# Add a user
ansible all -m ansible.builtin.user -a "name=foo"
# Remove a user
ansible all -m ansible.builtin.user -a "name=foo state=absent"
#
# 4.6. Package Management (apt)
# Add a ppa repository
ansible ubuntu20 -m apt_repository -a "repo=ppa:ondrej/php state=present" --become 
# Remove a ppa repository
ansible ubuntu20 -m apt_repository -a "repo=ppa:ondrej/php state=absent" --become
# Update cache (apt update)
ansible ubuntu20 -m apt -a "update_cache=yes" --become
# update cache and upgrade all modules (apt update && apt upgrade)
ansible ubuntu20 -m apt -a "upgrade=dist update_cache=yes" --become
# Install apache (don't do anything if it is already installed)
ansible ubuntu20 -m apt -a "name=apache2 state=present" --become 
#  or
ansible ubuntu20 -m apt -a "name=apache2 state=installed" --become 
# Install apache, if it is already installed, update it
ansible ubuntu20 -m apt -a "name=apache2 state=latest" --become
# Remove apache
ansible ubuntu20 -m apt -a "name=apache2 state=absent" --become
# Remove apache and remove all configuration about it
ansible ubuntu20 -m apt -a "name=apache2 state=absent purge=yes" --become
# Remove apache, remove all configuration about it, and also remove all unused packages
ansible ubuntu20 -m apt -a "name=apache2 state=absent purge=yes autoremove=yes" --become
#
# 4.7. Service Management
# Start and Enable Apache service
ansible ubuntu20 -m service -a "name=apache2 state=started enabled=yes" --become
# Stop Apache service
ansible ubuntu20 -m service -a "name=apache2 state=stopped" --become
# Restart Apache service
ansible ubuntu20 -m service -a "name=apache2 state=restarted" --become
 

5. A Simple Playbook to Install Apache

# Playbooks are files in YAML format. They contains commands
#   to run by Ansible.
# Our playbook will install apache, and prepare a sample 
#   homepage containing the host name
# 5.1. Create directories
sudo su ansible
mkdir /home/ansible/ansible/playbooks/apache
mkdir /home/ansible/ansible/playbooks/apache/templates
cd /home/ansible/ansible/playbooks/apache
#
# 5.2. Create ansible file and index.html template
nano /home/ansible/ansible/playbooks/apache/apache.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Create webserver with apache
  become: True
  hosts: ubuntu20
  tasks:
  - name: install apache
    apt: name=apache2 update_cache=yes
  - name: copy index.html
    template: src=templates/index.html.j2 dest=/var/www/html/index.html
      mode=0644
  - name: restart apache
    service: name=apache2 state=restarted
#_____________________________________________________
nano /home/ansible/playbooks/apache/templates/index.html.j2
#_____________________________________________________
<html>
<head>
<title>Welcome to ansible on {{ ansible_hostname }}</title>
</head>
<body>
<h1>Apache, configured by Ansible on {{ inventory_hostname }}</h1>
<p>If you can see this, Ansible successfully installed Apache.</p>
</body>
</html>
#_____________________________________________________
#
# 5.3. Explanations
# /home/ansible/ansible/playbooks/apache/apache.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Create webserver with apache
# Name of the playbook, displayed when the playbook runs
  become: True
  # Use sudo
  hosts: ubuntu20
  # Host or host group to run on
  tasks:
  # Tasks to do in this playbook
  - name: install apache
  # Name of task, displayed when the playbook runs, install apache
    apt: name=apache2 update_cache=yes
    # Install apache2, first update the cache
    # Equivalent to:
    #   apt update
    #   apt install apache2
  - name: copy index.html
  # Name of task, displayed when the playbook runs, copy customized index.html
  #   from the template
    template: src=templates/index.html.j2 dest=/var/www/html/index.html
    # variables in index.html.j2 are updated and copied to server
      mode=0644
      # File mode will be 0644
  - name: restart apache
  # Name of task, displayed when the playbook runs, restart apache
    service: name=apache2 state=restarted
  # Restart apache, systemctl restart apache2
#_____________________________________________________
# Variables in /home/ansible/playbooks/apache/templates/index.html.j2
# {{ ansible_hostname }} : hostname as ansible gathers
# {{ inventory_hostname }} : hostname as in inventory file
#
# 5.4. Run the playbook
ansible-playbook apache.yml
# or just
./apache.yml
 

6. A More Complex Playbook to Install LAMP

# 6.0. Necessary Steps
# Cache Update (sudo apt update)
# Install Apache (sudo apt install apache2)
# Install Mariadb (sudo apt install mariadb-server)
# Install PHP (sudo apt install php libapache2-mod-php php-mysql)
#
# 6.1. Create Directories
sudo su ansible
mkdir /home/ansible/ansible/playbooks/lamp
cd /home/ansible/ansible/playbooks/lamp
#
# 6.2. Create ansible playbook
nano /home/ansible/ansible/playbooks/lamp/lamp.yml
#_______________________________________________________________________
#!/usr/bin/env ansible-playbook
- name: Install LAMP; Apache, MariaDB, PHP
  become: True
  hosts: test201
  tasks:
  - name: Update apt cache if not updated in 1 hour
    apt:
      update_cache: yes
      cache_valid_time: 3600
  - name: Install apache
    apt:
      name: apache2
      state: present
  - name: Install MariaDB
    apt:
      name: mariadb-server
      state: present
  - name: Install PHP and dependencies
    apt: 
      name: "{{ item }}"
      state: present
    loop:
      - php
      - libapache2-mod-php
      - php-mysql
#_______________________________________________________________________
 

7. (IMHO) Important Ansible Modules

# Well, actually all of the Ansible modules are important. I just selected some 
#   of them considering my very humble opinion.
# To use an example, you have to put it in a playbook or in a role and apply 
#   necessary indentation. Ansible is like Python, indentation is very important.
# Below is a sample, using an example in a playbook
#_______________________________________________________________________
#!/usr/bin/env ansible-playbook
- name: Tutorial tasks
  become: True
  hosts: test201
  tasks:
  - name: Start apache if not started
    service:
      name: apache2
      state: started
#_______________________________________________________________________
 

7.0. apk Module: Manages Alpine apk packages.

# Examples:
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apk:
    name: apache2
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apk:
    name: apache2
    state: present
#______________________________________________________________
- name: Install apache, upgrade to latest if already installed
  apk:
    name: apache2
    state: latest
#______________________________________________________________
- name: Update repositories and install apache
  apk:
    name: apache2
    update_cache: yes
#______________________________________________________________
- name: Remove apache
  apk:
    name: apache2
    state: absent
#______________________________________________________________
- name: Install more than 1 packages
  apk:
    name: apache2, php
#______________________________________________________________
- name: Update cache and update apache to latest
  apk:
    name: apache2
    state: latest
    update_cache: yes
#______________________________________________________________
- name: Update all packages to their latest version
  apk:
    upgrade: yes
#______________________________________________________________
- name: Update cache (apt-get update)
  apk:
    update_cache: yes
#______________________________________________________________
 

7.1. apt Module: Manages Debian/Ubuntu apt packages.

# Examples:
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apt:
    name: apache2
#______________________________________________________________
- name: Install apache, don't do anything if already installed
  apt:
    name: apache2
    state: present
#______________________________________________________________
- name: Install apache, upgrade to latest if already installed
  apt:
    name: apache2
    state: latest
#______________________________________________________________
- name: Update repositories and install apache
  apt:
    name: apache2
    update_cache: yes
#______________________________________________________________
- name: Remove apache
  apt:
    name: apache2
    state: absent
#______________________________________________________________
- name: Install more than 1 packages
  apt:
    pkg:
    - apache2
    - php
#______________________________________________________________
- name: Update cache and update apache to latest
  apt:
    name: apache2
    state: latest
    update_cache: yes
#______________________________________________________________
- name: Install latest php, ignore "install-recommends"
  apt:
    name: php
    state: latest
    install_recommends: no
#______________________________________________________________
- name: Update all packages to their latest version
  apt:
    name: "*"
    state: latest
#______________________________________________________________
- name: Upgrade the OS (apt-get dist-upgrade)
  apt:
    upgrade: dist
#______________________________________________________________
- name: Update cache (apt-get update)
  apt:
    update_cache: yes
#______________________________________________________________
- name: Update cache if the last update is more than 1 hour
  apt:
    update_cache: yes
    cache_valid_time: 3600
#______________________________________________________________
- name: Remove unused packages
  apt:
    autoclean: yes
#______________________________________________________________
- name: Remove unused dependencies
  apt:
    autoremove: yes
#______________________________________________________________
 

7.2. blockinfile Module: Insert/update/remove a text block between marked lines

# Examples:
#______________________________________________________________
- name: Add or update a block to a html file
  blockinfile:
    path: /var/www/html/index.html
    marker: "<!-- {mark} MANAGED by ANSIBLE BLOCK -->"
    # The block will be wrapper by this marker
    # {mark} is replaced as BEGIN at the beginning
    #   and END at the end.
    insertafter: "<body>"
    # The block with the markers will be inserted after the last
    #   match of this this text. Regexps can be used. If there is no
    #   match or value is EOF, block is added at the end of file.
    # Similarly insertbefore can be used.
    block: |
      <h1>Web server: {{ ansible_hostname }}</h1>
      <p>Update time: {{ ansible_date_time.date }}
      {{ ansible_date_time.time }} </p>
#______________________________________________________________
- name: Remove previously added block
  blockinfile:
    path: /var/www/html/index.html
    marker: "<!-- {mark} MANAGED by ANSIBLE BLOCK -->"
    block: ""
#______________________________________________________________
- name: Add mappings to /etc/hosts file, make a backup of file
  blockinfile:
    path: /etc/hosts
    backup: yes
    block: |
      {{ item.ip }} {{ item.hostname }}
    marker: "<!-- {mark} {{ item.hostname }} MANAGED by ANSIBLE BLOCK -->"
  loop:
    - { hostname: test201, ip: 192.168.0.201 }
    - { hostname: test202, ip: 192.168.0.202 }
    - { hostname: test203, ip: 192.168.0.203 }
#______________________________________________________________
 

7.3. command Module: Execute commands

# Examples:
#______________________________________________________________
- name: Run a command on server and take its output to a variable
  command: free
  register: freevals
#______________________________________________________________
- name: Run a command if a path does not exist
  command: /usr/sbin/reboot now creates=/etc/flag
#______________________________________________________________
- name: Run a command if a path does not exist
  command:
    cmd: /usr/sbin/reboot now
    creates: /etc/flag
#______________________________________________________________
- name: Run a command if a path does not exist
  command:
    argv:
      - /usr/sbin/reboot
      - now
    creates: /etc/flag
#______________________________________________________________
- name Become user dbadmin, change to home dir and run mariad backup
  command: mysqldump 
  become: yes
  become_user: dbadmin
  args:
    chdir: /home/dbadmin
#______________________________________________________________
 

7.4. copy Module: Copy files to remote servers

# Examples
#______________________________________________________________
- name: Copy a file with specified owner and permissions, backup the file
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: '0644'
    backup: yes
#______________________________________________________________
- name: Copy a file with specified owner and permissions
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: u=rw,g=r,o=r
#______________________________________________________________
- name: Copy a file with specified owner and permissions
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: u+rw,g-wx,o-rwx
#______________________________________________________________
- name: Copy a file with specified owner and permissions, backup the file
  copy:
    src: /home/ansible/main.cf
    dest: /etc/postfix/master.cf
    owner: root
    group: root
    mode: '0644'
    backup: yes
#______________________________________________________________
- name: Copy a file on the server to another location
  copy:
    src: /etc/apache2/apache.conf
    dest: /etc/apache2/apache.conf.backup
    remote_src: yes
#______________________________________________________________
- name: Copy an inline text to a file
  copy:
    content: "This file is empty"
    dest: /etc/test
#______________________________________________________________
 

7.5. debug Module: Print debug messages

# Examples:
#______________________________________________________________
- name: Display all variables/facts known for a host
  debug:
    var: hostvars[inventory_hostname]
#______________________________________________________________
- name: Display a message
  debug:
    msg: Working fine so far
#______________________________________________________________
- name: Print return information from a previous task part 1
  shell: /usr/bin/date
  register: result 
- name: Print return information from a previous task part 2
  debug:
    var: result.stdout_lines
#______________________________________________________________
- name: Print multi lines of information from variables part1
  shell: whoami
  register: var1
- name: Print multi lines of information from variables part2
  shell: who -b
  register: var2
- name: Print multi lines of information from variables part3
  debug:
    msg:
    - "Information gathered so far:"
    - "1. User name is {{ var1.stdout_lines }}"
    - "2. System is on since {{ var2.stdout_lines }}"
#______________________________________________________________
 

7.6. expect Module: Executes a command and responds to prompts

# Examples:
#______________________________________________________________
- name: Login to mariadb asking the root password and run a command from a file
  expect:
    command: /bin/bash -c "mariadb -u root -p < /tmp/test.sql"
    responses:
      (.*)password: "password12"
  register: DBUsers
  no_log: true
  # hide your password from log
- name: Display Result
  debug:
    var: DBUsers.stdout_lines
#______________________________________________________________
- name: Generic question with multiple different responses
  expect:
    command: command
    # Assuming a command asking 3 questions
    responses:
      Question:
        - Answer 1
        - Answer 2
        - Answer 3
#______________________________________________________________
 

7.7. fail Module: Fail with a message

# Examples:
#______________________________________________________________
- name: Stop execution if hostname is something special
  fail:
    msg: Cannot continue with hostname Test201
  when: inventory_hostname == "Test201"
#______________________________________________________________
 

7.8. fetch Module: Fetch files from server to the workstation

# Examples:
#______________________________________________________________
- name: Fetch server file, preserve directory information
  fetch:
    src: /etc/apache2/apache.conf
    dest: /tmp/conf
    # File will be copied to /tmp/conf/hostname/etc/apache2/apache.conf
#______________________________________________________________
- name: Fetch server file, directly to the specified directory
  fetch:
    src: /etc/apache2/apache.conf
    dest: /tmp/conf/apache.conf
    # File will be copied to /tmp/conf/apache.conf
    # Consecutive files will be overwritten
    flat: yes
#______________________________________________________________
- name: Fetch server file, directly to the specified directory
#   directly to the specified directory for every server
  fetch:
    src: /etc/apache2/apache.conf
    dest: /tmp/conf/{{ inventory_hostname }}/apache.conf
    flat: yes
#______________________________________________________________
 

7.9. file Module: File and directory management

# Examples:
#______________________________________________________________
- name: Change ownership and permission of a file
  file:
    path: /etc/test.conf
    owner: ansible
    group: ansible
    mode: '0644'
#______________________________________________________________
- name: Create a symbolic link of a file, change ownership of the original file
  file:
    src: /etc/test.conf
    dest: /home/ansible/test.conf
    owner: ansible
    group: ansible
    state: link
#______________________________________________________________
- name: Create a hard link
  file:
    src: /tmp/test.conf
    dest: /home/ansible/test.conf
    state: hard
#______________________________________________________________
- name: Touch a file and set permissions
  file:
    path: /etc/test.conf
    state: touch
    mode: u=rw,g=r,o=r
#______________________________________________________________
- name: Touch a file, but preserve its times. 
#  so there is no change if it was touched before
  file:
    path: /etc/foo.conf
    state: touch
    modification_time: preserve
    access_time: preserve
#______________________________________________________________
- name: Create a directory, do nothing if it already exists
  file:
    path: /tmp/test
    state: directory
    mode: '0755'
#______________________________________________________________
- name: Update modification and access time of a file to now
  file:
    path: /tmp/test.conf
    state: file
    modification_time: now
    access_time: now
#______________________________________________________________
- name: Change ownership of a directory recursively 
  file:
    path: /var/www
    state: directory
    recurse: yes
    owner: www-data
    group: www-data
#______________________________________________________________
- name: Delete a file
  file:
    path: /etc/test.conf
    state: absent
#______________________________________________________________
- name: Remove a directory recursively
  file:
    path: /etc/test
    state: absent
#______________________________________________________________
 

7.10. geturl Module: Download files

# Examples:
#______________________________________________________________
- name: Download a file (wordpress)
  get_url:
    url: https://wordpress.org/latest.tar.gz
    dest: /tmp/wordpress.tar.gz
    mode: '0440'
#______________________________________________________________
- name: Download file with md5 checksum
  get_url:
    url: https://wordpress.org/latest.tar.gz
    dest: /tmp/wordpress/wordpress.tar.gz
    checksum: md5:b7a9eb3560e5e17df79b7131a9fafdd7
#______________________________________________________________
 

7.11. group Module: Linux group management

# Examples:
#______________________________________________________________
- name: Create a group named admins
  group:
    name: admins
    state: present
#______________________________________________________________
- name: Create a group named admins gid 1250
  group:
    name: admins
    state: present
    gid: 1250
#______________________________________________________________
- name: Delete admins group
  group:
    name: admins
    state: absent
#______________________________________________________________
 

7.12. lineinfile Module: Manage lines in text files

# Uses a back referenced rexexp, and puts, updates or deletes a 
#   line in a file
# Examples:
#______________________________________________________________
- name: Change or add the name of an host in /etc/hosts
  lineinfile:
    path: /etc/hosts
    regexp: '^192\.168\.0\.201'
    line: 192.168.0.201 test201.x386.xyz
#______________________________________________________________
- name: Remove previously added line in /etc/hosts
  lineinfile:
    path: /etc/hosts
    regexp: '^192\.168\.0\.201'
    state: absent
#______________________________________________________________
- name: Create a file if it does not exist and add a line
  lineinfile:
    path: /tmp/test
    line: 192.168.0.201 test201.x386.xyz
    create: yes
#______________________________________________________________
 

7.13. pause Module: Pause execution

# Examples:
#______________________________________________________________
- name: Pause for 5 minutes
  pause:
    minutes: 5
#______________________________________________________________
- name: Pause for 30 seconds
  pause:
    seconds: 30
#______________________________________________________________
- name: Pause until prompted
  pause:
#______________________________________________________________
- name: Pause until prompted with message
  pause:
    prompt : "Press enter to continue"
#______________________________________________________________
- name: Pause to get password
  pause:
    prompt: "Enter password"
    echo: no
  register: password
#______________________________________________________________
 

7.14. reboot Module: Reboot server

# Examples:
#______________________________________________________________
 name: Reboot and connect again
  reboot:
#______________________________________________________________
- name: Reboot and wait up to 1 hour for connecting again
  reboot:
    reboot_timeout: 3600
#______________________________________________________________
- name: Display a message to users, wait 5 minutes and reboot
  reboot:
    pre_reboot_delay: 300
    msg: "Rebooting in 5 minutes, please save your work and exit"
#______________________________________________________________
 

7.15. replace Module: Replace a string in a file using a back ref regexp

# Examples:
#______________________________________________________________
- name: Replace all .org names with .com names in /etc/hosts
  replace:
    path: /etc/hosts
    regexp: '(.*)\.org(\s+)'
    # Starts with anything, then comes .org and one or more whitespace
    replace: '\1.com\2'
    # \1 = (.*)   \2 = (\s+)
#______________________________________________________________
- name: Do the same, but start after and expression and end other another
  replace:
    path: /etc/hosts
    after: 'Start Here'
    before: 'End Here'
    regexp: '(.*)\.org(\s+)'
    # Starts with anything, then comes .org and one or more whitespace
    replace: '\1.com\2'
    # \1 = (.*)   \2 = (\s+)
#______________________________________________________________
- name: Comment every line containing TEST, backup the original file
  replace:
    path: /tmp/test.sh
    regexp: '^(.*)TEST(.*)$'
    replace: '#\1TEST\2'
    backup: yes
#______________________________________________________________
 

7.16. script Module: Transfer and run a script from workstation to server

# A script on the worktation is copied to the server(s) and run there
# Examples:
#______________________________________________________________
- name: Run a script 
  script: /home/ansible/ansible/backup.sh all
#______________________________________________________________
- name: Run a script
  script:
    cmd: /home/ansible/ansible/backup.sh all
#______________________________________________________________
- name: Run a script only if a file does not exist on the server
  script: /home/ansible/ansible/backup.sh all
  args:
    creates: /tmp/backup.txt
#______________________________________________________________
- name: Run a script only if a file exists on the server
  script: /home/ansible/ansible/backup.sh all
  args:
    removes: /tmp/backup.txt
#______________________________________________________________
- name: Run a script using bash
  script: /home/ansible/ansible/backup.sh
  args:
    executable: /bin/bash
#______________________________________________________________
- name: Run a python script
  script: /home/ansible/ansible/backup.py
  args:
    executable: python3
#______________________________________________________________
 

7.17. service Module: Manage services

# Examples:
#______________________________________________________________
- name: Start apache if not started
  service:
    name: apache2
    state: started
#______________________________________________________________
- name: Stop apache if started
  service:
    name: apache2
    state: stopped
#______________________________________________________________
- name: Restart apache
  service:
    name: apache2
    state: restarted
#______________________________________________________________
- name: Reload apache
  service:
    name: apache2
    state: reloaded
#______________________________________________________________
- name: Enable apache service, do not touch the state
  service:
    name: apache2
        enabled: yes
#______________________________________________________________
 

7.18. shell Module: Execute shell commands on servers

# Different from command module, redirection and pipes are safe
# Examples:
#______________________________________________________________
- name: Execute command on remote shell; stdout to a file
  shell: backup.sh >> backup.log
#______________________________________________________________
- name: Execute command on remote shell; stdout to a file
  shell: 
    cmd: backup.sh >> backup.log
#______________________________________________________________
- name: Change to a directory before executing a command
  shell: backup.sh >> backup.log
  args:
    chdir: /tmp/
#______________________________________________________________
- name: command only if a file does not exist
  shell: backup.sh >> backup.log
  args:
    creates: backup.log
#______________________________________________________________
- name: Change to a directory before executing a command, disable warning
  shell: backup.sh >> backup.log
  args:
    chdir: /tmp/
    warn: no
#______________________________________________________________
 

7.19. tempfile Module: Create a temporary file or directory

# Examples:
#______________________________________________________________
- name: Create a temporary directory with suffix tempdir
  tempfile:
    state: directory
    suffix: tempdir
#______________________________________________________________
- name: Create a temporary file with suffix and save its name to a variable
  tempfile:
    state: file
    suffix: temp
  register: tempfilename
#______________________________________________________________
- name: Use the variable created above to remove the file
  file:
    path: "{{ tempfilename.path }}"
    state: absent
  when: tempfilename.path is defined
#______________________________________________________________
 

7.20. template Module: Copy a file to servers using a template

# Unlike file module, you can use variables in template files
#   like in 5.2.
# Examples:
#______________________________________________________________
- name: Create an html file from a template
  template:
    src: /home/ansible/ansible/playbooks/apache/templates/index.html.j2
    dest: /var/www/html/index.html
    owner: www-data
    group: www-data
    mode: '0660'
#______________________________________________________________
- name: Create an html file from a template
  template:
    src: /home/ansible/ansible/playbooks/apache/templates/index.html.j2
    dest: /var/www/html/index.html
    owner: www-data
    group: www-data
    mode: u=rw,g=r
#______________________________________________________________
 

7.21. unarchive Module: Unpack an archive

# The archive might be on the workstation or on the server
# Examples:
#______________________________________________________________
- name: Extract a tar.xz file
  unarchive:
    src: /home/ansible/test.tar.xz
    dest: /tmp
#______________________________________________________________
- name: Unarchive a file on the server
  unarchive:
    src: /home/ansible/test.zip
    dest: /tmp
    remote_src: yes
#______________________________________________________________
- name: Download and unpack wordpress
  unarchive:
    src: https://wordpress.org/latest.zip
    dest: /var/www/html
    remote_src: yes
#______________________________________________________________
 

7.22. user Module: User management

# Examples:
#______________________________________________________________
- name: Add user exforge with a primary group with the same name
  user:
    name: exforge
    comment: main user
    group: exforge
#______________________________________________________________
- name: Add user exforge with a primary group with the same name,
#   with a specific uid
  user:
    name: exforge
    comment: main user
    uid: 1111
    group: exforge
#______________________________________________________________
- name: Add user exforge with bash shell, append the user to www-data and postfix group
  user:
    name: exforge
    shell: /bin/bash
    groups: www-data,postfix
    append: yes
#______________________________________________________________
- name: Add vmail user with a specific home dir
  user:
    name: vmail
    comment: Postfix Mail User
    uid: 2222
    group: vmail
    home: /var/mail
#______________________________________________________________
- name: Remove user exforge
  user:
    name: exforge
    state: absent
#______________________________________________________________
- name: Remove user exforge, remove directories too
  user:
    name: exforge
    state: absent
    remove: yes
#______________________________________________________________
 

8. Roles

# 8.0. Introduction
# Playbooks can be splitted into roles. That way, we can create reusable code.
# The lamp example at 7. will be rewritten using 4 roles:
#   Cache Update
#   Install Apache
#   Install MariaDB
#   Install PHP and dependencies
# 8.1. Role Structure
# Roles are created with ansible-galaxy init command. Naming convetion for roles is
#   as identifier.role. I use exforge as my identifier, you can use anything you 
#   want. For a role to install apache, my rolename would be exforge.apache
ansible-galaxy init exforge.apache
# A directory with role.name is created under the current directory with
#   the following structure:
#
# README.md  (file)
# defaults  (directory)
#    defaults/main.yml  (file)
# files  (directory)
# handlers  (directory)
#    handlers/main.yml (file)
# meta  (directory)
#    meta/main.yml (file)
# tasks  (directory)
#    tasks/main.yml (file)
# templates  (directory)
# tests  (directory)
#    tests/inventory  (directory)
#    tests/test.yml (file)
# vars  (directory)
#    vars/main.yml  (file)
#
# All of the directories and files are optional.
# README.md file is used for documentation. Expected to contain the purpose
#   of the role and any other important information.
# defaults/main.yml is used as a configuration file to define default variables in
#   the role. Variables in vars/main.yml overrides variables defined here.
# files directory is used to place static files. Files used in roles without any
#   manipulation can be stored here.
# handlers/main.yml is used to define handlers (like starting, stopping or 
#   restarting services). 
# meta/main.yml is used to contain metadata for the role. Metadata can be used
#   if you want to publish your role to Ansible Galaxy.
# tasks/main.yml is the main file of the role. Expected to contain role actions. 
#   Actions here will be executed when your role runs.
# templates directory is used to place template (dynamic) files. The files here can 
#   contain variables to interpolate them before using on target systems.
# test directory is used to create test playbooks to consume the role. Mostly used to
#   test roleswith a system like Jenkins or Travis.
# vars/main.yml is similar to defaults/main.yml with an exception. Variables defined 
#   here overrides the variables defined at fact gathering section. Variables defined
#   here also overrides the variables defined in defaults/main.yml.
#
# 8.2. Preparing LAMP Roles
# We will have 4 roles for LAMP installation. Namely; aptcache, apache, mariadb and
#   php.
# Create a directory for the roles and init the roles:
mkdir -p /home/ansible/ansible/playbooks/roles
cd /home/ansible/ansible/playbooks/roles
ansible-galaxy init exforge.aptcache
ansible-galaxy init exforge.apache
ansible-galaxy init exforge.mariadb
ansible-galaxy init exforge.php
#
# 8.3. Create the new playbook with the roles
nano /home/ansible/ansible/playbooks/lamp.yml
#_______________________________________
#!/usr/bin/env ansible-playbook
---
- hosts: test201
  become: true
  roles:
  - exforge.aptcache 
  - exforge.apache 
  - exforge.mariadb
  - exforge.php 
#_______________________________________
#
# Make it executable
chmod +x /home/ansible/ansible/playbooks/lamp.yml
#
# 8.4. aptcache role
nano /home/ansible/ansible/playbooks/roles/exforge.aptcache/tasks/main.yml
#_______________________________________
---
# tasks file for exforge.aptcache
- name: Update apt cache if not updated in 1 hour
  apt:
    update_cache: yes
    cache_valid_time: 3600
#_______________________________________
#
# 8.5. apache role
nano /home/ansible/ansible/playbooks/roles/exforge.apache/tasks/main.yml
#_______________________________________
---
# tasks file for exforge.apache
- name: Install apache
  apt:
    name: apache2
    state: present
#_______________________________________
#
# 8.6. mariadb role
nano /home/ansible/ansible/playbooks/roles/exforge.mariadb/tasks/main.yml
#_______________________________________
---
# tasks file for exforge.mariadb
- name: Install MariaDB
  apt:
    name: mariadb-server
    state: present
#_______________________________________
#
# 8.7. php role
nano /home/ansible/ansible/playbooks/roles/exforge.php/tasks/main.yml
#_______________________________________
- name: Install PHP and dependencies
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - php
    - libapache2-mod-php
    - php-mysql
#_______________________________________
#
# 8.8. Running the new playbook
cd /home/ansible/ansible/playbooks
ansible-playbook lamp.yml
 

9.Ansible Facts and Magic Variables

# 9.1. Ansible Facts
# 9.1.1. Getting Facts
# Get all facts for test201 server
ansible test201 -m setup
# The output will be long, something like:
#_____________________________________________________________________
test201 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.0.111"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::a00:27ff:fe10:9"
        ],
        "ansible_apparmor": {
            "status": "enabled"
        },
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "12/01/2006",
        "ansible_bios_version": "VirtualBox",
        "ansible_cmdline": {
            "BOOT_IMAGE": "/boot/vmlinuz-5.4.0-48-generic",
            "maybe-ubiquity": true,
            "ro": true,
            "root": "UUID=8842fb18-ffef-4b0d-8c10-419865ae27a2"
        },
        "ansible_date_time": {
            "date": "2020-10-16",
            "day": "16",
            "epoch": "1602869883",
            "hour": "17",
            "iso8601": "2020-10-16T17:38:03Z",
            "iso8601_basic": "20201016T173803661252",
            "iso8601_basic_short": "20201016T173803",
            "iso8601_micro": "2020-10-16T17:38:03.661346Z",
            "minute": "38",
            "month": "10",
            "second": "03",
            "time": "17:38:03",
            "tz": "UTC",
            "tz_offset": "+0000",
            "weekday": "Friday",
            "weekday_number": "5",
            "weeknumber": "41",
            "year": "2020"
        },
        "ansible_system_capabilities_enforced": "True",
        "ansible_system_vendor": "innotek GmbH",
        "ansible_uptime_seconds": 244,
        "ansible_user_dir": "/home/ansible",
        "ansible_user_gecos": "",
        "ansible_user_gid": 1001,
        "ansible_user_id": "ansible",
        "ansible_user_shell": "/bin/bash",
        "ansible_user_uid": 1001,
        "ansible_userspace_architecture": "x86_64",
        "ansible_userspace_bits": "64",
        "ansible_virtualization_role": "guest",
        "ansible_virtualization_type": "virtualbox",
        "discovered_interpreter_python": "/usr/bin/python3",
        "gather_subset": [
            "all"
        ],
        "module_setup": true
    },
    "changed": false
#_________________________________________________________________________
#
# 9.1.2. Accessing Ansible Facts
# You can use any value from the facts as a variable. Some examples:
# Model of first disk
{{ ansible_facts['devices']['xvda']['model'] }}
# System hostname
{{ ansible_facts['nodename'] }}
# Using another system's fact
{{ hostvars['asdf.example.com']['ansible_facts']['os_family'] }}
# 10.1.3. Important Ansible Facts
# Date and time
ansible_date_time.date -->  "2020-11-11"
ansible_date_time.time -->  "08:44:06"
# OS
ansible_os_family      -->  "Debian" for Debian and Ubuntu, "RedHat" for CentOS
ansible_distribution   -->  "Ubuntu" for Ubuntu, "Debian" for Debian, "CentOS" for CentOS
ansible_hostname       -->  "test201"
ansible_distribution_version --> "20.04"
#
# 9.2. Ansible Magic Variables
inventory_hostname: Hostname as in inventory
inventory_hostname_short: Hostname as in inventory short format
ansible_play_hosts: list of all hosts still active in the current play.
ansible_play_batch: list of hostnames that are in scope for the current ‘batch’ of the play.
ansible_playbook_python: Path to the python executable used to invoke the Ansible command line tool.
inventory_dir: Pathname of the directory holding Ansible’s inventory 
inventory_file: Pathname and the filename pointing to the Ansible’s inventory host file.
playbook_dir: Playbook base directory.
role_path: current role’s pathname and only works inside a role.
ansible_check_mode: Boolean, set to True if you run Ansible with --check.
 

10. Distinguishing Linux Distribution

# Debian and Ubuntu name name Apache Server as apache2 and use apt package manager. 
#   Alpine also names Apache Server as apache2 and uses apk package manager. RHEL 
#   (and Alma) names it as httpd and use dnf package manager. 
# Our example role will distinguish the distribution and call appropriate tasks.
#
# 10.1. A Playbook to install Apache on Ubuntu (and Debian) and Centos (and Redhat)
nano /home/ansible/ansible/playbooks/apache.yml
#______________________________________________________
#!/usr/bin/env ansible-playbook
- name: Install Apache on Ubuntu (Debian), RHEL (Alma) and Alpine
  become: True
  hosts: all
  tasks:
  - name: install apache if Ubuntu or Debian
    apt: 
      name: apache2 
      update_cache: yes
    when: ansible_os_family == "Debian"
  - name: install apache if Redhat or Alma
    dnf: 
      name: httpd
    when: (ansible_os_family == "RedHat") or (ansible_os_family == "AlmaLinux")
  - name: install apache if Alpine
    apk: 
      name: apache2 
      update_cache: yes
    when: ansible_os_family == "Alpine"
#______________________________________________________
#
# 10.2. Other OS Families
# Some of the other possible ansible_os_family options are:
#   "Debian" for Linux Mint, Neon, KDE Neon, Raspbian
#   "RedHat" for Centos, Fedora, Scientific, CloudLinux, PSBM, OracleLinux, Amazon
#   "AlmaLinux" for Alma
#   "Suse" for Suse, OpenSuSe, SLES, SLED
#   "Gentoo" for Gentoo
#   "Archlinux" for ArchLinux, Manjaro
#   "Mandrake" for Mandrake, Mandriva
#   "Solaris"  for Solaris, Nexenta, OnmiOS, OpenIndiana, SmartOS
#   "Slackware" for Slackware
#   "Darwin" for MacOSX
#
# 10.3. A Role to install Apache on Ubuntu (Debian), Alma (Redhat), and Alpine
cd /home/ansible/ansible/playbooks/roles
ansible-galaxy init exforge.apacheDRA
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/main.yml
#_______________________________________________________________
---
# tasks file for exforge.apacheDRA
- include_tasks: debian.yml
  when: ansible_os_family == "Debian"
- include_tasks: redhat.yml
  when: (ansible_os_family == "RedHat") or (ansible_os_family == "AlmaLinux")
- include_tasks: alpine.yml
  when: ansible_os_family == "Alpine"
#_______________________________________________________________
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/debian.yml
#_______________________________________________________________
- name: install apache if Ubuntu or Debian
  apt: 
    name: apache2 
    state: present
    update_cache: yes
#_______________________________________________________________
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/redhat.yml
#_______________________________________________________________
- name: install apache if RedHat or Alma
  dnf: 
    name: httpd 
    state: present
#_______________________________________________________________
nano  /home/ansible/ansible/playbooks/roles/exforge.apacheDRA/tasks/alpine.yml
#_______________________________________________________________
- name: install apache if Alpine
  apk: 
    name: apache2 
    state: present
    update_cache: yes
#_______________________________________________________________
# Now we can create a playbook to consume this role
nano  /home/ansible/ansible/playbooks/apacheDRA.yml
#_______________________________________________________________
#!/usr/bin/env ansible-playbook
---
- hosts: test
  become: true
  roles:
  - exforge.apacheDRA
#_______________________________________________________________
# Run the playbook
cd /home/ansible/ansible/playbooks
ansible-playbook apacheDRA.yml
#
# 10.1.4. Exercise
# Redesign apacheDR role, using package module.
 

11. Role Variables

# You can set role variables when you consume a role in a playbook. The variables
#   are defined in the roles and can be set values at the playbook.
# Our example will install apache (if it is not installed), create a configuration
#   with the given site name and create a default page with the given parameters.
# After creating the role, default variables will be defined in defaults/main.yml dir,
#   apache conf file and html templates will be created at templates/ dir, and role 
#   tasks will be coded at tasks/main.yml. After all, we will create a playbook, 
#   set all variables there and run the role.
#
# 11.1. Create the role apachesite
cd /home/ansible/ansible/playbooks/roles
ansible-galaxy init exforge.apachesite
# 
# 11.2. Define Variables
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/defaults/main.yml
#____________________________________________________
---
# defaults file for exforge.apachesite
server_name: www.example.com
server_alias: example.com
html_title: Welcome to {{ ansible_hostname }}
html_header: Welcome to {{ ansible_hostname }}
html_text: This page is created by Ansible
#____________________________________________________
#
# 11.3. Create Apache conf file and index.html file templates
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/apache.conf.j2
#____________________________________________________
<VirtualHost *:80>
  ServerAdmin webmaster@{{ server_name }}
  ServerName {{ server_name }}
  ServerAlias {{ server_alias }}
  DocumentRoot /var/www/{{ server_name }}
  ErrorLog ${APACHE_LOG_DIR}/{{ server_name }}-error.log
  CustomLog ${APACHE_LOG_DIR}/{{ server_name }}-access.log combined
</VirtualHost>
#____________________________________________________
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/index.html.j2
#____________________________________________________
<html>
<head>
<title>{{ html_title }}</title>
</head>
<body>
<h1>{{ html_header }}</h1>
<p>{{ html_text }}</p>
</body>
</html>
#____________________________________________________
#
# 11.4. Create Tasks
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/tasks/main.yml
#____________________________________________________
---
# tasks file for exforge.apachesite
- name: Stop execution if OS is not in Debian family
  fail:
    msg: Only works on Debian and her children (Ubuntu, Mint, ..)
  when: ansible_os_family != "Debian"
- name: Install apache2 if not already installed
  apt: 
    name: apache2 
    state: present
    update_cache: yes
- name: Create apache conf file from the template
# File is named as servername.conf and will be put in /etc/apache2/sites-available
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/apache.conf.j2
    dest: /etc/apache2/sites-available/{{ server_name }}.conf
    mode: "0644"
    owner: root
    group: root
- name: Enable new conf
# It will be enabled if we create a link to this conf file in /etc/apache2/sites-enabled
  file:
    src: /etc/apache2/sites-available/{{ server_name }}.conf
    dest: /etc/apache2/sites-enabled/{{ server_name }}.conf
    remote_src: yes
    owner: root
    group: root
    state: link
- name: Create home directory for the site
# Home directory will be /var/www/server_name
  file:
    path: /var/www/{{ server_name }}
    state: directory
    mode: "0770"
    owner: www-data
    group: www-data
- name: Copy index.html to site's home directory
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/index.html.j2
    dest: /var/www/{{ server_name }}/index.html
    mode: "0644"
    owner: www-data
    group: www-data
- name: Reload apache2
  service:
    name: apache2
    state: reloaded
#____________________________________________________
#
# 11.5. Create a playbook and consume the role
nano /home/ansible/ansible/playbooks/apachesite.yml
#____________________________________________________
#!/usr/bin/env ansible-playbook
---
- hosts: test201
  become: true
  vars:
    server_name: test201.x386.xyz
    server_alias: test201
    html_title: test201.x386.xyz Homepage
    html_header: This is the homepage of test201.x386.xyz
    html_text: This is a sample page created by Ansible    
  roles:
  - exforge.apachesite
#_______________________________________________________________
# Run the playbook
cd /home/ansible/ansible/playbooks
ansible-playbook apachesite.yml
#____________________________________________________
 

12. Variable Filters

# There are a number of filters available for the variables used in templates, 
#   playbooks and roles.
#
# 12.1. Syntax Filters
# Allows case manipulation: lowercase, uppercase, capital case, title case
# my_message: We are the world
# {{ my_message | lower }}  --> we are the world
# {{ my_message | upper }}  --> WE ARE THE WORLD
# {{ my_message | capitalize }}  --> We are the world
# {{ my_message | title }}  --> We Are The World
#
# 12.2. Default Filter
# Using an undefined variable causes an error, to avoid that situation default filter
#   can be used.
# {{ my_message | default('No message') }}
#
# 12.3. List Filters
# List definition is similar to Python:
#   num_list: [1,2,3,4,5,6,7,8,9,0]
# Some of the list filters are: max, min and random
# {{ num_list | max }}  --> 9
# {{ num_list | min }}  --> 0
# {{ num_list | random }}  --> a random one
#
# 12.4. Pathname Filters
# First, let's define a variable containing a path
#   path: "/etc/apache2/apache2.conf"
# Two of the most important filters are; dirname and basename
# {{ path | dirname }}   --> /etc/apache2
# {{ path | basename }}  --> apache2.conf
#
# 12.5. Date and Time Filters
# {{ '%d-%m-%Y' | strftime }}  --> Current date
# {{ '%H:%M:%S' | strftime }}  --> Current time
# {{ '%d-%m-%Y %H:%M:%S' | strftime }}  --> Current date and time
#
# 12.6. Math Filters
# {{ num | log }}     --> log of num on base e
# {{ num | log(10) }} --> log of num on base 10
# {{ num | pow(2) }}  --> square of num
# {{ num | root }}    --> square root of num
# {{ num | root(3) }} --> third root of num
# {{ num | abs }}     --> absolute of num
# {{ num | round }}   --> round of num
#
# 12.7. Encryption Filters
# {{ my_message | hash('sha1') }}  --> sha1 hash of variable
# {{ my_message | hash('md5') }}   --> md5 hash of variable
# {{ my_message | checksum }}     --> checksum of variable
#
# 12.8. An Example Playbook to Cover all the Filters Here
nano /home/ansible/ansible/playbooks/filters.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Demonstration of Filters
  become: True
  hosts: test201
  vars:
    my_message: "We are the world"
    num_list: [1,2,3,4,5,6,7,8,9,0]
    path: "/etc/apache2/apache2.conf"
    num: 85
    num2: -8
    num3: 2.6
  tasks:
  - name: All the filters
    debug:
      msg:
        - "My original message: {{ my_message }}"
        - "My message in lowercase: {{ my_message | lower }}"
        - "My message in upper: {{ my_message | upper }}"
        - "My message in sentence case: {{ my_message | capitalize }}"
        - "My message in title case: {{ my_message | title }}"
        - "Sha1 hash of my message: {{ my_message | hash('sha1') }}"
        - "Md5 hash of my message: {{ my_message | hash('md5') }}" 
        - "checksum of my message: {{ my_message | checksum }}"
        - "---"
        - "Default value: {{ my_message2 | default('No message') }}"
        - "---"
        - "My list: {{ num_list }}"
        - "Maximum of list: {{ num_list | max }}"
        - "Minimum of list: {{ num_list | min }}"
        - "A random item of list: {{ num_list | random }}"
        - "---"
        - "Path: {{path}}"
        - "Directory of path: {{ path | dirname }}"
        - "Filename of path: {{ path | basename }}"
        - "---"
        - "Current date: {{ '%d-%m-%Y' | strftime }}"
        - "Current time: {{ '%H:%M:%S' | strftime }}"
        - "Current date and time: {{ '%d-%m-%Y %H:%M:%S' | strftime }}"
        - "---"
        - "e base log of {{ num }}: {{ num | log }}"
        - "10 base log of {{ num }}: {{ num | log(10) }}"
        - "Square of {{ num }}: {{ num | pow(2) }}"
        - "4th power of {{ num }}: {{ num | pow(4) }}"
        - "Square root of {{ num }}: {{ num | root }}"
        - "3rd root of {{ num }}: {{ num | root(3) }}"
        - "Absolute of {{ num2 }}: {{ num2 | abs }}"
        - "Round of {{ num3 }}: {{ num3 | round }}"
#_____________________________________________________        
 

13. Handlers

# If you want a task to run when something is changed, you can use handlers. For
#   example, a task tries to change a conf file for apache, and you need to reload
#   or restart apache if the file is changed. That is when you use handlers.
# You might remember, there is a folder for handlers for the roles. That is you are
#   expected to put your handlers there. 
#
# 13.1. A Simple Example
# Our example playbook will install apache and reload it if it is installed. 
nano /home/ansible/ansible/playbooks/simple_handler.yml
#_____________________________________________________
#!/usr/bin/env ansible-playbook
- name: Simple handler example
  become: true
  hosts: test201
  tasks:
  - name: Install Apache
    apt:
      name: apache2
      state: present
    notify: restart_apache
  handlers:
  - name: restart_apache
    service:
      name: apache2
      state: restarted
#
# 13.2. Handlers in Roles
# Let's change the role in apachesite in 11. so that it includes handlers.
# First change tasks in tasks folder:
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/tasks/main.yml
#____________________________________________________
---
# tasks file for exforge.apachesite
- name: Stop execution if OS is not in Debian family
  fail:
    msg: Only works on Debian and her children (Ubuntu, Mint, ..)
  when: ansible_os_family != "Debian"
- name: Install apache2 if not already installed
  apt: 
    name: apache2 
    state: present
    update_cache: yes
- name: Create apache conf file from the template
# File is named as servername.conf and will be put in /etc/apache2/sites-available
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/apache.conf.j2
    dest: /etc/apache2/sites-available/{{ server_name }}.conf
    mode: "0644"
    owner: root
    group: root
  notify: reload_apache
- name: Enable new conf
# It will be enabled if we create a link to this conf file in /etc/apache2/sites-enabled
  file:
    src: /etc/apache2/sites-available/{{ server_name }}.conf
    dest: /etc/apache2/sites-enabled/{{ server_name }}.conf
    remote_src: yes
    owner: root
    group: root
    state: link
  notify: reload_apache
- name: Create home directory for the site
# Home directory will be /var/www/server_name
  file:
    path: /var/www/{{ server_name }}
    state: directory
    mode: "0770"
    owner: www-data
    group: www-data
  notify: reload_apache
- name: Copy index.html to site's home directory
  template:
    src: /home/ansible/ansible/playbooks/roles/exforge.apachesite/templates/index.html.j2
    dest: /var/www/{{ server_name }}/index.html
    mode: "0644"
    owner: www-data
    group: www-data
  notify: reload_apache
#____________________________________________________
# Handlers run after the play is finished, so if a handler called twice (or more), it will
#   run only once.
#
# Add handlers
nano /home/ansible/ansible/playbooks/roles/exforge.apachesite/handlers/main.yml
#____________________________________________________
---
# handlers file for exforge.apachesite
- name: reload_apache
  service:
    name: apache2
    state: reloaded
#____________________________________________________
#
# Now you can run the role with handlers by calling the playbook we wrote at 11:
# Run the playbook
cd /home/ansible/ansible/playbooks
ansible-playbook apachesite.yml
 

14. Error Recovery (Block and rescue)

# Ansible has an exception handling (error recovery) mechanism similar to Python's
#   try-except-finally block.
# 14.1. Block-Rescue-Always usage
# A very simple example playbook would be:
nano /home/ansible/ansible/playbooks/blocktest.yml
#_______________________________________________
#!/usr/bin/env ansible-playbook
- name: Demonstration block-rescue-always
  become: True
  hosts: test201
  vars:
    message1: "1. Message"
    message2: "2. Message"
  tasks:
  - block:
    - name: Task 1
      debug:
        msg: "{{ message1 }}"
    - name: Task 2
      debug:
        msg: "{{ message2 }}"
    - name: Task 3  (Error expected, variable is not defined)
      debug:
        msg: "{{ message3 }}"
    - name: Task 4  (Never expected to run)
      debug:
        msg: "{{ message4 }}"
    rescue:
      - name: Rescue Task
        debug:
          msg: "Some of the messages could not be displayed"
    always:
      - name: Always Task
        debug:
          msg: "Job finished"
#_______________________________________________
#
# 14.2. Explanations
# Tasks in the block (Tasks 1, 2, 3 and 4 in our example) run sequentially. 
# If an error occurs in any task (Task 3 in our example), execution stops and 
#   the control goes to rescue task. Then the tasks in rescue block (Rescue Task in our
#   example) run. Then the tasks in always block (Always task in our example) run.
# If there is no error in tasks, rescue block is skipped and the tasks in always block
#   (Always task in our example) run.
#
# Error recovery is a very important subject in all kinds of programming. I believe you
#   should use it as much as possible to prevent an unexpected termination of programs
#   (playbooks for ansible).
 

15. Skipped Content

# I skipped the following subjects just because I think I won't use them. I believe
#   most of you won't use them ever. 
#
# Ansible vault
# Ansible pull
# Ansible collections
# Testing (with a test tool)
# Writing your own modules
# Using Ansible on Windows servers
# And may be some more that I am not able to know now :)
 

Posted in Ubuntu

Post navigation

User and Group Administration on Ubuntu
KVM On Ubuntu Server 1 (Beginner)
Proudly powered by WordPress | Theme: micro, developed by DevriX.