Ansible: variable interpolation in task name - ansible

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

Related

Ansible hostvars not evaluated in group_vars

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.

Ansible 2.6: Is there a way to reference the playbook's name in a role task?

Given a playbook like this:
- name: "Tasks for service XYZ"
hosts: apiservers
roles:
- { role: common }
Is there a way to reference the playbook's name ("Tasks for service XYZ")? (i.e. a variable)
EDIT:
My intention is to be able to reference the playbook's name in a role task, i.e. sending a msg via slack like
- name: "Send Slack notification indicating deploy has started"
slack:
channel: '#project-deploy'
token: '{{ slack_token }}'
msg: '*Deploy started* to _{{ inventory_hostname }}_ of `{{ PLAYBOOK_NAME }}` version *{{ service_version }}*'
delegate_to: localhost
tags: deploy
It was added in 2.8:
ansible_play_name
The name of the currently executed play. Added in 2.8.
No, the special variables for Ansible are documented here, and you can see that there is no variable to return the playbook name.
As mentioned in the comments, however, you can always do this:
---
- name: "{{ task_name }}"
hosts: localhost
vars:
task_name: "Tasks for service XYZ"
tasks:
- debug:
msg: "{{ task_name }}"
From your circumstances, it looks like you only want this for audit/notification purposes? In that case (and assuming unixy clients), using
lookup('file', '/proc/self/cmdline') | regex_replace('\u0000',' ')
will give you the entire command line that ansible-playbook was called with, parameters and all, which would include the playbook name. Depending on your circumstances, that might be a useful enough tradeoff.

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 }}"

How to make Ansible playbook run on first host in the group?

How can I run a playbook only on first host in the group?
I am expecting something like this:
---
- name: playbook that only run on first host in the group
hosts: "{{ groups[group_name] | first }}"
tasks:
- debug:
msg: "on {{ inventory_hostname }}"
But this doesn't work, gives error:
'groups' is undefined
How can I make it work?
You can use:
hosts: group_name[0]
Inventory hosts values (specified in the hosts directive) are processed with a custom parser, which does not allow Jinja2 expressions like the regular template engine does.
Read about Patterns.

How can I persist an ansible variable across ansible roles?

I've registered a variable in a play.
---
- hosts: 127.0.0.1
gather_facts: no
connection: local
sudo: no
vars_files:
- vars.yml
tasks:
- name: build load balancer
os_load_balancer: net=mc_net ext_net=vlan3320 name=load_balancer protocol=HTTPS port=80
register: my_lb
I can access that variable fine, until I make the request inside a role.
For example, in a separate role in the same run, I want to access that registered variable:
- debug: var=my_lb
I get the following output:
{'msg': "AnsibleUndefinedVariable: One or more undefined variables: 'my_lb' is undefined", 'failed': True}
How can I access variables registered in a separate role, within the same play?
Edit for clarity of how things piece together:
Top Play
-includes:
- Sub play 1
- registers variable foo
- Sub play 2
-includes:
- sub play A
- role 1
- role 2
- role 3
- references variable foo in template
- Sub play B
- Sub play 3
NB: This was written referring to Ansible 1.X. I suspect variable parsing and scoping has changed considerably with Ansible 2.0, so bear that in mind. I'll try to update the answer if I get time (or maybe someone else will answer for v2.0!)
There are two options here. The simplest is to define the variables in your top-level playbook, then propagate them down into your various roles. You can either set these as simple vars, or use pre_tasks to lookup / calculate values dynamically, e.g.
vars:
foo_static: "value_bar"
pre_tasks:
- set_fact:
foo_ncpu: "{{ ansible_processor_vcpus }}"
roles:
- {role: role1, role1_var1: "{{foo_static}}", role1_var2: "{{foo_ncpu}}" }
- {role: role2, role2_var1: "{{foo_static}}", role2_var2: "{{foo_ncpu}}" }
The second option requires that you add a task to whichever role you need to extract a variable from (though since all ansible roles are open-source, that should be easy enough). The trick is to use set_fact to export a variable as a 'host_fact', e.g.
- name: Export role1_varfoo to a 'host-fact' type variable
set_fact:
role1_varfoo: "{{ role1_varfoo }}"
Which can then be accessed later like so:
vars:
role2_varfoo: "{{ hostvars['myhost']['role1_varfoo']}}"
As described in this bit of the ansible docs.
Note that if you always want to lookup the hostvars of the current machine you're running commands on (without knowing what it's actually called in the ansible hosts file), you can use the variable inventory_hostname like so:
vars:
role2_varfoo: "{{ hostvars[inventory_hostname]['role1_varfoo']}}"
(Note lack of quote-marks, since it's a variable, not a string-literal.)
Slightly awkward, but those combinations have met all my needs so far.
Try moving your variable declarations into a pre_task block. Variables set here should be available within and following roles.
https://docs.ansible.com/playbooks_roles.html#roles
e.g.
pre_tasks:
- name: build load balancer
os_load_balancer: net=mc_net ext_net=vlan3320 name=load_balancer protocol=HTTPS port=80
register: my_lb
roles:
- { role: some_role }
Update: To access the variable using the hostvars syntax use the appropriate host GROUP variable rather than the host that executed the set_fact:
hostvars[inventory_hostname]['variable']
To broaden the accepted question.
1.-You can define ANOTHER role where you register the variable and then set it there then refer that variable from multiple roles. AS LONG as the roles are in the same play.
Docu here:
http://docs.ansible.com/ansible/playbooks_variables.html#variable-examples
Generally speaking, variables set in one role are available to others. This means if you have a roles/common/vars/main.yml you can set variables in there and make use of them in other roles and elsewhere in your playbook
Edit: Clarification, this applies for REGISTERED and set variables in Ansible 2.x in my experience.
2.-As far as using hostvars goes, I tried it myself and failed with this error:
fatal: [localhost]: FAILED! => {"failed": true, "msg": "'dict object' has no attribute 'ec2_instance_id'"}
I my case I was doing the following.
- hosts: localhost
gather_facts: yes
roles:
- { role: role_1 }
post_tasks:
- name: Check instance variables within localhost
debug: var={{ hostvars['localhost']['ec2_instance_id'] }}
On role 1 I had:
- name: register instance_id
set_fact: ec2_instance_id="{{ item.id }}"
with_items: "{{ ec2_instance.instances }}"
Although according to this old thread, the hostvar approach should work:
https://github.com/ansible/ansible/issues/1934
How abt passing it as parameter to role. I'm assuming such (register) variables aren't passed implicitly (like hotvars are).
- { role: my_role, my_lb: "{{my_lb}}" }

Resources