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

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

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(',') }}"

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: Lookup variables dynamically in v2.3

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.

Ansible: make output from a command become a key-value item/variable for the next command

I want to use this output (from a previous command) as an array of key-values or as an inventory for the next command in the same playbook
stdout:
hot-01: 10.100.0.101
hot-02: 10.100.0.102
hot-03: 10.100.0.103
....
hot-32: 10.100.0.132
like this:
- shell: "echo {{ item.key }} has value {{ item.value }}"
with_items: "{{ output.stdout_lines }}"
or:
- add_host: name={{ item.key }} ansible_ssh_host={{ item.value }}
with_items: "{{ output.stdout_lines }}"
Desired output of the echo command:
hot-01 has value 10.100.0.101
I also tried with with_dict: "{{ output.stdout }}" but still no luck
"fatal: [ANSIBLE] => with_dict expects a dict"
AFAIK there are no Jinja2 filters to convert strings to dictionaries.
But in your specific case, you can use the python's split string function to separate the key from the value:
- shell: "echo {{ item.split(': ')[0] }} has value {{ item.split(': ')[1] }}"
with_items: "{{ output.stdout_lines }}"
I know, having to use split twice is a bit sloppy.
As in this case your output is a valid YAML, you can also do the following:
- shell: "echo {{ item.key }} has value {{ item.value }}"
with_dict: "{{ output.stdout | from_yaml }}"
As a last resort, you can also create your own ansible module to create a Jinja2 filter to cover your case. There is an split module filter that you can use as inspiration here: https://github.com/timraasveld/ansible-string-split-filter