Ansible hostvars not evaluated in group_vars - ansible

I have the following playbook :
- name: Do something
hosts: myHost
tasks:
- debug:
msg: "{{hostvars[groups['gatewayHost'][0]].ansible_host}}"
- debug:
msg: "{{hostvars[item].foo}}"
with_items: "{{ groups['webapps'] }}"
For group webapps I have a groups_vars/webapps.yml :
foo: "{{ hostvars[groups['gatewayHost'][0]].ansible_host }}"
My issue is that when running the playbook the first debug message is correctly evaluated, but the second debug message just display hostvars[groups['gatewayHost'][0]].ansible_host and doesn't evaluate the variable. I guess I'm missing something but I can't see what.
(Note, If I try to evaluate groups['gatewayHost'], it's correctly evaluated in both cases, playbook and group_vars)

After digging into Ansible issues, I found this one https://github.com/ansible/ansible/issues/20545 which is pretty close to my issue.

Related

Ansible: variable is defined but error shows not defined

I have an ansible playbook as follows:
---
- name: test variables
hosts: all
tasks:
- name: test
command: echo {{ ansible_role_dir }}
register: result
- name: show result
debug:
msg="{{ result.stdout }}"
#roles: #line12
# - "{{ ansible_role_dir }}/testrole" #line13
the variable ansible_role_dir is defined under group_vars/all.yaml.
if I run the playbook which comments out the line12 and line13, it shows the result of variable correctly. Obviously it knows where the variable ansible_role_dir is defined. But if I uncomment line12 and line13, it shows error ERROR! 'ansible_role_dir' is undefined. Why it does not know where the ansible_role_dir is defined this time?
Ansible does not like jinja tags without quotes. try this:
---
- name: test variables
hosts: all
tasks:
- name: test
command: "echo {{ ansible_role_dir }}"
register: result
- name: show result
debug:
msg="{{ result.stdout }}"
#roles: #line12
# - "{{ ansible_role_dir }}/testrole" #line13
EDIT:
sorry, i think i misread the question.
have you tried putting in the real path instead of the var, just to see if it works? usually role paths are given in ansible's config, i never saw them run with a direct path

How to get value of --limit argument inside an Ansible playbook?

In ansible, is it possible to get the value of the argument to the "--limit" option within a playbook? I want to do is something like this:
---
- hosts: all
remote user: root
tasks:
- name: The value of the --limit argument
debug:
msg: "argument of --limit is {{ ansible-limit-arg }}"
Then when I run he command:
$ ansible-playbook getLimitArg.yaml --limit webhosts
I'll get this output:
argument of --limit is webhost
Of course, I made up the name of the variable "ansible-limit-arg", but is there a valid way of doing this? I could specify "webhosts" twice, the second time with --extra-args, but that seems a roundabout way of having to do this.
Since Ansible 2.5 you can access the value using a new magic variable ansible_limit, so:
- debug:
var: ansible_limit
Have you considered using the {{ ansible_play_batch }} built-in variable?
- hosts: all
become: "False"
gather_facts: "False"
tasks:
- name: The value of the --limit argument
debug:
msg: "argument of --limit is {{ ansible_play_batch }}"
delegate_to: localhost
It won't tell you exactly what was entered as the argument but it will tell you how Ansible interpreted the --limit arg.
You can't do this without additional plugin/module. If you utterly need this, write action plugin and access options via cli.options (see example).
P.S. If you try to use --limit to run playbooks agains different environments, don't do it, you can accidentally blow your whole infrastructure – use different inventories instead.
Here is the small code block to achieve the same
- block:
- shell: 'echo {{inventory_hostname}} >> /tmp/hosts'
- shell: cat /tmp/hosts
register: servers
- file:
path: /tmp/hosts
state: absent
delegate_to: 127.0.0.1
- debug: var=servers.stdout_lines
Then use stdout_lines output as u wish like mine
- add_host:
name: "{{ item }}"
groups: cluster
ansible_user: "ubuntu"
with_items:
- "{{ servers.stdout_lines }}"

Passing a variable to an included Ansible playbook

I'd like to pass a variable to an included Ansible playbook as follows:
---
- hosts: localhost
connection: local
vars:
my_group: foo
- include: site.yml hosts={{ my_group }}
Then, in site.yml...
---
- hosts: "{{ hosts }}"
...
Unfortunately, I get an error saying that my_group is undefined in site.yml. Ansible docs do say that:
Note that you cannot do variable substitution when including one playbook inside another.
Is this my case? Is there a way around it?
You can use this syntax, but my_group has to be defined at the global level. Now it's local to the first play - it's even clear from the indentation.
You can confirm this by running your playbook with --extra-vars my_group=foo.
But generally what you seem to want to achieve is done using in-memory inventory files and add_host module. Take this as an example:
- hosts: localhost
gather_facts: no
vars:
target_host: foo
some_other_variable: bar
tasks:
- add_host:
name: "{{ target_host }}"
groups: dynamically_created_hosts
some_other_variable: "{{ some_other_variable }}"
- include: site.yml
with site.yml:
---
- hosts: dynamically_created_hosts
tasks:
- debug:
var: some_other_variable
I added some_other_variable to answer the question from your comment "how do I make a variable globally available from inside a play". It's not global, but it's passed to another play as a "hostvar".
From what I see (and I can't explain why) in Ansible >=2.1.1.0, there must be an inventory file specified for the dynamic in-memory inventory to work. In older versions it worked with Ansible executed ad hoc, without an inventory file, but now you must run ansible-playbook with -i inventory_file or have an inventory file defined through ansible.cfg.

Ansible: variable interpolation in task name

I cannot get this seemingly simple example to work in Ansible 1.8.3. The variable interpolation does not kick in the task name. All examples I have seen seem to suggest this should work. Given that the variable is defined in the vars section I expected the task name to print the value of the variable. Why doesn't this work?
Even the example from the Ansible documentation seems to not print the variable value.
---
- hosts: 127.0.0.1
gather_facts: no
vars:
vhost: "foo"
tasks:
- name: create a virtual host file for {{ vhost }}
debug: msg="{{ vhost }}"
This results in the following output:
PLAY [127.0.0.1]
**************************************************************
TASK: [create a virtual host file for {{ vhost }}]
****************************
ok: [127.0.0.1] => {
"msg": "foo"
}
PLAY RECAP
********************************************************************
127.0.0.1 : ok=1 changed=0 unreachable=0 failed=0
Update
This works with 1.7.2 but does not work with 1.8.3. So either this is a bug or a feature.
Variables are not resolved inside the name. Only inside the actual tasks/conditions etc. the placeholders will be resolved. I guess this is by design. Imagine you have a with_items loop and use the {{ item }}in the name. The tasks name will only be printed once, but the {{ item }} would change in every iteration.
I see the examples, even the one in the doc you linked to, use variables in the name. But that doesn't mean the result would be like you expected it. The docs are community managed. It might be someone just put that line there w/o testing it - or maybe it used to work like that in a previous version of Ansible and the docs have not been updated then. (I'm only using Ansible since about one year). But even though it doesn't work like we wish it would, I'm still using variables in my name's, just to indicate that the task is based on dynamic parameters. Might be the examples have been written with the same intention.
An interesting observation I recently made (Ansible 1.9.4) is, default values are written out in the task name.
- name: create a virtual host file for {{ vhost | default("foo") }}
When executed, Ansible would show the task title as:
TASK: [create a virtual host file for foo]
This way you can avoid ugly task names in the output.
Explanation
Whether the variable gets interpolated depends on where it has been declared.
Imagine You have two hosts: A and B.
If variable foo has only per-host values, when Ansible runs the play, it cannot decide which value to use.
On the other hand, if it has a global value (global in a sense of host invariance), there is no confusion which value to use.
Source: https://github.com/ansible/ansible/issues/3103#issuecomment-18835432
Hands on playbook
ansible_user is an inventory variable
greeting is an invariant variable
- name: Test variable substitution in names
hosts: localhost
connection: local
vars:
greeting: Hello
tasks:
- name: Sorry {{ ansible_user }}
debug:
msg: this won't work
- name: You say '{{ greeting }}'
debug:
var: ansible_user
I experienced the same problem today in one of my Ansible roles and I noticed something interesting.
When I use the set_fact module before I use the vars in the task name, they actually get translated to their correct values.
In this example I wanted to set the password for a remote user:
Notice that I use the vars test_user and user_password that I set as facts before.
- name: Prepare to set user password
set_fact:
user_password: "{{ linux_pass }}"
user_salt: "s0m3s4lt"
test_user: "{{ ansible_user }}"
- name: "Changing password for user {{ test_user }} to {{ user_password }}"
user:
name: "{{ ansible_user }}"
password: "{{ user_password | password_hash('sha512', user_salt) }}"
state: present
shell: /bin/bash
update_password: always
This gives me the following output:
TASK [install : Changing password for user linux to LiNuXuSeRPaSs#]
So this solved my problem.
It might be ugly, but you can somewhat workaround with something like this:
- name: create a virtual host file
debug:
msg: "Some command result"
loop: "{{ [ vhost ] }}"
or
- name: create a virtual host file
debug:
msg: "Some command result"
loop_control:
label: "{{ vhost }}"
loop: [1]
I wouldn't do this in general, but it shows how you can use items or label to give information outside of the command result. While it might not
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html

ansible: using with_items with notify handler

I want to pass a variable to a notification handler, but can't find anywhere be it here on SO, the docs or the issues in the github repo, how to do it. What I'm doing is deploying multiple webapps, and when the code for one of those webapps is changed, it should restart the service for that webapp.
From this SO question, I got this to work, somewhat:
- hosts: localhost
tasks:
- name: "task 1"
shell: "echo {{ item }}"
register: "task_1_output"
with_items: [a,b]
- name: "task 2"
debug:
msg: "{{ item.item }}"
when: item.changed
with_items: task_1_output.results
(Put it in test.yml and run it with ansible-playbook test.yml -c local.)
But this registers the result of the first task and conditionally loops over that in the second task. My problem is that it gets messy when you have two or more tasks that need to notify the second task! For example, restart the web service if either the code was updated or the configuration was changed.
AFAICT, there's no way to pass a variable to a handler. That would cleanly fix it for me. I found some issues on github where other people run into the same problem, and some syntaxes are proposed, but none of them actually work.
Including a sub-playbook won't work either, because using with_items together with include was deprecated.
In my playbooks, I have a site.yml that lists the roles of a group, then in the group_vars for that group I define the list of webapps (including the versions) that should be installed. This seems correct to me, because this way I can use the same playbook for staging and production. But maybe the only solution is to define the role multiple times, and duplicate the list of roles for staging and production.
So what is the wisdom here?
Variables in Ansible are global so there is no reason to pass a variable to handler. If you are trying to make a handler parameterized in a way that you are trying to use a variable in the name of a handler you won't be able to do that in Ansible.
What you can do is create a handler that loops over a list of services easily enough, here is a working example that can be tested locally:
- hosts: localhost
tasks:
- file: >
path=/tmp/{{ item }}
state=directory
register: files_created
with_items:
- one
- two
notify: some_handler
handlers:
- name: "some_handler"
shell: "echo {{ item }} has changed!"
when: item.changed
with_items: files_created.results
I finally solved it by splitting the apps out over multiple instances of the same role. This way, the handler in the role can refer to variables that are defined as role variable.
In site.yml:
- hosts: localhost
roles:
- role: something
name: a
- role: something
name: b
In roles/something/tasks/main.yml:
- name: do something
shell: "echo {{ name }}"
notify: something happened
- name: do something else
shell: "echo {{ name }}"
notify: something happened
In roles/something/handlers/main.yml:
- name: something happened
debug:
msg: "{{ name }}"
Seems a lot less hackish than the first solution!
To update jarv's answer above, Ansible 2.5 replaces with_items with loop. When getting results, item by itself will not work. You will need to explicitly get the name, e.g., item.name.
- hosts: localhost
tasks:
- file: >
path=/tmp/{{ item }}
state=directory
register: files_created
loop:
- one
- two
notify: some_handler
handlers:
- name: "some_handler"
shell: "echo {{ item.name }} has changed!"
when: item.changed
loop: files_created.results
I got mine to work like this - I had to add some curly brackets
tasks:
- name: Aktivieren von Security-, Backport- und Non-Security-Upgrades
lineinfile:
path: /etc/apt/apt.conf.d/50unattended-upgrades
regexp: '^[^"//"]*"\${distro_id}:\${distro_codename}-{{ item }}";'
line: ' "${distro_id}:${distro_codename}-{{ item }}";'
insertafter: "Unattended-Upgrade::Allowed-Origins {"
state: present
register: aenderung
loop:
- updates
- security
- backports
notify: Auskommentierte Zeilen entfernen
handlers:
- name: Auskommentierte Zeilen entfernen
lineinfile:
path: /etc/apt/apt.conf.d/50unattended-upgrades
regexp: '^\/\/.*{{ item.item }}";.*'
state: absent
when: item.changed
loop: "{{ aenderung.results }}"

Resources