ansible_managed is a string that can be inserted into files written by Ansible’s config templating system. You put the macro string # {{ ansible_managed }} in your jinja2 template and it gets expanded to something meaningful like:

# Ansible managed: /path/to/file/template/hosts.j2 modified on 2014-09-24 10:52:51 by username on hostname

You get a good idea of where the file came from. Unfortunately, templates work only with ansible playbooks and not with the direct ansible command. But even when you use the copy module outside a playbook it is a good practice to put a comment that includes {{ ansible_managed }} at the beginning of the file. It serves as a handy reminder on how this file got installed in the first place. And in the future, if you make a template and a playbook work with it, you’re already set.

Ansible offers the convenience of running scripts on remote servers. But as the documentation notes:

It is usually preferable to write Ansible modules than pushing scripts. Convert your script to an Ansible module for bonus points!

There is a reason for this. Usually you have ansible run a script on your behalf when what you want to do is not achievable via a module or some combination of modules in a playbook. In extreme circumstances you will need to run a script via ansible when the receiving computer has no Python installed.

But there is a problem with running scripts this way: They are opaque.

A playbook that is applied to your machines is actually a model of that part of the machines that you want to manage. And ansible is your sensor that deals with the situation when things go sour.

It is very easy to write a script that does one thing well to one machine and does not check for failure. Now apply this to 100 or 500 machines that are similar, yet have some subtle differences between them. Can you imagine what a rewrite your script needs in order to account for all corner cases? And if you make it bullet-proof, congratulations! You’re half-way through to making your own incompatible version of ansible.

Having said that, I am guilty of running scripts instead of describing work to be done in a playbook. This mostly involves stuff that needs to be executed from a login shell (hello rvm!) which means the script begins with #!/bin/bash. However, in order to exercise better control in such situations I am not running more than one command plus checks for return codes in every script. This breaks the script down in many smaller ones, but allows me a better view when something goes wrong. Because my playbooks instead of having one script directive, they have 5 or six in a row.

You may have not described an accurate model of what you want to do using a playbook’s markup, but at least the name: directive for every single task is accurate enough to let you know what is executing, rather than having it issue a larger script where you wait whether it succeeded or not, and if not try to find out from which point exactly to roll back (if rolling back is possible).

So the new rule is:

When pushing a script through ansible, it should execute one command only plus any checks needed for return status.

The ping module documentation says that it does not make sense in playbooks, but it is useful only for /usr/bin/ansible. Well I think there is a case where you can include it in a playbook, and that is when you disable fact gathering. I really want to know if there is something wrong with connecting to a server, prior to starting executing the whole playbook scenario and be left with a half played one to redo. So, at least for the host sizes that I apply this, it does not hurt to have this as the first task:

- hosts: whatever
  user: whoever
  gather_facts: no
  - name: ping all hosts

The fact gathering phase implicitly runs the setup module. If your play does not make use of fact computation, you may want to disable it and use ping, just to check how ssh communicates with ansible before feeding it work to do.

After a certain size of servers, it is impossible to remember whether they are all current or not, or even check a documentation wiki page to find out about. So how can one use ansible to find out the answer? The setup module enters the room. Assuming an all Debian installation one could run:

ansible debian-machines -m setup --tree /tmp/invetory
cd /tmp/inventory
grep ansible_distribution_version * | grep -v 7\.2

This will list Debian machines not running 7.2 (Wheezy). You can build more complex versions of the above to match your infrastructure.

PS: Many thanks to @laserllama and @jpmens.

I think I wrote the following playbook a few months ago, when I was halfway through watching the 2 hour introduction (now replaced with this):

- name: automatic deploy new version
  hosts: ruby-production
  user: root
  sudo: yes

  - name: stop monit
    command: service monit stop

  - name: checkout correct version
    sudo_user: projectuser
    command: chdir=/workspace/project git checkout release-1.0

  - name: grab latest sources for that release
    sudo_user: projectuser
    command: chdir=/workspace/project git pull origin release-1.0

  - name: run db:migrate
    sudo_user: projectuser
    sudo: no
    script: migrate.sh

  - name: restart apache
    command: service httpd restart

  - name: start monit
    command: service monit start

What the above playbook does is simple:

– Stop monit to avoid any automatic restarts when you’re half way through updating stuff that it monitors.

– It makes sure that you grab the latest updates from the correct branch

– It runs a script on all servers that takes care of any bundle install and db:migrate stuff.

– It restarts apache. Yes, a touch tmp/restart.txt should do the trick but sometimes it does not.

– It restarts monit.

There is plenty of room for improvement here, for example using the git module instead of running an explicit command and even making use of roles as the project expands and becomes more demanding and of course get rid of the script in favor of a more ansible specific play.

So why post it now? Basically at the request of @nanobeep and as a means of self-pressure to improve the playbook. Maybe I should promise this to someone?

So there, nothing complex or elaborate. BTW, here is a similar way to do this with git and Capistrano that I bumped into.

Here is a weird thing:

When running /etc/init.d/milter-greylist restart via ansible (either direct or via a playbook) it hangs. I had no time to debug this, so I reverted to the next best workaround since the machine was already running monit:

Have ansible distribute greylist.conf and then have monit restart the process. So here is a simple playbook:

- hosts: greylist
  user: root
  - name: copy local milter-greylist configuration to hosts
    action: copy src=/usr/local/etc/greylist.conf dest=/etc/milter-greylist/greylist.conf

and here is how monit finishes the task:

check file greylist.conf with path /etc/milter-greylist/greylist.conf
 if changed checksum then exec "/etc/init.d/milter-greylist restart"

Of course this is just a simple case of having the two cooperate. But once you get the hang of it, more elaborate schemes where ansible and monit can cooperate will pop out.

When calling yum check-update from an ansible playbook, your play may fail because check-update returns non-zero exit status for two reasons:

check-update […] returns exit value of 100 if there are packages available for an update. Also returns a list of the packages to be updated in list format. Returns 0 if no packages are available for update. Returns 1 if an error occurred.

One quick and dirty way to bypass this is to use ignore_errors: yes in your task, but this will ignore both the case of pending updates and any other kind of error and your play will continue regardless. To avoid this one can modify the play sightly to check for the exit status:

  - name: yum check-update
    shell: 'yum check-update || test $? -eq 100'

The single quotes in the shell command above do matter.