Question:
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:
1 2 3 |
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?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
--- - 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:
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.
Suppose you have a playbook like this:
1 2 3 4 5 6 7 8 9 10 |
- 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
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:
1 2 |
{{ hostvars['localhost']['app_git_sha1'] }} |