Syntax for retrieving variable using another variable - ansible

I'm trying to find the UUID of a VM based on it's hostname. I'm not sure what is missing in my syntax here, but I've tried several different methods. Here is the sample playbook I'm working with currently:
---
- name: Test taking snapshot by UUID
hosts: tst000.company.com
vars_files:
- vars/credentials.yml
tasks:
- name: Gather all registered virtual machines
vmware_vm_facts:
hostname: 'vcenter.company.com'
username: '{{ vcenter.username }}'
password: '{{ vcenter.password }}'
validate_certs: False
delegate_to: localhost
register: vmfacts
- debug:
var: vmfacts.virtual_machines.{{ ansible_facts['hostname'] }}.uuid
- set_fact:
vm_uuid: "{{ lookup('vars', 'vmfacts.virtual_machines.' + ansible_facts['hostname'] + '.uuid') }}"
Results are as follows:
Identity added: /opt/tmp/awx_2507_ithHYD/credential_3
(/opt/tmp/awx_2507_ithHYD/credential_3) Vault password:
PLAY [Test taking snapshot by UUID]
********************************************
TASK [Gathering Facts]
********************************************************* ok: [tst000.company.com]
TASK [Gather all registered virtual machines]
********************************** ok: [tst000.company.com -> localhost]
TASK [debug]
******************************************************************* ok: [tst000.company.com] => {
"vmfacts.virtual_machines.tst000.uuid": "421d2491-8896-e52f-e4f5-5118687ce0e9" }
TASK [set_fact]
**************************************************************** fatal: [tst000.company.com]: FAILED! => {"msg": "The task
includes an option with an undefined variable. The error was: No
variable found with this name:
vmfacts.virtual_machines.tst000.uuid\\n\\nThe error appears to
have been in '/var/lib/awx/projects/quick-stuff/test_snapshot.yml':
line 21, column 7, but may\\nbe elsewhere in the file depending on the
exact syntax problem.\\n\\nThe offending line appears to be:\\n\\n\\n
- set_fact:\\n ^ here\\n"}
PLAY RECAP
********************************************************************* tst000.company.com : ok=3 changed=0 unreachable=0
failed=1
In the debug and set_fact modules, you can see that the ansible_facts['hostname]' properly places the hostname as needed, however, it returns the proper value in the debug module, but is claiming there is no variable found by that name in the set_fact module. I'm not sure what is wrong with my syntax here.

There's no need to use the lookup in that way, since vars and its hostvars friend are effectively dicts:
- set_fact:
vm_uuid: "{{ vmfacts.virtual_machines[ansible_facts['hostname']].uuid }}"

Related

Ansible default variable naming seems to be inconsistent

I am learning Ansible and I am observe that the variable (fact) naming seems to be inconsistent.
For example, my Jinja2 template can run successfully.
{% for host in groups['node'] %}
{{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }}
{% endfor %}
But if I run ad-hoc command setup to show all variables, the variable name will be ansible_default_ipv4. If I input the prefix ansible_ in playbooks, it then not run.
I ran then setup module to look for the variable name I need and I am so confuse about it now.
Am I doing it wrong, or have another proper way to look for variable name?
I tried looking for answer anywhere.
Thank you.
Q: "I run ad-hoc command setup to show all variables."
A: Use lookup plugins vars and varnames instead. See
shell> ansible-doc -t lookup varnames
shell> ansible-doc -t lookup vars
For example,
- hosts: localhost
gather_facts: false
vars:
my_vars: "{{ query('varnames', 'ipv4') }}"
tasks:
- setup:
gather_subset: default_ipv4
- debug:
var: my_vars
- debug:
msg: "{{ lookup('vars', item) }}"
loop: "{{ my_vars }}"
gives
PLAY [localhost] *****************************************************************************
TASK [setup] *********************************************************************************
ok: [localhost]
TASK [debug] *********************************************************************************
ok: [localhost] =>
my_vars:
- ansible_default_ipv4
- ansible_all_ipv4_addresses
TASK [debug] *********************************************************************************
ok: [localhost] => (item=ansible_default_ipv4) =>
msg:
address: 10.1.0.184
alias: eth1
broadcast: 10.1.0.255
gateway: 10.1.0.10
interface: eth1
macaddress: <sanitized>
mtu: 1500
netmask: 255.255.255.0
network: 10.1.0.0
type: ether
ok: [localhost] => (item=ansible_all_ipv4_addresses) =>
msg:
- 172.17.0.1
- 10.1.0.184
PLAY RECAP ***********************************************************************************
localhost: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Use dynamic variable name

I'm trying to get the value of ip_address from the following yaml that I'm including as variables on ansible:
common:
ntp:
- time.google.com
node1:
default_route: 10.128.0.1
dns:
- 10.128.0.2
hostname: ip-10-128-5-17
device_interface: ens5
cluster_interface: ens5
interfaces:
ens5:
ip_address: 10.128.5.17
nat_ip_address: 18.221.63.178
netmask: 255.255.240.0
version: 2
However the network interface (ens5 here) may be named something else, such as eth0. My ansible code is this:
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
vars:
msg: node1.interfaces.{{ cvp_device_interface }}.ip_address
debug:
msg: "{{ msg }}"
tags: debug_info
Running the code like this I can get the key's name:
TASK [Dump Interface Settings] *************************************************
│ ok: [18.221.63.178] => {
│ "msg": "node1.interfaces.ens5.ip_address"
│ }
But what I actually need is the value (i.e: something like {{ vars[msg] }}, which should expand into {{ node1.interfaces.ens5.ip_address }}). How can I accomplish this?
Use sqare brackets.
Example: a minimal playbook, which defines a variable called "device". This variable is used to return the active status of the device.
- hosts: localhost
connection: local
vars:
device: enx0050b60c19af
tasks:
- debug: var=device
- debug: var=hostvars.localhost.ansible_facts[device].active
Output:
$ ansible-playbook example.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************
TASK [Gathering Facts] *************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************
ok: [localhost] => {
"device": "enx0050b60c19af"
}
TASK [debug] ***********************************************************************
ok: [localhost] => {
"hostvars.localhost.ansible_facts[device].active": true
}
PLAY RECAP *************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
see comment
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
debug:
msg: "{{ node1['interfaces'][cvp_device_interface]['ip_address'] }}"
debug:
msg: "{{ msg }}"
tags: debug_info

Access variable from one role in another role in an Ansible playbook with multiple hosts

I'm using the latest version of Ansible, and I am trying to use a default variable in role-one used on host one, in role-two, used on host two, but I can't get it to work.
Nothing I have found in the documentation or on StackOverflow has really helped. I'm not sure what I am doing wrong. Ideally I want to set the value of the variable once, and be able to use it in another role for any host in my playbook.
I've broken it down below.
In my inventory I have a hosts group called [test] which has two hosts aliased as one and two.
[test]
one ansible_host=10.0.1.10 ansible_connection=ssh ansible_user=centos ansible_ssh_private_key_file=<path_to_key>
two ansible_host=10.0.1.20 ansible_connection=ssh ansible_user=centos ansible_ssh_private_key_file=<path_to_key>
I have a single playbook with a play for each of these hosts and I supply the hosts: value as "{{ host_group }}[0]" for host one and "{{ host_group }}[1]" for host two.
The play for host one uses a role called role-one and the play for host two uses a role called role-two.
- name: Test Sharing Role Variables
hosts: "{{ host_group }}[0]"
roles:
- ../../ansible-roles/role-one
- name: Test Sharing Role Variables
hosts: "{{ host_group }}[1]"
roles:
- ../../ansible-roles/role-two
In role-one I have set a variable variable-one.
---
# defaults file for role-one
variable_one: Role One Variable
I want to use the value of variable_one in a template in role-two but I haven't had any luck. I'm using the below as a task in role-two to test and see if the variable is getting "picked-up".
---
# tasks file for role-two
- debug:
msg: "{{ variable_one }}"
When I run the playbook with ansible-playbook test.yml --extra-vars "host_group=test" I get the below failure.
TASK [../../ansible-roles/role-two : debug] ***********************************************************************************************************************************************************************************************
fatal: [two]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: \"hostvars['test']\" is undefined\n\nThe error appears to be in 'ansible-roles/role-two/tasks/main.yml': line 3, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n# tasks file for role-two\n- debug:\n ^ here\n"}
Variables declared in roles are scoped to the play. If you want to access a variable from role-one in role-two, they would both need to be in the same play. For example, you could write:
- name: Test Sharing Role Variables
hosts: "{{ host_group }}"
tasks:
- import_role:
name: role-one
when: inventory_hostname == "one"
- import_role:
name: role-two
when: inventory_hostname == "two"
Alternatively, you could restructure your roles so that the variables can be imported separately from your actions. That is, have a role_one_vars role that does nothing but define variables, and then you can import that in both role-one and role-two. That is, you would have a structure something like:
playbook.yml
hosts
roles/
role-one/
tasks/
main.yml
role-one-vars/
variables/
main.yml
role-two/
tasks/
main.yml
And role-one/tasks/main.yml would look like:
- import_role:
name: role-one-vars
- debug:
msg: "in role-one: {{ variable_one }}"
role-two/tasks/main.yml would look like:
---
- import_role:
name: role-one-vars
- debug:
msg: "in role-two: {{ variable_one }}"
And role-one-vars/vars/main.yml would look like:
---
variable_one: role one variable
Putting this all together, the output looks like:
PLAY [Test Sharing Role Variables] *****************************************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************************************************
ok: [one]
TASK [role-one : debug] ****************************************************************************************************************************************************
ok: [one] => {
"msg": "in role-one: role one variable"
}
PLAY [Test Sharing Role Variables] *****************************************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************************************************
ok: [two]
TASK [role-two : debug] ****************************************************************************************************************************************************
ok: [two] => {
"msg": "in role-two: role one variable"
}
PLAY RECAP *****************************************************************************************************************************************************************
one : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
two : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Q: "Access variable from one role in another role in an Ansible playbook with multiple hosts"
A: Short answer: Use set_fact and put the variable into the hostvars.
Details: Given the roles
shell> cat roles/role-one/defaults/main.yml
variable_one: Role One Variable
shell> cat roles/role-one/tasks/main.yml
- debug:
var: variable_one
shell> cat roles/role-two/tasks/main.yml
- debug:
var: variable_one
The playbook
- hosts: one
roles:
- role-one
tasks:
- debug:
var: variable_one
- hosts: two
roles:
- role-two
- hosts: one
tasks:
- debug:
var: variable_one
gives (abridged)
PLAY [one] ************************************************
TASK [role-one : debug] ****
ok: [one] =>
variable_one: Role One Variable
TASK [debug] ****
ok: [one] =>
variable_one: Role One Variable
PLAY [two] ************************************************
TASK [role-two : debug] ****
ok: [two] =>
variable_one: VARIABLE IS NOT DEFINED!
PLAY [one] ************************************************
TASK [debug] ****
ok: [one] =>
variable_one: VARIABLE IS NOT DEFINED!
As expected, the variable variable_one is visible to the tasks in the first play. But, there is no reason the variable should be visible to the host two in the second play. The variable is not visible also to the same host in the third play because it hasn't been stored in the hostvars aka "instantiated". The playbook below
- hosts: one
roles:
- role-one
tasks:
- debug:
var: variable_one
- set_fact:
variable_one: "{{ variable_one }}"
- hosts: two
roles:
- role-two
- hosts: one
tasks:
- debug:
var: variable_one
gives (abridged)
PLAY [one] ************************************************
TASK [role-one : debug] ****
ok: [one] =>
variable_one: Role One Variable
TASK [debug] ****
ok: [one] =>
variable_one: Role One Variable
TASK [set_fact] ****
ok: [one]
PLAY [two] ************************************************
TASK [role-two : debug] ****
ok: [two] =>
variable_one: VARIABLE IS NOT DEFINED!
PLAY [one] ************************************************
TASK [debug] ****
ok: [one] =>
variable_one: Role One Variable
Now, the variable is visible to the host one in the whole playbook and can be visible to other hosts using hostvars as well. For example, the playbook below
- hosts: one
roles:
- role-one
tasks:
- debug:
var: variable_one
- set_fact:
variable_one: "{{ variable_one }}"
- hosts: two
tasks:
- set_fact:
variable_one: "{{ hostvars.one.variable_one }}"
- include_role:
name: role-two
gives (abridged)
PLAY [one] ************************************************
TASK [role-one : debug] ****
ok: [one] =>
variable_one: Role One Variable
TASK [debug] ****
ok: [one] =>
variable_one: Role One Variable
TASK [set_fact] ****
ok: [one]
PLAY [two] ************************************************
TASK [set_fact] ****
ok: [two]
TASK [include_role : role-two] ****
TASK [role-two : debug] ****
ok: [two] =>
variable_one: Role One Variable
The problem with the above setting is that the host referencing hostvars is hardcoded. A better approach is to "instantiate" the variable in the first play for all hosts. For example, add a dummy task to the role
shell> cat roles/role-one/tasks/noop.yml
- meta: noop
Then, in the first play, include all hosts, run_once import the role, run the dummy task only, and "instantiate" the variable for all hosts. For example
- hosts: all
tasks:
- import_role:
name: role-one
tasks_from: noop.yml
run_once: true
- set_fact:
variable_one: "{{ variable_one }}"
run_once: true
- hosts: two
roles:
- role-two
- hosts: one
roles:
- role-two
gives (abridged)
PLAY [all] ************************************************
TASK [set_fact] ****
ok: [one]
PLAY [two] ************************************************
TASK [role-two : debug] ****
ok: [two] =>
variable_one: Role One Variable
PLAY [one] ************************************************
TASK [role-two : debug] ****
ok: [one] =>
variable_one: Role One Variable

Ansible loop giving warning found a duplicate dict key (when). Using last defined value only

I am trying to iterate over an array and assign the value to variables hooks_enabled, workflow_artifact_id, workflow_version, one by one in every iteration and perform a specific task (currently debug, later change to Helm install command).
Code:
---
- name: Executing Ansible Playbook
hosts: localhost
become: yes
become_user: someuser
pre_tasks:
- include_vars: global_vars.yaml
- name: Print some debug information
set_fact:
all_vars: |
Content of vars
--------------------------------
{{ vars | to_nice_json }}
tasks:
- name: Iterate over an array
set_fact:
hooks_enabled: '{{ array_item1_hooks_enabled }}'
workflow_artifact_id: '{{ array_item1_workflow_artifact_id }}'
workflow_version: '{{ array_item1_workflow_version }}'
when: "item == 'array_item1'"
set_fact:
hooks_enabled: '{{ array_item2_hooks_enabled }}'
workflow_artifact_id: '{{ array_item2_workflow_artifact_id }}'
workflow_version: '{{ array_item2_workflow_version }}'
when: "item == 'array_item2'"
with_items: "{{ array}}"
# Change debug with helm install command
- debug:
msg: " id= '{{ workflow_artifact_id }}'"
The issue I am facing is, only the last when is considered and others are skipped
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
[WARNING]: While constructing a mapping from /c/ansible-test/second.yaml, line 16, column 7, found a duplicate dict key (set_fact). Using last defined value only.
[WARNING]: While constructing a mapping from /c/ansible-test/second.yaml, line 16, column 7, found a duplicate dict key (when). Using last defined value only.
PLAY [Executing Ansible Playbook] *********************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [include_vars] ***********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Print some debug information] *******************************************************************************************************************************************************************************
ok: [localhost]
TASK [Iterate over an array] **************************************************************************************************************************************************************************************
skipping: [localhost] => (item=array_item1)
ok: [localhost] => (item=array_item2)
skipping: [localhost] => (item=array_item3)
skipping: [localhost] => (item=array_item4)
skipping: [localhost] => (item=array_item5)
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": " id= 'algorithm-Workflow'"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
How do I modify the block to enable all the when statement execute and later use helm install command to take the variables one by one.
I would go with a dynamic variable construction using the vars lookup.
Something along the lines of:
- set_fact:
hooks_enabled: "{{ lookup('vars', item ~ '_hooks_enabled') }}"
workflow_artifact_id: "{{ lookup('vars', item ~ '_workflow_artifact_id') }}"
workflow_version: "{{ lookup('vars', item ~ '_workflow_version') }}"
when: "item in ['array_item1', 'array_item2']"
with_items: "{{ array }}"

How to pass vars in nested Ansible playbook?

I have a nested ansible playbook (master) file and I want to call included playbook (slave) with their own JSON vars.
Master.yaml
- name: this is a play at the top level of a file
hosts: local
connection: local
tasks:
- debug: msg=hello
- include: slave_first.yaml
- include: slave_second.yaml
slave_first.yaml should make use of "vars/slave_first_vars.json" file and slave_second.yaml should make use of "vars/slave_second_vars.json" file.
When including playbooks you can only override variables with vars statement, like:
- include: slave_first.yaml
vars:
myvar: foo
- include: slave_second.yaml
vars:
myvar: foo
There are no other options for PlaybookInclude.
If you need to load variables from files, you have to use vars_files or include_vars inside your slave playbooks.
In your scenario, I'll use like this, master.yml:
- hosts: localhost
connection: local
tasks:
- include: slave_first.yml
vars:
VAR_FILE: "slave_first_vars"
- include: slave_second.yml
vars:
VAR_FILE: "slave_second_vars"
While slave_first.yml and slave_second.yml are like this, in my case both are same but you get an idea that how you can use them:
slave_first.yml:
---
- include_vars: "{{ VAR_FILE }}.yml"
- debug:
msg: "{{ DOMAIN_NAME }}"
slave_second.yml:
---
- include_vars: "{{ VAR_FILE }}.yml"
- debug:
msg: "{{ DOMAIN_NAME }}"
Now come to the different variable part:
slave_first_vars.yml: in your case it will be json
---
DOMAIN_NAME: "first.com"
slave_second_vars.yml:
---
DOMAIN_NAME: "second.com"
Then you can run and verify that if work as expected:
➤ansible-playbook -i localhost, master.yml
PLAY [localhost] **********************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [localhost]
TASK [include_vars] **********************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "first.com"
}
TASK [include_vars] **********************************************************************************
ok: [localhost]
TASK [debug] **********************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "second.com"
}
PLAY RECAP **********************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
Hope that might help you!

Resources