Skip to main content

Using Ansible to verify configurations

Ansible can do many things. This article demonstrates how to use Ansible to verify your configurations.

Ansible is a great tool for configuring servers to the state you desire. You can create a playbook, and if correctly written, it always yields the same state no matter how many times you run it. This is called idempotency. You can also run an Ansible playbook with the --check option and verify what the playbook would change if it were run so that you don't unexpectedly make a change when you do not want to.

But what if you need to verify the configuration of existing servers that were not originally configured with Ansible? There are tools out there for this purpose, such as Chef's InSpec, testinfra, and serverspec. But, if you already know Ansible, you can use some of its built-in functionality to do this.

Example 1

In this example, I have six hosts of various Linux distributions and versions. Because the actual memory reported can vary, I'm going to check for memory between 800 MB and 1100 MB. The goal is for hosts that meet the criteria to pass with changed=0 and hosts that fail the criteria to output changed=1:

- name: assert
  hosts: all

  tasks:

  - name: check if memory is between 800 and 1100MB
    assert:
      that:
        - ansible_memtotal_mb | int >= 800
        - ansible_memtotal_mb | int <= 1100
      fail_msg: "Memory is {{ ansible_memtotal_mb }}MB not 1024MB"
    register: result
    changed_when:
      - result.evaluated_to is defined
      - result.evaluated_to == False
    failed_when: False

The results:

[kpirkle@defiant config-verify]$ ansible-playbook assert.yml


PLAY [assert] ****************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************
ok: [wookie]
ok: [ewok]
ok: [centurion]
ok: [web]
ok: [venture]
ok: [c3po]

TASK [check if memory is between 800 and 1100MB] *****************************************************************************************************************************************************************
ok: [venture] => {
    "changed": false,
    "failed_when_result": false,
    "msg": "All assertions passed"
}
ok: [ewok] => {
    "changed": false,
    "failed_when_result": false,
    "msg": "All assertions passed"
}
ok: [web] => {
    "changed": false,
    "failed_when_result": false,
    "msg": "All assertions passed"
}
changed: [centurion] => {
    "assertion": "ansible_memtotal_mb | int <= 1100",
    "changed": true,
    "evaluated_to": false,
    "failed_when_result": false,
    "msg": "Memory is 7737MB not 1024MB"
}
ok: [wookie] => {
    "changed": false,
    "failed_when_result": false,
    "msg": "All assertions passed"
}
ok: [c3po] => {
    "changed": false,
    "failed_when_result": false,
    "msg": "All assertions passed"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************
c3po                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
centurion                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ewok                       : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
venture                    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web                        : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
wookie                     : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

assert

The function of the assert module, per the documentation, is to "assert given expressions are true."

In this case, five of the six passed - one failed because host centurion had more memory than the range I was checking. I used the fail_msg option of the assert module to provide useful information as to why it failed. There are also options to always display a custom message with msg and to pass a custom message when the assertion passes called success_msg.

changed_when

The changed_when option is not a module but rather a built-in function that alters how error handling is done for a task. In this case, we are saving the results from the assert test to the result variable. We then alter the changed value based on two tests: 1) does the evaluated_to variable exist, and 2) is it false? If it exists and is false, we know the test failed, and we return the result as "changed."

failed_when

To prevent the play from failing and ending the run of the playbook, set the failed_when option to false.

Example 2

Another useful configuration comparison strategy is to use a checksum tool like md5sum to compare files.

---
- name: compare
  hosts: all
  gather_facts: no
 
  vars:
 
    std_conf_crontab: 'c39252b11aad842fcb75e05c6a27eef8'
    std_conf_lvm: '2d90187abd40dbcb6fc6de41640fd022'
    std_conf_resolv: 'db323688118c844a76ebd6c70508b434'

  tasks:

  - name: compare config files
    stat:
      path: '{{ item.file }}'
      checksum_algorithm: md5
    register: result
    changed_when: item.md5sum != result.stat.checksum
    failed_when: False
    loop:
      - { file: /etc/crontab, md5sum: '{{ std_conf_crontab }}' }
      - { file: /etc/lvm/lvm.conf, md5sum: '{{ std_conf_lvm }}' }
      - { file: /etc/resolv.conf, md5sum: '{{ std_conf_resolv }}'}   

The result:

[kpirkle@defiant config-verify]$ ansible-playbook compare.yml

PLAY [compare] ****************************************************************************************************************************

TASK [compare config files] ***************************************************************************************************************
changed: [wookie] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [centurion] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [web] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
changed: [wookie] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [centurion] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [venture] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [wookie] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [ewok] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
ok: [centurion] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [web] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
changed: [venture] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [web] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
changed: [ewok] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [ewok] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [venture] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})
ok: [c3po] => (item={'file': '/etc/crontab', 'md5sum': 'c39252b11aad842fcb75e05c6a27eef8'})
changed: [c3po] => (item={'file': '/etc/lvm/lvm.conf', 'md5sum': '2d90187abd40dbcb6fc6de41640fd022'})
ok: [c3po] => (item={'file': '/etc/resolv.conf', 'md5sum': 'db323688118c844a76ebd6c70508b434'})

PLAY RECAP ********************************************************************************************************************************
c3po                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
centurion                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ewok                       : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
venture                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web                        : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
wookie                     : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

In this example, we are setting variables for the md5sums of three files that we want to check for consistency. Our task then uses the stat module to calculate the MD5 checksum of each file and compare it to the defined variable. Once again, we are using the changed_when function to make the comparison and render a "changed" state when there isn't a match.

Example 3

My final example is a way to check the contents of a file for a specific item. One way to do this is by using the shell module and using grep.

---
- name: grep
  hosts: all
  gather_facts: no

  tasks:

  - name: grep for nameserver
    shell: grep 'nameserver 192.168.0.1' /etc/resolv.conf
    register: result
    changed_when: result.rc != 0
    failed_when: False

The result:

[kpirkle@defiant config-verify]$ ansible-playbook grep.yml

PLAY [grep] *******************************************************************************************************************************

TASK [grep for nameserver] ****************************************************************************************************************
ok: [wookie]
ok: [web]
ok: [centurion]
ok: [ewok]
changed: [venture]
ok: [c3po]

PLAY RECAP ********************************************************************************************************************************
c3po                       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
centurion                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
ewok                       : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
venture                    : ok=1    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web                        : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
wookie                     : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Here again, we are using the combination of changed_when and failed_when to manipulate the output to be useful. By registering the output of grep to the result variable, we can then check the return code of the command and find out if the desired nameserver string is present. More complex commands can be used if needed.

Wrap up

Hopefully, others find these tips useful. You can use Ansible as not only a tool to configure servers to the desired state but also as an investigative tool to verify the configuration of servers or possibly as an ad-hoc monitoring tool.

[ Need more on Ansible? Take a free technical overview course from Red Hat. Ansible Essentials: Simplicity in Automation Technical Overview. ]

Topics:   Automation   Ansible  
Author’s photo

Kent Pirkle

Kent is a Linux Systems Engineer with over 20 years of experience with Linux and UNIX systems. His current focus is on Ansible, automation, and infrastructure-as-code. He is a member of the Red Hat Accelerators and is a Red Hat Certified Engineer.   More about me

Try Red Hat Enterprise Linux

Download it at no charge from the Red Hat Developer program.