Ansible Roles, Vault, CSV files and more

Tips to improve your Ansible Playbooks

Alt text

General

Ansible has some tricks to advance your playbook syntax. We will make our code reusable by separating tasks and variables and secure them with Ansible-Vault. The playbook I focus on will create two tenants in ACI.

Roles

With roles you can group your playbooks and reference them easily through subtasks. My ansible directory is the standard path /etc/ansible. In this directory I create a new folder called roles. Now I create another folder named create-tenant in roles which will be our first role.

We will create a playbook under /etc/ansible and use the task roles.


/etc/ansible/create-tenant.yml

- name: create-tenant
  hosts: localhost
  connection: local
  gather_facts: False
  vars:
   ansible_python_interpreter: /usr/bin/python3
   aci_host: 192.168.15.81
   aci_username: admin
   aci_password: cisco123

  roles:
   - create-tenant

Next I will create a playbook under /etc/ansible/roles/get-tenant/tasks. When calling a role, Ansible will always check for a playbook named main.yml and execute it first.


/etc/ansible/roles/get-tenant/tasks/main.yml

---
- name: Include tenant yml
  ansible.builtin.include_vars:
    file: tnt.yml

- name: Create Tenant
  cisco.aci.aci_rest:
    host: "{{ aci_host }}"
    username: "{{ aci_username }}"
    password: "{{ aci_password }}"
    validate_certs: false
    path: /api/node/mo/uni.json
    method: post
    content:
      fvTenant: { attributes: { name: "{{ tnt['name'] }}", rn: "tn-{{ tnt['name'] }}", descr: "{{ tnt['desc'] }}", status: created }, children: [] }
  loop: "{{ tntgrp }}"
  loop_control:
    loop_var: tnt

I will use a yml file for my variables and store it in a new folder called vars.


/etc/ansible/rolescreate-tenant/vars/tnt.yml

tntgrp:
 - { name: 'TNT1', desc: 'This is TNT1' }
 - { name: 'TNT2', desc: 'This is TNT2' }

Now we can issue the command ansible-playbook create-tenant.yml and two Tenants will be created.

CSV files

Instead of a YML file for the variables we can also create a CSV file. In VSCode there is a nice extension called Edit CSV from janisdd that lists our CSV files as a table. That makes it easier to work with.


/etc/ansible/rolescreate-tenant/vars/tnt.csv

name,desc
TNT1,this is TNT1
TNT2,this is TNT2

When we activate the Edit CSV extension in VSCode the file will display like this.

Alt text

We have to change the main.yml a bit to import the CSV as list.

---
- name: Include tenant yml
  community.general.read_csv:
    path: roles/create-tenant/vars/tnt.csv
  register: tenants

- name: Create Tenant
  cisco.aci.aci_rest:
    host: "{{ aci_host }}"
    username: "{{ aci_username }}"
    password: "{{ aci_password }}"
    validate_certs: false
    path: /api/node/mo/uni.json
    method: post
    content:
      fvTenant: { attributes: { name: "{{ tnt.name }}", rn: "tn-{{ tnt.name }}", descr: "{{ tnt.desc }}", status: created }, children: [] }
  loop: "{{ tenants.list }}"
  loop_control:
    loop_var: tnt

Now we can issue the command ansible-playbook create-tenant.yml again and two Tenants will be created.

Tags

You can use tags to only run specific tasks in your playbook. Let us add another task which deletes our two tenants by changing the status to deleted and tag all tasks.

---
- name: Include tenant yml
  community.general.read_csv:
    path: roles/create-tenant/vars/tnt.csv
  register: tenants
  tags: create, delete

- name: create login alias for apic login
  set_fact:
      aci_login: &aci_login       
        host: "{{ aci_host }}"
        username: "{{ aci_username }}"
        password: "{{ aci_password }}"
        validate_certs: false
          
- name: Create Tenant
  cisco.aci.aci_rest:
    <<: *aci_login
    path: /api/node/mo/uni.json
    method: post
    content:
      fvTenant: { attributes: { name: "{{ tnt.name }}", rn: "tn-{{ tnt.name }}", descr: "{{ tnt.desc }}", status: created }, children: [] }
  loop: "{{ tenants.list }}"
  loop_control:
    loop_var: tnt
  tags: create

- name: Delete Tenant
  cisco.aci.aci_rest:
    <<: *aci_login
    path: /api/node/mo/uni.json
    method: post
    content:
      fvTenant: { attributes: { name: "{{ tnt.name }}", rn: "tn-{{ tnt.name }}", descr: "{{ tnt.desc }}", status: deleted }, children: [] }
  loop: "{{ tenants.list }}"
  loop_control:
    loop_var: tnt
  tags: delete

Now if we issue the ansible-playbook create_tenant.yml --tags delete command it will only execute the tasks with the delete tag.

Alias

We have two tasks now that both use the same variables to authenticate. We can declare these variables in a specific task called aci_login and use it as an alias for other tasks.

---
- name: Include tenant yml
  community.general.read_csv:
    path: roles/create-tenant/vars/tnt.csv
  register: tenants
  tags: create, delete

- name: create login alias for apic login
  set_fact:
      aci_login: &aci_login       
        host: "{{ aci_host }}"
        username: "{{ aci_username }}"
        password: "{{ aci_password }}"
        validate_certs: false
          
- name: Create Tenant
  cisco.aci.aci_rest:
    <<: *aci_login
    path: /api/node/mo/uni.json
    method: post
    content:
      fvTenant: { attributes: { name: "{{ tnt.name }}", rn: "tn-{{ tnt.name }}", descr: "{{ tnt.desc }}", status: created }, children: [] }
  loop: "{{ tenants.list }}"
  loop_control:
    loop_var: tnt
  tags: create

- name: Delete Tenant
  cisco.aci.aci_rest:
    <<: *aci_login
    path: /api/node/mo/uni.json
    method: post
    content:
      fvTenant: { attributes: { name: "{{ tnt.name }}", rn: "tn-{{ tnt.name }}", descr: "{{ tnt.desc }}", status: deleted }, children: [] }
  loop: "{{ tenants.list }}"
  loop_control:
    loop_var: tnt
  tags: delete

Ansible-Vault

You can encrypt your variables and create a file to decrypt it.

in this example I create the file .vault_pass with cisco as private key. Then I encrypt the cisco123 string and copy/paste the encrypted version in my playbook. I also change file ownership to root.

touch .ansible_vault && echo 'cisco' > .vault_pass && echo '.vault_pass' >> .gitignore
ansible-vault encrypt_string --vault-password-file .vault_pass 'cisco123' --name 'aci_password'
sudo chown root:root file #change file ownership to root
sudo chmod 600 file #only file owner has read and write permissions
sudo ansible-playbook create_tenant.yml --vault-password-file=.vault_pass

create_tenant.yml

---
- name: Create-tenant
  hosts: localhost
  connection: local
  gather_facts: false
  vars:
    ansible_python_interpreter: /usr/bin/python3
    aci_host: 192.168.15.81
    aci_username: admin
    aci_password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      31366662373630346366623034373939366463623437613563613032393338353830646639386466
      6635393465663362316534396532356338623064336336300a363663313535306439383533386538
      31343465633132343832633139393738623433363238323862633062613139323136383835666536
      3038636234656431350a653965663364376162356235306463343834303739346538653932353532
      3730


  roles:
    - create-tenant

Some helpful commands I use for Ansible-Vault debugging.

sudo whoami #check current user rights
ls -l .vault_pass #check file permissions
chmod -R -x+X .vault_pass #remove executable bit
sudo ansible-playbook create_tenant.yml --ask-vault-pass #type cisco when prompted

Here is the recursive directory listing of my folders with the command: tree

├── create_tenant.yml
└── roles
    └── create-tenant
        ├── tasks
        │   └── main.yml
        └── vars
            ├── tnt.csv
            └── tnt.yml

Thanks for reading my article. If you have any questions or recommendations you can message me via arvednetblog@gmail.com.