Templates and Variables

In this tutorial, we are going to make the roles that we created earlier dynamically by adding templates and defining variables.

Variables

Variables are of two types

  • Automatic Variables/ Facts
  • User Defined Variables

Lets try to discover information about our systems by using facts.

Finding Facts About Systems

  • Run the following command to see to facts of db servers
cd chap7
ansible db -m setup

[Output]

192.168.61.11 | SUCCESS => {
        "ansible_facts": {
            "ansible_all_ipv4_addresses": [
                "10.0.2.15",
                "192.168.61.11"
            ],
            "ansible_all_ipv6_addresses": [
                "fe80::a00:27ff:fe30:3251",
                "fe80::a00:27ff:fe8e:83e0"
.....
                "tz_offset": "+0100",
                "weekday": "Monday",
                "weekday_number": "1",
                "weeknumber": "36",
                "year": "2016"
            }

Filtering facts

  • Use filter attribute to extract specific data
ansible db -m setup -a "filter=ansible_distribution"

[Output]

192.168.61.11 | SUCCESS => {
  "ansible_facts": {
      "ansible_distribution": "CentOS"
  },
  "changed": false
}  

Defining release versions with vars

Currently, while deploying the application, the versions of the artifacts as well as release directories are defined statically. This should change to vars so that the version can be defined from one place, and dynamically so.

Define the default vars

file: roles/frontend/defaults/main.yml

---
# defaults file for frontend
  app:
    version: 1.5

Update tasks to use the var defined above,

wherever you see version number e.g. 1.1, replace that with {{ app.version }} var.

file: roles/frontend/tasks/main.yml

- name: Download and extract the release
  unarchive:
    src: https://github.com/devopsdemoapps/devops-demo-app/archive/{{ app.version }}.tar.gz
    dest: /opt/app/release
    owner: apache
    group: apache
    creates: /opt/app/release/devops-demo-app-{{ app.version }}
    remote_src: yes

- name: create a symlink
  file:
    src: /opt/app/release/devops-demo-app-{{ app.version }}
    dest: /var/www/html/app
    owner: apache
    group: apache
    state: link

Try This: * Run playbook and check whether the above code works * Change the version e.g. 1.4 and check if it has any effect

Creating application configurations dynamically

Application configs are defined with config.ini file. The version of config.ini as shipped with the application is as follows,

[database]
hostname = DBHOST
username = SQLUSER
password = SQLPASSWORD
dbname = SQLDBNAME

[environment]
environment = ENVNAME

[prefs]
color  = white
fruit  = apple
car    = fiat
laptop = dell

You should be able to customize these configs. In order to do that, you need to do split this into 2 things as follows,

  • vars which define the actual properties and allow you to change it from different places
  • a jinja2 template which will collect and process the vars on the fly and create the resulting configs dynamically

Defining the vars for app config

file: roles/frontend/defaults/main.yml

---
# defaults file for frontend
  app:
    version: 1.5
    env: LOCALDEV

  fav:
    color: white
    fruit: orange
    car: chevy
    laptop: toshiba

  dbconn:
    host: localhost
    user: root
    pass: changeme
    db: devopsdemo

Create directory and template file. You could either use the commands below or directly create it from the graphical editor.

cd roles/frontend
mkdir templates
touch templates/config.ini.j2

file: roles/frontend/templates/config.ini.j2


[database]
hostname = {{ dbconn['host'] }}
username = {{ dbconn['user'] }}
password = {{ dbconn['pass'] }}
dbname = {{ dbconn['db'] }}

[environment]
environment = {{ app['env'] }}

[prefs]
color  = {{ fav['color'] }}
fruit  = {{ fav['fruit'] }}
car    = {{ fav['car'] }}
laptop = {{ fav['laptop'] }}

Adding task to generate the config from jinja2 template

file: roles/frontend/tasks/main.yml ( append the following code to the file)

- name: add application configs
  template:
    src: config.ini.j2
    dest: /var/www/html/app/config.ini
    owner: apache
    group: apache
    mode: 0644

Now, run the playbook, reload the application page and validate. You should also browse to http://IPADDRESS:81/app/prefs.php to view if it prints the preferences you defined in the default vars.

cd chap7
ansible-playbook  app.yml

Beyond defaults - Playing with vars precedence

Lets define the variables from couple of other places, to learn about the Precedence rules. We will create, group_vars playbook vars

Since we are going to define the variables using multi level hashes, define the way hashes behave when defined from multiple places.

Update chap7/ansible.cfg and add the following,

hash_behaviour=merge

Lets create group_vars and create a group prod to define vars common to all prod hosts.

cd chap7
mkdir group_vars
cd group_vars
touch prod.yml

Edit group_vars/prod.yml file and add the following contents,

---
  fav:
    color: yellow
    fruit: guava

Lets also add vars to playbook. Edit app.yml and add vars as below,

---
  - hosts: app
    become: true
    vars:
      fav:
        fruit: mango
    roles:
      - apache
      - php
      - frontend

Execute the playbook and check the output

ansible-playbook app.yml

If you view the content of the html file generated, you would notice the following,

<h3> color     : yellow </h3>
<h3> fruit     : mango </h3>
<h3> car       : chevy </h3>
<h3> laptop    : toshiba </h3>
fav item role defaults group_vars playbook_vars
color white yellow
fruit orange guava mango
car chevy
laptop toshiba
  • value of color comes from group_vars/all.yml
  • value of fruit comes from playbook vars
  • value of car and laptop comes from role defaults

Registered Variables

Lets create a playbook to run a shell command, register the result and display the value of registered variable.

Create register.yml in chap7 directory

---
  - name: register variable example
    hosts: local
    tasks:
      - name: install net tools to make ifconfig command available
        package:
          name: net-tools
          state: installed

      - name: run a shell command and register result
        shell: "/sbin/ifconfig eth0"
        register: result

      - name: print registered variable
        debug: var=result

Execute the playbook to display information about the registered variable.

ansible-playbook  register.yml

Adding support for Ubuntu

Apache role that we have developed supports only RedHat based systems at the moment. To add support for ubuntu (app2), we must handle platform specific differences.

e.g.

RedHat Debian
Package Name httpd apache2
Service Name httpd apache2

OS specific configurations can be defined by creating role vars and by including those in tasks.

file: roles/apache/vars/RedHat.yml

---
apache:
  package:
    name: httpd
  service:
    name: httpd
    status: started

file: roles/apache/vars/Debian.yml

---
apache:
  package:
    name: apache2
  service:
    name: apache2
    status: started

Lets now selectively include those var files from tasks/main.yml . Also selectively call configurations. file: role/apache/tasks/main.yml

---
# tasks file for apache
  - include_vars: "{{ ansible_os_family }}.yml"
  - include: install.yml
  - include: service.yml
  - include: config_{{ ansible_os_family }}.yml  

We are now going to create two different config tasks. Since the current config is applicable to RedHat, lets rename it to config_RedHat.yml

mv roles/apache/tasks/config.yml roles/apache/tasks/config_RedHat.yml

We will now create a new config for Debian

file: roles/apache/tasks/config_Debian.yml

- name: Copying index.html file...
  template: >
    src=index.html.j2
    dest=/var/www/html/index.html
    mode=0777

Update tasks and handlers to install and start the correct service

tasks/install.yml

---
  - name: install httpd on centos
    package: >
      name={{ apache['package']['name']}}
      state=installed

tasks/service.yml

---
  - name: start httpd service
    service: >
      name={{ apache['service']['name']}}
      state={{ apache['service']['status']}}

handlers/main.yml

---
# handlers file for apache
  - name: restart apache service
    service: >
      name={{ apache['service']['name']}}
      state=restarted

Now add host app3 to the inventory

file: environments/prod

[app]
app1
app2
app3 ansible_password=codespaces

and apply playbook

ansible-playbook app.yml

Exercises

  • Create host specific variables in host_vars/HOSTNAME for one of the app servers, and define some variables values specific to the host. See the output after applying playbook on this node.
  • Generate MySQL Configurations dynamically using templates and modules.
  • Create a template for my.cnf. Name it as roles/mysql/templates/my.cnf.j2
  • Replace parameter values with templates variables
  • Define variables in role defaults.