How to get an Ansible check to run only once in a playbook?

Since version 1.7 of Ansible you can use run_once: true to only run a task one time and only on one host.


Updated

When I fist wrote my answer (2014-02-27), Ansible had no built-in support for running a task only once per playbook, not once per affected host that the playbook was run on. However, as tlo writes, support for this was introduced with run_once: true in Ansible version 1.7.0 (released on 2014-08-06). With this feature, the example task definition from the question should be changed to

- name: Ensure local git repository is up-to-date
  local_action: git pull
  run_once: true
  register: command_result
  failed_when: "'Updating' in command_result.stdout"

to accomplish what is asked for.

Original Answer

[The following answer was my suggested solution for the particular problem of making sure that the local git branch is updated before Ansible runs the tasks of a playbook.]

I wrote the following Ansible callback plugin that will avoid playbook execution if the current git branch is out of sync (is either behind or has diverged) with the remote branch. To use it, place the following code in a file like callback_plugins/require_updated_git_branch.py in your top-level Ansible playbook directory:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import os
import re
import subprocess
import sys

from ansible.callbacks import display, banner


class CallbackModule(object):
    """Makes Ansible require that the current git branch is up to date.
    """
    env_var_name = 'IGNORE_OUTDATED_GIT_BRANCH'

    msg = 'OUTDATED GIT BRANCH: Your git branch is out of sync with the ' \
          'remote branch.  Please update your branch (git pull) before ' \
          'continuing, or skip this test by setting the environment ' \
          'variable {0}=yes.'.format(env_var_name)

    out_of_sync_re = re.compile(r'Your branch (is behind|and .* have diverged)',
                                re.MULTILINE)

    def __init__(self, *args, **kwargs):
        if os.getenv(self.env_var_name, 'no') == 'yes':
            self.disabled = True

    def playbook_on_start(self):
        subprocess.call(['git', 'fetch'])

        if self.out_of_sync_re.search(subprocess.check_output([
            'git', 'status', '--untracked-files=no'])):
            display(banner(self.msg), color='bright purple')
            sys.exit(1)

For example, when the local branch is behind the remote branch, the command ansible-playbook site.yml halts early with the following output:

 __________________________________________________________
/ OUTDATED GIT BRANCH: Your git branch is out of sync with \
| the remote branch. Please update your branch (git pull)  |
| before continuing, or skip this test by setting the      |
\ environment variable IGNORE_OUTDATED_GIT_BRANCH=yes.     /
 ----------------------------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

And, as the cow suggests, to turn off this check you can run the command like:

$ IGNORE_OUTDATED_GIT_BRANCH=yes ansible-playbook site.yml

This solution does not solve the general problem of avoiding to run any Ansible task more than once regardless of the number of hosts involved, but it ensures that outdated playbooks are not executed, and it handles the concern that you mentioned regarding my alias-based suggestion.

Tags:

Git

Ansible