How do I set register a variable to persist between plays in ansible?

Chris Adams picture Chris Adams · Nov 24, 2015 · Viewed 73.5k times · Source

I have an ansible playbook, where I'd like a variable I register on one machine to be available on another.

In my case, I'd like to run a command on localhost, in this case git rev-parse --abbrev-ref HEAD, so I can make a note of the current git branch, and sha1, and register this output, so I can refer to it later when working any machine in the main group, in the second play.

However, it's not clear to me how I register a variable on localhost, so I can access it from main. When I try to access the variable in the second play I get this message:

    TASK: [debug msg={{ app_git_sha1.stdout }}] ***********************************
    fatal: [main] => One or more undefined variables: 'app_git_sha1' is undefined


Here's the play I'm using. Is there anything obvious I should be doing?

    ---
    - hosts: localhost
      connection: local
      gather_facts: no
      tasks:
        - name: register current branch
          command: git rev-parse --abbrev-ref HEAD
          register: git_branch
          sudo: no
          when: vagrant
          tags:
            - debugsha

        - debug: msg={{ git_branch.stdout }}
          tags:
            - debugsha

        - name: register the SHA1 of the branch being deployed
          command: git rev-parse origin/{{ git_branch.stdout }}
          register: app_git_sha1
          sudo: no
          tags:
            - slack
            - debugsha

        - debug: msg={{ app_git_sha1.stdout }}
          tags:
            - debugsha



    - hosts: main
      sudo: yes
      roles:
        - role: productscience.deploy_user
        # TODO reprovision using these roles, for consistency
        # - role: app.essentials
        # - role: zenoamaro.postgresql
        - role: productscience.papertrailapp
        - role: jdauphant.nginx
      tasks:
        - include: setup.yml
        # - include: db.yml

        - name: checkout source control when deploying to remote servers
          include: source.yml
          when: not vagrant
          tags:
              - deploy

        - include: django.yml
          tags:
              - deploy


        - name: include vagrant specific dependencies for local development
          include: vagrant.yml
          when: vagrant

      handlers:
        - name: restart postgres
          sudo: yes
          service: name=postgresql state=restarted
        - name: start restart uwsgi
          sudo: yes
          service: name={{ app }} state=restarted

    - hosts: localhost
      connection: local
      gather_facts: no
      tasks:
        - name: register the SHA1 of the branch being deployed
          when: not vagrant
          command: git rev-parse origin/{{ git_branch }}
          register: git_sha
          tags:
            - slack

        - name: Send notification message via Slack all options
          when: not vagrant
          tags:
            - slack
          local_action:
            module: slack
            token: "{{ wof_slack_token }}"
            msg: "Deployment of `{{ git_branch }}` to {{ app_url }} completed with sha `{{ git_sha.stdout }}`"
            channel: "#wof"
            username: "Ansible deploy-o-tron"

Answer

Bruce P picture Bruce P · Nov 24, 2015

The problem you're running into is that you're trying to reference facts/variables of one host from those of another host. You need to keep in mind that in Ansible, the variable app_git_sha1 assigned to the host localhost is distinct from the variable app_git_sha1 assigned to the host main or any other host. If you want to access one hosts facts/variables from another host then you need to explicitly reference it via the hostvars variable. There's a bit more of a discussion on this in this question.

Suppose you have a playbook like this:

- hosts: localhost
  tasks:   
    - command: /bin/echo "this is a test"
      register: foo


- hosts: localhost
  tasks:
    - debug: var=foo

This will work because you're referencing the host localhost and localhosts's instance of the variable foo in both plays. The output of this playbook is something like this:

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [command /bin/echo "this is a test"] ************************************
changed: [localhost]

PLAY [localhost] **************************************************************

GATHERING FACTS ***************************************************************
ok: [localhost]

TASK: [debug var=foo] *********************************************************
ok: [localhost] => {
    "var": {
        "foo": {
            "changed": true,
            "cmd": [
                "/bin/echo",
                "this is a test"
            ],
            "delta": "0:00:00.004585",
            "end": "2015-11-24 20:49:27.462609",
            "invocation": {
                "module_args": "/bin/echo \"this is a test\"",
                "module_complex_args": {},
                "module_name": "command"
            },
            "rc": 0,
            "start": "2015-11-24 20:49:27.458024",
            "stderr": "",
            "stdout": "this is a test",
            "stdout_lines": [
                "this is a test"
            ],
            "warnings": []
        }
    }
}

If you modify this playbook slightly to run the first play on one host and the second play on a different host, you'll get the error that you encountered. The solution is to use Ansible's built-in hostvars variable to have the second host explicitly reference the first hosts variable. So modify the first example like this:

- hosts: localhost
  tasks:

    - command: /bin/echo "this is a test"
      register: foo


- hosts: anotherhost
  tasks:
    - debug: var=foo
      when: foo is defined

    - debug: var=hostvars['localhost']['foo']
      when: hostvars['localhost']['foo'] is defined

The output of this playbook shows that the first task is skipped because foo is not defined by the host anotherhost. But the second task succeeds because it's explicitly referencing localhosts's instance of the variable foo:

TASK: [debug var=foo] *********************************************************
skipping: [anotherhost]

TASK: [debug var=hostvars['localhost']['foo']] **************************
ok: ['anotherhost'] => {
    "var": {
        "hostvars['localhost']['foo']": {
            "changed": true,
            "cmd": [
                "/bin/echo",
                "this is a test"
            ],
            "delta": "0:00:00.005950",
            "end": "2015-11-24 20:54:04.319147",
            "invocation": {
                "module_args": "/bin/echo \"this is a test\"",
                "module_complex_args": {},
                "module_name": "command"
            },
            "rc": 0,
            "start": "2015-11-24 20:54:04.313197",
            "stderr": "",
            "stdout": "this is a test",
            "stdout_lines": [
                "this is a test"
            ],
            "warnings": []
        }
    }
}

So, in a nutshell, you want to modify the variable references in your main playbook to reference the localhost variables in this manner:

{{ hostvars['localhost']['app_git_sha1'] }}