Ansible: Lookup variables dynamically in v2.3 - variables

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.

Related

How can I print out the actual values of all the variables used by an Ansible playbook?

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

add variables from Ansible inventory file to a dynamic inventory

i have an inventory file containing 200 servers and thier respective variables as shown in a sample below:
[myhost1.mrsh.com]
myhost1.mrsh.com ORACLE_HOME=/u/orahome12/middleware/12c_db1 ansible_user=wladmin
[myhost2.mrsh.com]
myhost2.mrsh.com ORACLE_HOME=/u/orahome12/middleware/12c_db1 ansible_user=wladmin
..........
........
i ask the user to enter any hostname which is passed to hostnames variable as below:
ansible-playbook /web/playbooks/automation/applycpupatch/applycpupatch.yml -i /web/playbooks/automation/applycpupatch/applycpupatch.hosts -f 5 -e action=status -e hostnames='myhost1
myhost2' -e patch_file='p33286132_122130_Generic.zip'
if myhost1 is present in the applycpupatch.hosts file i then wish to create a dynamic inventory using add_host having only myhost1 and its variables like ORACLE_HOME
Below is my code:
- name: "Play 1 - Set Destination details"
hosts: all
tasks:
- add_host:
name: "{{ item | upper }}"
groups: dest_nodes
ansible_user: "{{ hostvars[item + '*'].ansible_user }}"
ORACLE_HOME: "{{ hostvars[item + '*'].ORACLE_HOME }}"
when: inventory_hostname | regex_search(item)"
with_items: "{{ hostnames.split() }}"
Unfortunately, i get the error as below:
TASK [add_host] ****************************************************************
Saturday 20 November 2021 19:05:38 -0600 (0:00:00.059) 0:00:23.532 *****
[0;31mfatal: [myhost222.mrsh.com]: FAILED! => {"msg": "The conditional check 'inventory_hostname | regex_search(item)\"' failed. The error was: template error while templating string: unexpected char '\"' at 45. String: {% if inventory_hostname | regex_search(item)\" %} True {% else %} False {% endif %}\n\nThe error appears to be in '/web/playbooks/automation/applycpupatch/applycpupatch.yml': line 36, 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 - add_host:\n ^ here\n"}[0m
I also tried the below but it fails with the error.
ORACLE_HOME: "{{ hostvars['all'][item + '*'].ORACLE_HOME }}"
Thus my dynamic inventory constructed runtime dest_nodes in this example should have ONLY the below.
myhost1.mrsh.com ORACLE_HOME=/u/orahome12/middleware/12c_db1 ansible_user=wladmin
myhost2.mrsh.com ORACLE_HOME=/u/orahome12/middleware/12c_db1 ansible_user=wladmin
i dont understand very well what do you want, but you have lot of errors to fix in your playbook:
1- launch your playbook with -e hostnames='myhost1,myhost2'
2- fix your playbook: you have to test the result of your regex_search, use the variable inventory_hostname and use split(','):
a sample
- name: "Play 1 - Set Destination details"
hosts: all
tasks:
- debug:
msg: "{{ item }} - {{ hostvars[inventory_hostname].ORACLE_HOME }}"
when: (inventory_hostname | regex_search(item)) != ''
with_items: "{{ hostnames.split(',') }}"

How can I find the user ID using ansible and use that in a jinja2 template?

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

Create Variable From Ansible Facts

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"

Ansible - How to ssh into an instance without the 'authenticity of host' prompt?

I am using ansible to create several ec2 instances, copy files into those newly created servers and run commands on those servers. The issue is that after creating the servers I still have to enter yes in the following ssh prompt:
TASK [Adding /etc/rc.local2 to consul servers] *********************************
changed: [localhost -> 172.31.52.147] => (item={u'ip': u'172.31.52.147', u'number': 0})
The authenticity of host '172.31.57.20 (172.31.57.20)' can't be established.
ECDSA key fingerprint is 5e:c3:2e:52:10:29:1c:44:6f:d3:ac:10:78:10:01:89.
Are you sure you want to continue connecting (yes/no)? yes
changed: [localhost -> 172.31.57.20] => (item={u'ip': u'172.31.57.20', u'number': 1})
The authenticity of host '172.31.57.19 (172.31.57.19)' can't be established.
ECDSA key fingerprint is 4e:71:15:fe:c9:ec:3f:54:65:e8:a1:66:74:92:f4:ff.
Are you sure you want to continue connecting (yes/no)? yes
How can I have ansible ignore this prompt and just answer yes automatically? For reference here is my playbook:
---
- hosts: localhost
connection: local
gather_facts: false
sudo: yes
vars_files:
- ami-keys.yml
- ami-image.yml
tasks:
- name: create 3 consul servers
ec2:
aws_access_key: '{{ aws_access_key }}'
aws_secret_key: '{{ aws_secret_key }}'
key_name: terra
group: default
instance_type: t2.micro
image: '{{ ami }}'
region: '{{ region }}'
wait: true
exact_count: 3
count_tag:
Name: consul-server
instance_tags:
Name: consul-server
register: ec2
- name: Wait for SSH to come up
wait_for: host={{ item }} port=22 delay=1 timeout=480 state=started
with_items:
- "{{ ec2['tagged_instances'][0]['private_ip'] }}"
- "{{ ec2['tagged_instances'][1]['private_ip'] }}"
- "{{ ec2['tagged_instances'][2]['private_ip'] }}"
# shows the json data for the instances created
- name: consul server ec2 instance json data
debug:
msg: "{{ ec2['tagged_instances'] }}"
# bootstrapping
- name: Adding /etc/rc.local2 to consul servers
template:
src: template/{{ item.number }}.sh
dest: /etc/rc.local2
delegate_to: "{{ item.ip }}"
with_items:
- ip: "{{ ec2['tagged_instances'][0]['private_ip'] }}"
number: 0
- ip: "{{ ec2['tagged_instances'][1]['private_ip'] }}"
number: 1
- ip: "{{ ec2['tagged_instances'][2]['private_ip'] }}"
number: 2
ignore_errors: true
- name: give /etc/rc.local2 permissions to run and starting swarm
shell: "{{ item[1] }}"
delegate_to: "{{ item[0] }}"
with_nested:
- [ "{{ ec2['tagged_instances'][0]['private_ip'] }}",
"{{ ec2['tagged_instances'][1]['private_ip'] }}",
"{{ ec2['tagged_instances'][2]['private_ip'] }}" ]
- [ "sudo chmod +x /etc/rc.local2",
"sleep 10",
"consul reload",
"docker run --name swarm-manager -d -p 4000:4000 --restart=unless-stopped \
swarm manage -H :4000 \
--replication --advertise \
$(hostname -i):4000 \
consul://$(hostname -i):8500" ]
ignore_errors: true
Note: I have already tried running:
ansible-playbook -e 'host_key_checking=False' consul-server.yml
and it does not remove the prompt.
Going into /etc/ansible/ansible.cfg and uncommenting the line host_key_checking=False does remove the prompt however I want to avoid doing this and either enter something into my playbook or the command line when I run my playbook instead.
The common recommendation is to set host_key_checking=False in the Ansible configuration. This is a bad idea, because it assumes your network connection will never be compromised.
A much better idea that only assumes the network isn't MitMed when you first create the servers is to use ssh-keyscan to add the servers' fingerprints to the known hosts file:
- name: accept new ssh fingerprints
shell: ssh-keyscan -H {{ item.public_ip }} >> ~/.ssh/known_hosts
with_items: '{{ ec2.instances }}'