PL - 008 — Ansible Conditionals, Handlers and Tags

Ansible Conditionals with Loops

Ansible conditionals allow tasks to be executed only when specified conditions are true.

  • with_items –> looping
  • when –> conditional execution

Note: The passwords shown here are lab-only examples. In a real environment, credentials should not be stored in plaintext in a playbook or variables file. Use Ansible Vault or another secrets-management solution.

[cnode@control-node ansible-lab]$ cat ansible.cfg 
[defaults]
inventory = ./inventory
remote_user = cnode

[privilege_escalation]
become = true
become_method = sudo
[cnode@control-node ansible-lab]$ cat inventory 
[develop]
dev1
dev2

[test]
testserver

[production]
prodserver

[testprod:children]
test
production
[cnode@control-node ansible-lab]$ vim vars/newemployee.yml
[cnode@control-node ansible-lab]$ cat vars/newemployee.yml 
---
newemp:
  - name: suman
    pass: suman123
    team: developers
  - name: raman
    pass: raman123
    team: developers
  - name: ravi
    pass: ravi123
    team: production
  - name: tom
    pass: tom123
    team: testers 
[cnode@control-node ansible-lab]$ vim create_newemp.yml
[cnode@control-node ansible-lab]$ cat create_newemp.yml
---
# Create new employees

- name: play to create new employee
  hosts: all
  vars_files:
    - vars/newemployee.yml

  tasks:
    - name: Create a group
      ansible.builtin.group:
        name: newemp
        state: present

    - name: Create developer accounts
      ansible.builtin.user:
        name: "{{ item.name }}"
        password: "{{ item.pass | password_hash('sha512') }}"
        groups: newemp
      with_items: "{{ newemp }}"
      when: 
        - item.team == "developers"
        - inventory_hostname in groups['develop']

    - name: Create production accounts
      ansible.builtin.user:
        name: "{{ item.name }}"
        password: "{{ item.pass | password_hash('sha512') }}"
        groups: newemp
      with_items: "{{ newemp }}"
      when: 
        - item.team == "production" 
        - inventory_hostname in groups['production']

    - name: Create test accounts
      ansible.builtin.user:
        name: "{{ item.name }}"
        password: "{{ item.pass | password_hash('sha512') }}"
        groups: newemp
      with_items: "{{ newemp }}"
      when: 
        - item.team == "testers" 
        - inventory_hostname in groups['test']
[cnode@control-node ansible-lab]$ 

PLAY [play to create new employee] **************************************************

TASK [Gathering Facts] **************************************************************
ok: [dev1]
ok: [testserver]
ok: [prodserver]
ok: [dev2]

TASK [Create a group] ***************************************************************
changed: [prodserver]
changed: [dev1]
changed: [testserver]
changed: [dev2]

TASK [Create developer accounts] ****************************************************
[DEPRECATION WARNING]: Encryption using the Python crypt module is deprecated. The 
Python crypt module is deprecated and will be removed from Python 3.13. Install the 
passlib library for continued encryption functionality. This feature will be removed
 in version 2.17. Deprecation warnings can be disabled by setting 
deprecation_warnings=False in ansible.cfg.
skipping: [prodserver] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [testserver] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [testserver] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [testserver] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [prodserver] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [prodserver] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [testserver] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [testserver]
skipping: [prodserver] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [prodserver]
changed: [dev1] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'})
changed: [dev2] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'})
changed: [dev2] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'})
skipping: [dev2] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
changed: [dev1] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'})
skipping: [dev2] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [dev1] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [dev1] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 

TASK [Create production accounts] ***************************************************
skipping: [dev1] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [dev1] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [dev1] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [dev1] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [dev2] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [dev2] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [dev2] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [dev1]
skipping: [dev2] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [dev2]
skipping: [testserver] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [testserver] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [testserver] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [prodserver] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [testserver] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [prodserver] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [testserver]
changed: [prodserver] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'})
skipping: [prodserver] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 

TASK [Create test accounts] *********************************************************
skipping: [dev1] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [dev1] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [dev1] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [dev1] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [dev2] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [dev1]
skipping: [dev2] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [dev2] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [dev2] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [testserver] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [dev2]
skipping: [testserver] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [testserver] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [prodserver] => (item={'name': 'suman', 'pass': 'suman123', 'team': 'developers'}) 
skipping: [prodserver] => (item={'name': 'raman', 'pass': 'raman123', 'team': 'developers'}) 
skipping: [prodserver] => (item={'name': 'ravi', 'pass': 'ravi123', 'team': 'production'}) 
skipping: [prodserver] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'}) 
skipping: [prodserver]
changed: [testserver] => (item={'name': 'tom', 'pass': 'tom123', 'team': 'testers'})

PLAY RECAP **************************************************************************
dev1                       : ok=3    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
dev2                       : ok=3    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
prodserver                 : ok=3    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   
testserver                 : ok=3    changed=2    unreachable=0    failed=0    skipped=2    rescued=0    ignored=0   

[cnode@control-node ansible-lab]$ 


[cnode@control-node ansible-lab]$ ansible develop -m shell -a 'id suman; id raman; getent group newemp'
dev2 | CHANGED | rc=0 >>
uid=1012(suman) gid=1013(suman) groups=1013(suman),1012(newemp)
uid=1013(raman) gid=1014(raman) groups=1014(raman),1012(newemp)
newemp:x:1012:suman,raman
dev1 | CHANGED | rc=0 >>
uid=1012(suman) gid=1013(suman) groups=1013(suman),1012(newemp)
uid=1013(raman) gid=1014(raman) groups=1014(raman),1012(newemp)
newemp:x:1012:suman,raman
[cnode@control-node ansible-lab]$ ansible production -m shell -a 'id ravi; getent group newemp'
prodserver | CHANGED | rc=0 >>
uid=1011(ravi) gid=1011(ravi) groups=1011(ravi),1003(newemp)
newemp:x:1003:ravi
[cnode@control-node ansible-lab]$ ansible test -m shell -a 'id tom; getent group newemp'
testserver | CHANGED | rc=0 >>
uid=1011(tom) gid=1011(tom) groups=1011(tom),1003(newemp)
newemp:x:1003:tom
[cnode@control-node ansible-lab]$ 


[cnode@control-node ansible-lab]$ ssh cnode@dev1
Last login: Sun Sep 13 07:30:16 2026 from 192.168.254.15
[cnode@dev1 ~]$ id suman
uid=1012(suman) gid=1013(suman) groups=1013(suman),1012(newemp)
[cnode@dev1 ~]$ id raman
uid=1013(raman) gid=1014(raman) groups=1014(raman),1012(newemp)
[cnode@dev1 ~]$ getent group newemp
newemp:x:1012:suman,raman
[cnode@dev1 ~]$
cnode@dev1 ~]$ exit
logout
Connection to dev1 closed.
[cnode@control-node ansible-lab]$ 

[cnode@control-node ansible-lab]$ ssh cnode@prodserver
Last login: Sun Sep 13 07:30:56 2026 from 192.168.254.15
[cnode@prodserver ~]$ id ravi
uid=1011(ravi) gid=1011(ravi) groups=1011(ravi),1003(newemp)
[cnode@prodserver ~]$ getent group newemp
newemp:x:1003:ravi
[cnode@prodserver ~]$ exit
logout
Connection to prodserver closed.
[cnode@control-node ansible-lab]$ 

Handlers

Handlers are special tasks in Ansible that run only when they are notified by another task.

A handler runs when it is notified by a task that reports a change. By default, notified handlers run after the regular tasks in the play have completed.

 
[cnode@control-node ansible-lab]$ ls
ansible.cfg  inventory
[cnode@control-node ansible-lab]$ mkdir files
[cnode@control-node ansible-lab]$ vim files/index.html
[cnode@control-node ansible-lab]$ cat files/index.html
<html>
<head>
    <title>Ansible Handler Practice</title>
</head>
<body>
    <h1>Hello from Ansible!</h1>
    <p>This page was deployed using Ansible.</p>
</body>
</html>

[cnode@control-node ansible-lab]$ vim handlers.yml
[cnode@control-node ansible-lab]$ cat handlers.yml 
---
- name: Practice Ansible handlers
  hosts: develop
  become: true

  tasks:
    - name: Install Apache
      ansible.builtin.dnf:
        name: httpd
        state: present

    - name: Copy website
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      notify:
        - Restart apache

  handlers:
    - name: Restart apache
      ansible: builtin.service:
        name: httpd
        state: restarted
[cnode@control-node ansible-lab]$
 
[cnode@control-node ansible-lab]$ ansible-playbook --syntax-check handlers.yml 
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: Expecting value: line 1 column 1 (char 0)

Syntax Error while loading YAML.
  mapping values are not allowed in this context

The error appears to be in '/home/cnode/ansible-lab/handlers.yml': line 21, column 31, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

    - name: Restart apache
      ansible: builtin.service:
                              ^ here
[cnode@control-node ansible-lab]$ vim +21 handlers.yml 
[cnode@control-node ansible-lab]$ ansible-playbook --syntax-check handlers.yml 

playbook: handlers.yml
[cnode@control-node ansible-lab]$ 

[cnode@control-node ansible-lab]$ ansible-playbook handlers.yml 

PLAY [Practice Ansible handlers] ****************************************************

TASK [Gathering Facts] **************************************************************
ok: [dev2]
ok: [dev1]

TASK [Install Apache] ***************************************************************
changed: [dev1]
changed: [dev2]

TASK [Copy website] *****************************************************************
changed: [dev1]
changed: [dev2]

RUNNING HANDLER [Restart apache] ****************************************************
changed: [dev2]
changed: [dev1]

PLAY RECAP **************************************************************************
dev1                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
dev2                       : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[cnode@control-node ansible-lab]$ 

[cnode@control-node ansible-lab]$ ansible-playbook handlers.yml

PLAY [Practice Ansible handlers] ********************************************************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************************************************************************
ok: [dev1]
ok: [dev2]

TASK [Install Apache] *******************************************************************************************************************************************************
ok: [dev1]
ok: [dev2]

TASK [Copy website] *********************************************************************************************************************************************************
ok: [dev1]
ok: [dev2]

PLAY RECAP ******************************************************************************************************************************************************************
dev1                       : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
dev2                       : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[cnode@control-node ansible-lab]$ 

[cnode@control-node ansible-lab]$ cat files/index.html 
<html>
<head>
    <title>Ansible Handler Practice</title>
</head>
<body>
    <h1>Hello from Ansible!</h1>
    <p>This page was deployed using Ansible.</p>
</body>
</html>
[cnode@control-node ansible-lab]$ vim files/index.html 
[cnode@control-node ansible-lab]$ cat files/index.html 
<html>
<head>
    <title>Ansible Handler Practice</title>
</head>
<body>
    <h1>Hello from Ansible Handler!</h1>
    <p>This page was deployed using Ansible.</p>
</body>
</html>
[cnode@control-node ansible-lab]$ ansible-playbook handlers.yml

PLAY [Practice Ansible handlers] ****************************************************

TASK [Gathering Facts] **************************************************************
ok: [dev1]
ok: [dev2]

TASK [Install Apache] ***************************************************************
ok: [dev1]
ok: [dev2]

TASK [Copy website] *****************************************************************
changed: [dev1]
changed: [dev2]

RUNNING HANDLER [Restart apache] ****************************************************
changed: [dev2]
changed: [dev1]

PLAY RECAP **************************************************************************
dev1                       : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
dev2                       : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[cnode@control-node ansible-lab]$  

Here:

  • If index.html is changed or copied for the first time, the Copy website task reports changed and the Restart apache handler runs.
  • If index.html is already up to date, the task reports ok and the handler does not run.
  • If index.html is modified later, the task reports changed again and the handler runs again.

Conclusion:

Handlers are event-driven tasks that are triggered by notifications from tasks that report a change.


Ansible Tags

A tag is a label assigned to an Ansible task that allows specific tasks or groups of tasks to be selectively executed from a playbook.

Tags are useful when a playbook contains multiple independent tasks and you want to execute only a particular set of tasks without running the entire playbook.

Ansible tags are labels assigned to tasks that allow selected tasks to be executed without running the entire playbook.

[cnode@control-node ansible-lab]$ vim tags_maildeploy.yml 
[cnode@control-node ansible-lab]$ cat tags_maildeploy.yml 
---
# Ansible Tags Practice

- name: Deploy SMTP and POP/IMAP services
  hosts: testprod
  become: true

  tasks:

    - name: Install SMTP server package
      ansible.builtin.yum:
        name: postfix
        state: present
      tags:
        - smtp_deploy

    - name: Start SMTP service
      ansible.builtin.service:
        name: postfix
        state: started
        enabled: true
      tags:
        - smtp_deploy

    - name: Allow SMTP packets through firewall
      ansible.posix.firewalld:
        service: smtp
        state: enabled
        permanent: true
        immediate: true
      tags:
        - smtp_deploy

    - name: Install POP and IMAP server package
      ansible.builtin.yum:
        name: dovecot
        state: present
      tags:
        - dovecot_deploy

    - name: Start POP and IMAP service
      ansible.builtin.service:
        name: dovecot
        state: started
        enabled: true
      tags:
        - dovecot_deploy

    - name: Allow POP3 packets through firewall
      ansible.posix.firewalld:
        service: pop3
        state: enabled
        permanent: true
        immediate: true
      tags:
        - dovecot_deploy
[cnode@control-node ansible-lab]$ ansible-playbook tags_maildeploy.yml --list-tags

playbook: tags_maildeploy.yml

  play #1 (testprod): Deploy SMTP and POP/IMAP services	TAGS: []
      TASK TAGS: [dovecot_deploy, smtp_deploy]
      

[cnode@control-node ansible-lab]$ ansible-playbook tags_maildeploy.yml --list-tags --tags smtp_deploy

playbook: tags_maildeploy.yml

  play #1 (testprod): Deploy SMTP and POP/IMAP services	TAGS: []
      TASK TAGS: [smtp_deploy]
[cnode@control-node ansible-lab]$ ansible-playbook tags_maildeploy.yml --list-tasks --tags dovecot_deploy

playbook: tags_maildeploy.yml

  play #1 (testprod): Deploy SMTP and POP/IMAP services	TAGS: []
    tasks:
      Install POP and IMAP server package	TAGS: [dovecot_deploy]
      Start POP and IMAP service	TAGS: [dovecot_deploy]
      Allow POP3 packets through firewall	TAGS: [dovecot_deploy]
[cnode@control-node ansible-lab]$ 


[cnode@control-node ansible-lab]$ ansible-playbook --syntax-check tags_maildeploy.yml 

playbook: tags_maildeploy.yml
[cnode@control-node ansible-lab]$ ansible-playbook tags_maildeploy.yml --tags smtp_deploy

PLAY [Deploy SMTP and POP/IMAP services] ********************************************

TASK [Gathering Facts] **************************************************************
ok: [testserver]
ok: [prodserver]

TASK [Install SMTP server package] **************************************************
ok: [prodserver]
ok: [testserver]

TASK [Start SMTP service] ***********************************************************
ok: [testserver]
ok: [prodserver]

TASK [Allow SMTP packets through firewall] ******************************************
ok: [prodserver]
ok: [testserver]

PLAY RECAP **************************************************************************
prodserver                 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
testserver                 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[cnode@control-node ansible-lab]$ 

[cnode@control-node ansible-lab]$ ansible-playbook tags_maildeploy.yml --tags dovecot_deploy

PLAY [Deploy SMTP and POP/IMAP services] ********************************************

TASK [Gathering Facts] **************************************************************
ok: [testserver]
ok: [prodserver]

TASK [Install POP and IMAP server package] ******************************************
changed: [prodserver]
changed: [testserver]

TASK [Start POP and IMAP service] ***************************************************
changed: [prodserver]
changed: [testserver]

TASK [Allow POP3 packets through firewall] ******************************************
changed: [testserver]
changed: [prodserver]

PLAY RECAP **************************************************************************
prodserver                 : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
testserver                 : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[cnode@control-node ansible-lab]$