How to create a 'null' default in Ansible - ansible

I want 'lucy' to follow the user module creators' default behaviour which is to create and use a group matching the user name 'lucy'. However for 'frank' I want the primary group to be an existing one; gid 1003. So my hash looks like this:
lucy:
comment: dog
frank:
comment: cat
group: 1003
And my task looks like this:
- name: Set up local unix user accounts
user:
name: "{{ item.key }}"
comment: "{{ item.value.comment }}"
group: "{{ item.value.group | default(undef) }}"
loop: "{{ users|dict2items }}"
This doesn't work, as undef is not recognised. Nor is anything else I can think of. 'null', 'None' etc. all fail. '' creates an empty string which is not right either. I can't find out how to do it.
Any ideas?

default(omit) is what you are looking for. For example,
- name: Set up local Unix user accounts
user:
name: "{{ item.key }}"
comment: "{{ item.value.comment }}"
group: "{{ item.value.group | default(omit) }}"
loop: "{{ users|dict2items }}"
Comments
Comment by Lucas Basquerotto: "... omit only works correctly when used directly in a module, it won't work in a set_fact ..."
A: You're wrong. For example, default(omit) works both in set_fact and in the module. The first item in the list defaults to false with the result "VARIABLE IS NOT DEFINED!". The second item defaults to omit. Omitted parameter get_checksum defaults to true with the checksum in the results
shell> cat pb.yml
- hosts: localhost
tasks:
- set_fact:
test:
- "{{ gchk|default(false) }}"
- "{{ gchk|default(omit) }}"
- stat:
path: /etc/passwd
get_checksum: "{{ item }}"
loop: "{{ test }}"
register: result
- debug:
var: item.stat.checksum
loop: "{{ result.results }}"
gives
shell> ansible-playbook pb.yml | grep item.stat.checksum
item.stat.checksum: VARIABLE IS NOT DEFINED!
item.stat.checksum: 7c73e9f589ca1f0a1372aa4cd6944feec459c4a8
In addition to this, default(omit) works as expected also in some expressions. For example
- debug:
msg: "{{ {'a': item}|combine({'b': true}) }}"
loop: "{{ test }}"
gives
msg:
a: false
b: true
msg:
b: true
See the results without default values
shell> ansible-playbook pb.yml -e "gchk={{ true|bool }}"

Related

Ansible - loop over multiple items in stdout_lines

I am performing a grep with multiple items.
---
- hosts: my_host
gather_facts: false
vars:
my_list:
- whatever
- something
tasks:
- name: grep for item in search path
shell: "grep -rIL {{ item }} /tmp"
register: the_grep
loop: "{{ my_list }}"
- debug:
msg: "{{ item.stdout_lines }}"
loop: "{{ the_grep.results }}"
Depending on the result, multiple files could match.
msg:
- /tmp/something.conf
- /tmp/folder/file.txt
Q: How would I configure Ansible to loop over the items in stdout_lines?
The use case I'm solving is to delete .ini sections based on the item, but in this case, Ansible doesn't loop over the stdout_lines.
- name: remove stanza from ini file
ini_file:
path: "{{ item.stdout_lines }}"
section: "{{ item.item }}"
mode: '0600'
state: absent
loop: "{{ the_grep.results }}"
when: item.stdout_lines | length > 0
It seems that this doesn't work, but configuring item.stdout_lines[0] gives the partially expected result, since Ansible will use only the first item in that list. But ofc, not the 2nd and so on.
Perhaps there's a prettier answer, but solved it by using with_nested and creating a json_query:
- name: remove stanza from ini file
ini_file:
path: "{{ item.0 }}"
section: "{{ item.1.item }}"
mode: '0600'
state: absent
with_nested:
- "{{ the_grep | json_query('results[].stdout_lines[]') }}"
- "{{ the_grep.results }}"

Create only those users from a list that do not exist

I have a list of users and I only want to create those, which do not exist on the system.
This is what I have tried:
- name: Connection to Unix server
hosts: localhost
vars:
USER_ID_details:
- user_id: my_user1
groups: wheel
real_full_name: my_user_name1
affected_host: localhost
email_id: my_user1#ibm.com
- user_id: my_user2
groups: wheel
real_full_name: my_user_name2
affected_host: localhost
email_id: my_user2#ibm.com
tasks:
- name: check for the ID is present
#shell: "id {{ item.user_id }}"
shell: grep "{{ item.user_id }}" /etc/passwd | awk -F":" '{print $1}'
loop: "{{ USER_ID_details }}"
ignore_errors: true
register: id_check
- name: setting var
set_fact:
user_id_names1: "{{ user_id_names1|default([]) + [item.stdout] }}"
with_items: "{{ id_check.results }}"
when: item.stdout != ""
- debug: var=user_id_names1
- block:
- name: create Linux user as per specification
user:
name: "{{ item.user_id }}"
password: "{{ pass_reg.stdout_lines[0] | password_hash('sha512') }}"
group: "{{ group_name }}"
groups: "{{ item.groups }}"
comment: "{{ comment }}"
shell: "{{ user_shell }}"
#uid: "{{ uid_num.item }}"
home: "/home/{{ item.user_id}}"
loop: "{{ USER_ID_details }}"
when:
- os_type == "RedHat"
- "{{ item.user_id }} not {{id_check.results}}"
What could be the best way to check if user exists, and only add those, that don't exist on server. I'm trying to check the user_id_names1 list of ids generated against list dictionary USER_ID_details and filter the existing ones.
As Vladimir Botka stated on the comment, ansible does that already. Most modules (including the user module) will ensure that the state you specify will be present on the machine, after ansible ran.
For example, if you specify that a certain user exists on the system, it will after you ran the playbook. It will be created if it didn't exist before, but it will not be added, if it already existed.
The catch is, that ansible will try to create the state you specified, possibly changing your existing users.
For example, let's assume your user already exists, but has changed the default shell to /bin/zsh while in your playbook you specify, that it should have /bin/bash. In that case, ansible will change the default shell to /bin/bash whenever you run your playbook.
If you don't care about existing users being modified (or you are sure they never will be) you can just run the user module for all users every time, as users will not be added twice.
Otherwise you can do this to check if a user exists and only add it if it does not:
tasks:
- name: get list of existing users
getent:
database: passwd
- name: get list of existing usernames
set_fact:
existing_users: "{{ ansible_facts.getent_passwd.keys() | list }}"
- name: create Linux user as per specification
user:
name: "{{ item.user_id }}"
password: "{{ pass_reg.stdout_lines[0] | password_hash('sha512') }}"
group: "{{ group_name }}"
groups: "{{ item.groups }}"
comment: "{{ comment }}"
shell: "{{ user_shell }}"
home: "/home/{{ item.user_id}}"
loop: "{{ USER_ID_details }}"
when: item.user_id not in existing_users
Make sure to read the documentation of the user module and that you understand what all the options do.
For example, the password option will set the password of that user to the specified value. If the user changed his password, you will change it back every time you run the playbook. Set update_password: on_create to prevent that.
You are also setting the primary group of all users to the same value (in group_name). Make sure that is what you actually want to do.
#toydarian Used below method too when i didnot know about the getent option.
- name: check for the ID is present
#shell: "id {{ item.user_id }}"
shell: grep "{{ item.user_id }}" /etc/passwd | awk -F":" '{print $1}'
loop: "{{ USER_ID_details }}"
ignore_errors: true
register: id_check
- name: setting var
set_fact:
user_id_names1: "{{ user_id_names1|default([]) + [item.stdout] }}"
with_items: "{{ id_check.results }}"
when: item.stdout != ""
- debug: var=user_id_names1
- name: create Linux user as per specification
user:
name: "{{ item.user_id }}"
password: "{{ pass_reg.stdout_lines[0] | password_hash('sha512') }}"
group: "{{ group_name }}"
groups: "{{ item.groups }}"
comment: "{{ item.real_full_name }}"
shell: "{{ user_shell }}"
#uid: "{{ uid_num.item }}"
home: "/home/{{ item.user_id}}"
loop: "{{ USER_ID_details }}"
when:
- os_type == "RedHat"
- item.user_id not in user_id_names1

How to get interpolated value of variable in Ansible / Jinja2

I'm trying to define Ansible variables this way:
user:
name: First Last
nick: '{{ vars["user"]["name"] | regex_replace("\W", "_") }}'
email: '{{ vars["user"]["nick"] }}#example.com'
And the result email is: "{{ vars[\"user\"][\"name\"] | regex_replace(\"\\W\", \"_\") }}#example.com.
I also tried to set email like this: {{ lookup("vars", "user.nick") }}#example.com or {{ lookup("vars", "user")["nick"] }}#example.com, and it says An unhandled exception occurred while running the lookup plugin 'vars'.
Is there a way to get resulting variable values as:
user:
name: First Last
nick: First_Last
email: First_Last#example.com
?
ansible 2.9.10,
python version = 3.8.5
It's not possible cross-reference keys in a dictionary. It's necessary to declare the variables outside the dictionary. For example, the playbook
- hosts: localhost
vars:
my_name: First Last
my_nick: "{{ my_name | regex_replace('\\W', '_') }}"
user:
name: "{{ my_name }}"
nick: "{{ my_nick }}"
email: "{{ my_nick }}#example.com"
tasks:
- debug:
var: user
gives (abridged)
user:
email: First_Last#example.com
name: First Last
nick: First_Last
A more flexible option is to create the variables in the loop. For example, the playbook
- hosts: localhost
vars:
users:
"First Last":
domain: example.com
tasks:
- debug:
msg:
- "name: {{ name }}"
- "nick: {{ nick }}"
- "email: {{ email }}"
loop: "{{ users|dict2items }}"
vars:
name: "{{ item.key }}"
nick: "{{ item.key|regex_replace('\\W', '_') }}"
email: "{{ nick ~ '#' ~ item.value.domain }}"
gives (abridged)
msg:
- 'name: First Last'
- 'nick: First_Last'
- 'email: First_Last#example.com'

Ansible - skip undefined variable in dict

I`m using ipa_user module to setup users. There is variable passsword which force new password.
For some users (when var is not in dict) I would like to skip it in iteration, but it always fail.
This is snippet from my playbook. Ansible version is 2.7
task:
- name: adding ipa users
ipa_user:
name: "{{ item.value.login }}"
state: "{{ item.value.state }}"
givenname: "{{ item.value.givenname }}"
sn: "{{ item.value.surname }}"
mail: "{{ item.value.mail }}"
telephonenumber: "{{ item.value.telephonenumber }}"
title: "{{ item.value.title }}"
password: "{{ item.value.password }}" <<- to be skipped if not found
ipa_host: ipa.gdi.telekom.de
ipa_user: admin
ipa_pass: "{{ ipa_pass }}"
with_dict: "{{ipausers}}"
when: item.key in ipausers.keys()
register: output_ipa_users
Log:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'password'\n\nThe error appears to have been in '/builds/gitlab/infra/user-management/roles/free-ipa/tasks/main.yml': line 13, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: adding ipa users\n ^ here\n"}
Note: I tried it with:
with_dict: "{{ipausers|default({})}}"
ignore_errors: yes
without success
Not sure if it'll be much help to you now but for others than stumble on this post, I ended up with something like below for a similar problem. I'm using Ansible 2.7.8.
- name: Creating user accounts...
user:
name: "{{ item.name }}"
state: "{{ item.state }}"
comment: "{{ item.comment | default(omit) }}"
group: "{{ item.groups is defined | ternary((item.groups|default([]))[0], omit) }}"
groups: "{{ item.groups | default(omit) }}"
password: "{{ item.password_hash | default(omit) }}"
uid: "{{ item.uid | default(omit) }}"
with_items: "{{ managed_users }}"
The solution is
group: "{{ item.groups is defined | ternary((item.groups|default([]))[0], omit) }}"
If groups isn't in item then Ansible will omit the group part of this tasks but jinja2 will evaluate item.groups[0] anyway. So to allow for this we have to use item.groups|default([]) so jinja2 uses an empty list when groups isn't defined instead of throwing a 'dict object' has no attribute error. The omit part is similar to the default(omit) filter where Ansible simply omits the option from the task.
Lubo's problem is a little simpler so using just default(omit) filter should work. That said as password is required so the entire task should be skipped with a conditional.
- name: adding ipa users
ipa_user:
name: "{{ item.value.login }}"
state: "{{ item.value.state }}"
givenname: "{{ item.value.givenname }}"
sn: "{{ item.value.surname }}"
mail: "{{ item.value.mail }}"
telephonenumber: "{{ item.value.telephonenumber }}"
title: "{{ item.value.title }}"
password: "{{ item.value.password | default(omit) }}" #<-- would be omitted
ipa_host: ipa.gdi.telekom.de
ipa_user: admin
ipa_pass: "{{ ipa_pass }}"
with_dict: "{{ipausers}}"
when: item.key in ipausers.keys() and item.key.password is defined #<-- second check for when password is not defined.
register: output_ipa_users
If you want to completely skip the ipa_user module execution when password is not defined, check for its presence in your when clause:
when: item.value.password | default('') | length > 0
If you want to execute the ipa_user module without specifying a password for user if it does not exists, use the omit placeholder in your module params:
password: "{{ item.value.password | default(omit) }}"
Note: your current when clause can be removed. It will always return true as you are looping over a dict and later checking if the current key in the loop is part of that dict.
There is a special omit variable to omit module parameters.
password: "{{ item.value.password|default(omit) }}"
To make a playbook or a role reusable it is a good idea to declare all parameters of a module in the task and default(omit) parameters that are not required.

Adding field to dict items

Consider the following play. What I am trying to do is add a field, tmp_path which is basically the key and revision appended together to each element in the scripts dict.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
# - with_items: "{{ scripts }}"
# set_fact: {{item.value.tmp_path}}="{{item.key}}_{{item.value.revision}}"
# - with_items: "{{ scripts }}"
# debug:
# msg: "{{ item.value.tmp_path }}"
...
Obviously the commented code doesn't work, any idea how I can get this working? Is it possible to alter the scripts dict directly, or should I somehow be creating a new dict to reference instead?
By the way welcome to correct the terminology for what I am trying to do.
OK, I think I got a solution (below), at least to let me move forwards with this. Disadvantages are it has removed the structure of my dict and also seems a bit redundant having to redefine all the fields and use a new variable, If anyone can provide a better solution I will accept that instead.
---
- hosts: localhost
connection: local
gather_facts: no
vars:
scripts:
a.pl:
revision: 123
b.pl:
revision: 456
tasks:
- with_dict: "{{ scripts }}"
debug:
msg: "{{ item.key }}_{{ item.value.revision }}"
- with_dict: "{{ scripts }}"
set_fact:
new_scripts: "{{ (new_scripts | default([])) + [ {'name': item.key, 'revision': item.value.revision, 'tmp_path': item.key ~ '_' ~ item.value.revision}] }}"
# - debug:
# var: x
# - with_dict: "{{ scripts }}"
- with_items: "{{ new_scripts }}"
debug:
msg: "{{ item.tmp_path }}"
...
BTW credit to the following question which pointed me in the right direction:
Using Ansible set_fact to create a dictionary from register results

Resources