I have four systems, in those I need to extract facts then use them as variables on a jinja 2 template.
In Ansible i have:
vars:
office1:
web01:
myip: 10.10.10.10 // or fact
peer: 10.10.10.20
web02
myip: 10.10.10.20 // or fact
peer: 10.10.10.10
office2:
web01:
myip: 10.20.20.30 // or fact
peer: 10.20.20.40
web02
myip: 10.20.20.40 // or fact
peer: 10.20.20.30
On the jinja 2 template I have:
# Config File:
host_name: {{ ansible_hostname }} // web01
host_ip: {{ ansible_eth0.ipv4.address }}
host_peer: {{ office1."{{ ansible_hostname }}".peer }}
I however get error that Ansible variable: office1.ansible_hostname.peer is not defined.
Any help with this would be greatly appreciated.
Expansion in Ansible is not recursive. Try the expansion below
host_peer: {{ office1[ansible_hostname].peer }}
For example the play below
- hosts: test_01
gather_facts: yes
vars:
office1:
test_01:
myip: 10.20.20.30
peer: 10.20.20.40
tasks:
- template:
src: template.j2
dest: /scratch/test_01.cfg
with template.j2
# Config File:
host_name: {{ ansible_hostname }}
host_peer: {{ office1[ansible_hostname].peer }}
gives
# cat /scratch/test_01.cfg
# Config File:
host_name: test_01
host_peer: 10.20.20.40
To answer the question
Q: "Create Variable From Ansible Facts"
A: An option would be to use lookup vars. For example the play below
vars:
var1: var1
var2: var2
var3: var3
tasks:
- debug:
msg: "{{ lookup('vars', 'var' + item) }}"
with_sequence: start=1 end=3
gives (abridged)
"msg": "var1"
"msg": "var2"
"msg": "var3"
Related
I want to overwrite some variables in my playbook file from the inventory file for a host that are defined as "vars_prompt". If I understand it correctly, Ansible shouldn't prompt for the variables if they were already set before, however, it still prompts for the variables when I try to execute the playbook.
How can I overwrite the "vars_prompt" variables from the inventory or is this not possible because of the variable precedence definition of Ansible?
Example:
playbook.yml
---
- name: Install Gateway
hosts: all
become: yes
vars_prompt:
- name: "hostname"
prompt: "Hostname"
private: no
...
inventory.yml
---
all:
children:
gateways:
hosts:
gateway:
ansible_host: 192.168.1.10
ansible_user: user
hostname: "gateway-name"
...
Q: "If I understand it correctly, Ansible shouldn't prompt for the variables if they were already set before, however, it still prompts for the variables when I try to execute the playbook."
A: You're wrong. Ansible won't prompt for variables defined by the command line --extra-vars. Quoting from Interactive input: prompts:
Prompts for individual vars_prompt variables will be skipped for any variable that is already defined through the command line --extra-vars option, ...
You can't overwrite vars_prompt variables from the inventory. See Understanding variable precedence. Inventory variables (3.-9.) is lower precedence compared to play vars_prompt (13.). The precedence of extra vars is 22.
Use the module pause to ask for the hostname if any variable is not defined. For example, the inventory
shell> cat hosts
host_1
host_2
and the playbook
hosts: all
gather_facts: false
vars:
hostnames: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'hostname')|
list }}"
hostnames_undef: "{{ hostnames|from_yaml|
select('eq', 'AnsibleUndefined')|
length > 0 }}"
tasks:
- debug:
msg: |
hostnames: {{ hostnames }}
hostnames_undef: {{ hostnames_undef }}
run_once: true
- pause:
prompt: "Hostname"
register: out
when: hostnames_undef
run_once: true
- set_fact:
hostname: "{{ out.user_input }}"
when: hostname is not defined
- debug:
var: hostname
gives
shell> ansible-playbook pb.yml
PLAY [all] ************************************************************************************
TASK [debug] **********************************************************************************
ok: [host_1] =>
msg: |-
hostnames: [AnsibleUndefined, AnsibleUndefined]
hostnames_undef: True
TASK [pause] **********************************************************************************
[pause]
Hostname:
gw.example.com^Mok: [host_1]
TASK [set_fact] *******************************************************************************
ok: [host_1]
ok: [host_2]
TASK [debug] **********************************************************************************
ok: [host_1] =>
hostname: gw.example.com
ok: [host_2] =>
hostname: gw.example.com
PLAY RECAP ************************************************************************************
host_1: ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host_2: ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The playbook won't ovewrite variables defined in the inventory. For example
shell> cat hosts
host_1
host_2 hostname=gw2.example.com
gives
TASK [debug] **********************************************************************************
ok: [host_1] =>
hostname: gw.example.com
ok: [host_2] =>
hostname: gw2.example.com
I don't know if you can stop the prompts but you can se a default value directly in vars_prompts. In this way you do not need to type "gateway-name" every time.
vars_prompt:
- name: "hostname"
prompt: "Hostname"
private: no
default: "gateway-name"
Source: https://docs.ansible.com/ansible/latest/user_guide/playbooks_prompts.html
I have the following playbook, jinja template and yaml data. I am running this against a Cisco 9300 or 3850 switch. I just get the following error. If I run just the commands without the template the playbook runs fine. I have output the template to a yaml file and I don't see any issues with the output. The command in the error below is a valis command.
fatal: [3850-access-42]: FAILED! => {
"changed": false,
"module_stderr": "before: default interface GigabitEthernet2/0/1\r\nbefore: default interface GigabitEthernet2/0/1\r\n ^\r\n% Invalid input detected at '^' marker.\r\n\r\n3850-Access-(config)#",
"module_stdout": "",
"msg": "MODULE FAILURE\nSee stdout/stderr for the exact error"
}
Playbook
- name: Update edge switch
hosts: all
gather_facts: false
vars:
- template_path: "./templates/interfaces.j2"
tasks:
- name: include variables
include_vars: "./files/vars/jinja-test-data.yml"
- name: update config
cisco.ios.ios_config:
src: "{{ template_path }}"
jinja template
{% for item in ip_interfaces %}
before: default interface {{ item.interface }}
lines:
{% for line in item.int_attributes %}
- {{ line }}
{% endfor %}
parents: interface {{ item.interface }}
match: strict
after:
{% for cmd in item.shut_commands %}
- {{ cmd }}
{% endfor %}
{% endfor %}
yaml data
---
ip_interfaces:
- interface: GigabitEthernet2/0/1
int_attributes:
- switchport access vlan 20
- switchport voice vlan 21
- speed 100
- duplex full
- switchport mode access
- switchport nonegotiate
- no switchport port-security
- spanning-tree portfast
- spanning-tree bpduguard enable
- device-tracking attach-policy IPDT_POLICY
- source template WIRED_DOT1X_CLOSED
shut_commands:
- shutdown
- no shutdown
If I run the playbook without the template using the same data it runs fine.
Working playbook
- name: Update edge switch
hosts: all
gather_facts: false
vars:
- template_path: "./templates/interfaces.j2"
tasks:
- name: include variables
include_vars: "./files/vars/jinja-test-data.yml"
# - name: update config
# cisco.ios.ios_config:
# src: "{{ template_path }}"
- name: update config
cisco.ios.ios_config:
before: default interface TwoGigabitEthernet1/0/1
lines:
- switchport access vlan 10
- switchport voice vlan 11
- speed 100
- duplex full
- switchport mode access
- switchport nonegotiate
- no switchport port-security
- spanning-tree portfast
- spanning-tree bpduguard enable
- device-tracking attach-policy IPDT_POLICY
- source template WIRED_DOT1X_CLOSED
parents: interface TwoGigabitEthernet1/0/1
match: strict
after:
- shutdown
- no shutdown
vars:
ansible_command_timeout: 480
Results from working playbook
TASK [update config]
*************************************************************************************************** task path: redirecting (type: connection) ansible.builtin.network_cli
to ansible.netcommon.network_cli redirecting (type: terminal)
ansible.builtin.ios to cisco.ios.ios redirecting (type: cliconf)
ansible.builtin.ios to cisco.ios.ios redirecting (type: action)
cisco.ios.ios_config to cisco.ios.ios redirecting (type: action)
cisco.ios.ios_config to cisco.ios.ios [WARNING]: To ensure idempotency
and correct diff the input configuration lines should be similar to
how they appear if present in the running configuration on device
changed: [9300-access-240] => {"banners": {}, "changed": true,
"commands": ["default interface TwoGigabitEthernet1/0/1", "interface
TwoGigabitEthernet1/0/1", "switchport voice vlan 11", "speed 100",
"duplex full", "switchport mode access", "switchport nonegotiate", "no
switchport port-security", "spanning-tree portfast", "spanning-tree
bpduguard enable", "device-tracking attach-policy IPDT_POLICY",
"source template WIRED_DOT1X_CLOSED", "shutdown", "no shutdown"],
"updates": ["default interface TwoGigabitEthernet1/0/1", "interface
TwoGigabitEthernet1/0/1", "switchport voice vlan 11", "speed 100",
"duplex full", "switchport mode access", "switchport nonegotiate", "no
switchport port-security", "spanning-tree portfast", "spanning-tree
bpduguard enable", "device-tracking attach-policy IPDT_POLICY",
"source template WIRED_DOT1X_CLOSED", "shutdown", "no shutdown"]}
META: ran handlers META: ran handlers
PLAY RECAP
************************************************************************************************************* hav-lab-9300-access-240 : ok=2 changed=1 unreachable=0
failed=0 skipped=0 rescued=0 ignored=0
I figured this out finally. It seems that the ios_config module assumes the lines: command so it is not needed in the template output. All of the lines do not need to be preceeded by the "- " as they would in a normal playbook call directly to the ios_config module. If there is a parent object the subsequent lines are indented by one space. This is plainly shown in the docs which I did not pick up as soon as I wished I had. I hope this helps someone else out or saves them some time. Now I just need to figure out if the before or after command can be used as well. It would be a shame to have to loop through three different templates and hit the interfaces three seperate times.
> # Example ios_template.j2
> # ip access-list extended test
> # permit ip host 192.0.2.1 any log
> # permit ip host 192.0.2.2 any log
> # permit ip host 192.0.2.3 any log
> # permit ip host 192.0.2.4 any log
An answer on StackOverflow suggests using - debug: var=vars or - debug: var=hostvars to print out all the variables used by an Ansible playbook.
Using var=hostvars did not print out all of the variables. But I did get all of the variables printed out when I added the following lines to the top of the main.yml file of the role executed by my playbook:
- name: print all variables
debug:
var=vars
The problem is that the values of the variables printed out are not fully evaluated if they are dependent on the values of other variables. For example, here is a portion of what gets printed out:
"env": "dev",
"rpm_repo": "project-subproject-rpm-{{env}}",
"index_prefix": "project{{ ('') if (env=='prod') else ('_' + env) }}",
"our_server": "{{ ('0.0.0.0') if (env=='dev') else ('192.168.100.200:9997') }}",
How can I get Ansible to print out the variables fully evaluated like this?
"env": "dev",
"rpm_repo": "project-subproject-rpm-dev",
"index_prefix": "project_dev",
"our_server": "0.0.0.0",
EDIT:
After incorporating the tasks section in the answer into my playbook file and removing the roles section, my playbook file looks like the following (where install-vars.yml contains some variable definitions):
- hosts: all
become: true
vars_files:
- install-vars.yml
tasks:
- debug:
msg: |-
{% for k in _my_vars %}
{{ k }}: {{ lookup('vars', k) }}
{% endfor %}
vars:
_special_vars:
- ansible_dependent_role_names
- ansible_play_batch
- ansible_play_hosts
- ansible_play_hosts_all
- ansible_play_name
- ansible_play_role_names
- ansible_role_names
- environment
- hostvars
- play_hosts
- role_names
_hostvars: "{{ hostvars[inventory_hostname].keys() }}"
_my_vars: "{{ vars.keys()|
difference(_hostvars)|
difference(_special_vars)|
reject('match', '^_.*$')|
list|
sort }}"
When I try to run the playbook, I get this failure:
shell> ansible-playbook playbook.yml
SSH password:
SUDO password[defaults to SSH password]:
PLAY [all] *********************************************************************
TASK [setup] *******************************************************************
ok: [192.168.100.111]
TASK [debug] *******************************************************************
fatal: [192.168.100.111]: FAILED! => {"failed": true, "msg": "lookup plugin (vars) not found"}
to retry, use: --limit #/usr/local/project-directory/installer-1.0.0.0/playbook.retry
PLAY RECAP *********************************************************************
192.168.100.111 : ok=1 changed=0 unreachable=0 failed=1
The minimal playbook below
shell> cat pb.yml
- hosts: localhost
gather_facts: false
vars:
test_var1: A
test_var2: "{{ test_var1 }}"
tasks:
- debug:
var: vars
reproduces the problem you described. For example,
shell> ansible-playbook pb.yml | grep test_var
test_var1: A
test_var2: '{{ test_var1 }}'
Q: How can I print out the actual values of all the variables used by an Ansible playbook?
A: You can get the actual values of the variables when you evaluate them. For example,
shell> cat pb.yml
- hosts: localhost
gather_facts: false
vars:
test_var1: A
test_var2: "{{ test_var1 }}"
tasks:
- debug:
msg: |-
{% for k in _my_vars %}
{{ k }}: {{ lookup('vars', k) }}
{% endfor %}
vars:
_special_vars:
- ansible_dependent_role_names
- ansible_play_batch
- ansible_play_hosts
- ansible_play_hosts_all
- ansible_play_name
- ansible_play_role_names
- ansible_role_names
- environment
- hostvars
- play_hosts
- role_names
_hostvars: "{{ hostvars[inventory_hostname].keys() }}"
_my_vars: "{{ vars.keys()|
difference(_hostvars)|
difference(_special_vars)|
reject('match', '^_.*$')|
list|
sort }}"
gives the evaluated playbook's vars
msg: |-
test_var1: A
test_var2: A
Looking for an answer to the same question, I found the following solution from this link:
- name: Display all variables/facts known for a host
debug:
var: hostvars[inventory_hostname]
tags: debug_info
I have to create a number of users using ansible. I pass the users as a list inside my ansible play in the vars section:
vars:
users: ['user1', 'user2']
I then have to create a script that uses this users' ID as an argument. The command inside the script is something like this:
blobfuse $1 --tmp-path=/mnt/resource/{{ item }}/ -o attr_timeout=240 -o entry_timeout=240 -o negative_timeout=120 -o uid=$USER_ID -o allow_other --container-name={{ item }} --file-cache-timeout-in-seconds=120 --config-file=/root/connection-{{ item }}.cfg
Everything works fine, with the exception of uid=
I have tried with the lookup's pipe plugin but this doesn't get me the correct UID:
{{ lookup('pipe', 'grep -w {{ item }} /etc/passwd | cut -d : -f3') }}
My end goal is to get the UID of each of the created users, and pass that to the blobfuse command above.
Q: "Get the UID of each created user."
A: Module getent serves precisely this purpose. For example,
- hosts: localhost
vars:
my_users: [root, admin]
tasks:
- getent:
database: passwd
- debug:
msg: |
{{ item }} uid: {{ getent_passwd[item].1 }}
{{ item }} gid: {{ getent_passwd[item].2 }}
{{ item }} home: {{ getent_passwd[item].4 }}
{{ item }} shell: {{ getent_passwd[item].5 }}
loop: "{{ my_users }}"
gives (abridged)
TASK [debug] ************************************************************
ok: [localhost] => (item=root) =>
msg: |-
root uid: 0
root gid: 0
root home: /root
root shell: /bin/bash
ok: [localhost] => (item=admin) =>
msg: |-
admin uid: 1002
admin gid: 1002
admin home: /home/admin
admin shell: /bin/bash
Module getent automatically created the dictionary getent_passwd which can be used in a template. For example, the template below
shell> cat template.j2
{% for user in my_users %}
{{ user }} {{ getent_passwd[user].1 }}
{% endfor %}
- template:
src: template.j2
dest: my_users.txt
gives
shell> cat my_users.txt
root 0
admin 1002
I had the same question.
Unfortunately, the lookup plugin is only useful on the control node.
In other words, the subjected users (with the same uid) need to be on both the control node and all the managed nodes. Or the task will fail with return code 1.
For me, using the getent module is a decent solution.
Alternatively, everything is actually there in the returned value of the user module. I only need to register its returned value to access them.
The playbook below demonstrates how I compute uid from the returned value of the user module both in an arbitrary subsequent task (debug) and in the jinja template.
- name: Demonstrate How to Compute UID after User Creations
hosts: all
become: true
gather_facts: no
vars:
my_users:
- u1
- u2
- u3
tasks:
- name: Create users in loop
user:
name: "{{ item }}"
loop: "{{ my_users }}"
register: created_users
- name: Display created users
debug:
msg: "User: uid:{{ item.uid }} name:{{ item.name }} group:{{ item.group }} home:{{ item.home }} shell:{{ item.shell }}"
loop: "{{ created_users.results }}"
# Use loop_control-label to suppress item verbosity
loop_control:
label: "{{ item.name }}"
- name: Work with template
template:
src: myusers.j2
dest: /tmp/myusers.txt
And here is myusers.j2 template.
{{ ansible_managed | comment }}
{% for item in created_users.results %}
User: uid:{{ item.uid }} name:{{ item.name }} group:{{ item.group }} home:{{ item.home }} shell:{{ item.shell }}
{% endfor %}
The output is as follows:
$ ansible-playbook compute-uid.yml
PLAY [Demonstrate How to Compute UID after User Creations] **********************************************************************************************
TASK [Create users in loop] *****************************************************************************************************************************
changed: [ansible1] => (item=u1)
changed: [ansible1] => (item=u2)
changed: [ansible1] => (item=u3)
TASK [Display created users] ****************************************************************************************************************************
ok: [ansible1] => (item=u1) => {
"msg": "User: uid:1001 name:u1 group:1001 home:/home/u1 shell:/bin/bash"
}
ok: [ansible1] => (item=u2) => {
"msg": "User: uid:1002 name:u2 group:1002 home:/home/u2 shell:/bin/bash"
}
ok: [ansible1] => (item=u3) => {
"msg": "User: uid:1003 name:u3 group:1003 home:/home/u3 shell:/bin/bash"
}
TASK [Work with template] *******************************************************************************************************************************
changed: [ansible1]
PLAY RECAP **********************************************************************************************************************************************
ansible1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
$ ansible all -a "cat /tmp/myusers.txt"
ansible1 | CHANGED | rc=0 >>
#
# Ansible managed
#
User: uid:1001 name:u1 group:1001 home:/home/u1 shell:/bin/bash
User: uid:1002 name:u2 group:1002 home:/home/u2 shell:/bin/bash
User: uid:1003 name:u3 group:1003 home:/home/u3 shell:/bin/bash
What about using id command and subshell? You can then do something like
blobfuse $1 --tmp-path=/mnt/resource/{{ item }}/ -o attr_timeout=240 -o entry_timeout=240 -o negative_timeout=120 -o uid="$(id {{ item }})" -o allow_other --container-name={{ item }} --file-cache-timeout-in-seconds=120 --config-file=/root/connection-{{ item }}.cfg
If you are using command module - you'll have to replace it with shell.
Edit: If you are using templates and want to use lookup plugin, which seems cleaner, you can do something like this (this was tested on local linux machine):
template.yaml
{% for item in users %}
{{ item }} {{ lookup('pipe', "id -u " + item) }}
{% endfor %}
ansible command
ansible -m template -i localhost, all -c local -a "src=template.yaml dest=result.txt" -e "{ users: [nobody,root]}"
result.txt
nobody 65534
root 0
In your case the error was using the {{ item }} in lookup, you should use just variable names and concatenation inside {{}} block
I have a set of variables and a task as follows. My intent is to dynamically do a healthcheck based on the URL the user chose.
vars:
current_hostname: "{{ ansible_hostname }}"
hc_url1: "https://blah1.com/healthcheck"
hc_url2: "https://blah2.com/healthcheck"
tasks:
- name: Notification Msg For Healthcheck
shell: "echo 'Performing healthcheck at the URL {{ lookup('vars', component) }} on host {{ current_hostname }}'"
Run playbook in Ansible 2.3
ansible-playbook ansible_playbook.yml -i inventory -k -v --extra-vars "component=hc_url1"
Error
fatal: [hostname]: FAILED! => {"failed": true, "msg": "lookup plugin (vars) not found"}
I know this happens because lookup plugin "var" was introduced in Ansible v2.5. Is there a way to do this in Ansible 2.3? I want get the value of {{ component }}, and then the value of {{ hc_url1 }}
PS - upgrading to 2.5 is not an option because of org restrictions
Alternatively, maybe you can do this using a dictionary.
For example,
vars:
current_hostname: "{{ ansible_hostname }}"
urls:
hc_url1: "https://blah1.com/healthcheck"
hc_url2: "https://blah2.com/healthcheck"
tasks:
- name: Notification Msg For Healthcheck
shell: "echo 'Performing healthcheck at the URL {{ urls[component] }} on host {{ current_hostname }}'"
That way, the user provided value of component will just be looked up as a key in the dictionary.