If you’re using Ansible to manage configuration files on your servers, you’re probably familiar with the
{{ ansible_managed }}
tag .
This tag is well behaved from an idempotentcy perspective, but in the nitty-gritty of managing servers where there’s a chance of interaction by humans, this built-in tag leaves a lot to be desired.
Enter Ansible filter plugins, and a little bit of magic in group_vars.
This filter plugin will obtain (according to git pretty-formats) the commit hash, author, and date of the last commit to the file provided by the filter plugin structure:
#!/usr/bin/python # this should be saved to ./filter_plugins/get_last_commit_to_file.py class FilterModule(object): def filters(self): return { 'get_last_commit_to_file': self.get_last_commit_to_file } def get_last_commit_to_file(self, file_path): import os try: # py3 from shlex import quote except ImportError: # py2 from pipes import quote stream = os.popen('git log -n 1 --pretty=format:\'%h by %an at %aD\' -- ' + quote(file_path)) output = stream.read() return output
So, now we can define two new variables in our group_vars/all.yml
file: ansible_workspace_git_url
and ansible_managed_custom
.
Notice the use of our custom filter plugin – we can pipe the variable template_path variable (which Ansible gives to us for free) into our filter plugin to obtain a string about the git history of our file!
ansible_workspace_git_url: "https://github.com/myrepo" ansible_managed_custom: " Ansible Managed for {{ inventory_hostname }} ({{ansible_system_vendor}}::{{ansible_product_name}}::{{ansible_product_serial}}) \n Ansible Git Repository URL: {{ ansible_workspace_git_url }}\n Inventory File: {{ inventory_file|replace(inventory_dir,'') }}@{{ inventory_file | get_last_commit_to_file }}\n Template file: {{ template_path|replace(inventory_dir,'') }}@{{ template_path | get_last_commit_to_file }}\n Role Context: {{ role_name | default('None') }}\n Rendered Template Target: {{ template_destpath }}"
We now have available for use inside any template a nicely formatted (but not necessarily idempotent) header which gives any readers DETAILED information including:
- The hostname and BIOS serial number of the host to which this file was rendered
- The git repository of the playbook responsible for this template
- The filename of the template used to render this file
- The hash of the last commit which affected the template used to render this file
- The filename of the inventory supplying variables to render this template
- The hash of the last commit which affected the inventory supplying variables to render this template
- The target path on the target server to where this file was rendered
Now, anytime I’m templating out a file (well, at least any file that supports multiline comments), I can just include our new ansible_managed_custom
variable:
<!-- {{ ansible_managed_custom }} -->
By switching to this header format, I was able to better assist my teammates with identifying why a template was rendered in a particular fashion, as well as provide them details about how to fix any problems they may find. This template format also helps answer questions about whether the rendered file was _copied_ from one server to another (possibly in a migration) and whether the file is likely to still be under Ansible management.