When we originally contributed the {{ ansible_managed }} feature to the Ansible project it was quite rudimentary: the intention was to have a token which would be templated out to ensure people looking at the remote file would keep their paws off it, warning against manual modifications to the file which would be overwritten at the next playbook run. We added more features, and I think we finalized the variable almost exactly six years ago.

ansible_managed        | {{ ansible_managed }}
template_host          | {{ template_host }}
template_uid           | {{ template_uid }}
template_path          | {{ template_path }}

The output from this template was something like

ansible_managed        | Ansible managed: /tmp/a/input.j2 modified on 2020-09-29 14:12:44 by jpm on rabbit.ww.mens.de
template_host          | rabbit.ww.mens.de
template_uid           | jpm
template_path          | /tmp/a/input.j2

The {{ ansible_managed }} variable was (and still is) configurable via the ansible.cfg mechanisms, and I recall spending quite some time on the time stamp feature, i.e. the modification timestamp of the source template on the controller. It was meant to change on the target only when the source template was modified. All was fine and dandy. I thought.

Years later somebody noticed that there are cases, particularly when using git, when the timestamp of the source changes involuntarily. This, in my opinion unfortunately, caused the project to change the default handling of {{ ansible_managed }} to henceforth output the string Ansible managed only – rather boring. :-)

As mentioned above, we originally made the string configurable, so it’s easy enough to adapt the value to almost anything users want. Here’s the top of my .ansible.cfg showing how the source template file’s modification timestamp is formatted with strftime(3) tokens:

nocows = 1
ansible_managed = Ansible managed: {file} modified on %Y-%m-%d %H:%M:%S by {uid} on {host}

The other tokens are {file} which contains the full path to the source template, {host} which is the controller’s hostname, and {uid}, the owner name of the source template.

There might well be problems with this format on some VCS or remote file system, and quite a few issues have been reported, so configure it as you wish. A different language? Why not?

ansible_managed = Cambiado por {uid}

Meanwhile certain deprecations reportedly having been suggested by developers include

  • {host} should expand to the inventory hostname rather than the controller hostname
  • {file} should expand to the destination file rather than the template file.

both of which quite definitely are not the intended behaviour, on the contrary: if I’m looking at the templated file on the remote node, I know on which host I am, and I know the name of the file! I want to know where the file came from, which is why we designed it thusly.

I still believe {{ ansible_managed }} is a good idea, and as it’s configurable, we can all use it as we wish, and I for one, hope the variable won’t be removed.

Happy templating!


Somebody asked on Mastodon how they could add the last git commit to this variable. It’s not possible to append to {{ ansible_managed }} without modifying the code, but a lookup plugin in the template does the trick:

# {{ ansible_managed }}
# commit: {{ lookup('pipe', 'git rev-parse --short HEAD') }}

Mohamad writes to say:

I store my Ansible templates store my Ansible templates in a git repo with my playbooks & roles. I wanted to include the last commit (plus date and author) that touched the template file in ansible_managed. I was able to accomplish that by putting this (ugly) config line in my ansible.cfg (yes, the double-braces are on purpose):

ansible_managed = "$Ansible {{{{ template_path|basename + lookup('pipe', 'git log --format=",%%h %%ad %%ae" -1 --date=format:"%%Y/%%m/%%d %%H:%%M" ' + template_fullpath|quote)|default(",UNCOMMITED", True) }}}}$"

Now his config headers look like this:

# $Ansible ntpd.conf.j2,cf1cb74 2022/04/10 13:30 self@example.com$

unless a file is still uncommitted, in which case the header will show up like so:

# $Ansible ntpd.conf.j2,UNCOMMITED$