Ansible script with | differences of empty map fails [duplicate] - automation

I'm customizing linux users creation inside my role. I need to let users of my role customize home_directory, group_name, name, password.
I was wondering if there's a more flexible way to cope with default values.
I know that the code below is possible:
- name: Create default
user:
name: "default_name"
when: my_variable is not defined
- name: Create custom
user:
name: "{{my_variable}}"
when: my_variable is defined
But as I mentioned, there's a lot of optional variables and this creates a lot of possibilities.
Is there something like the code above?
user:
name: "default_name", "{{my_variable}}"
The code should set name="default_name" when my_variable isn't defined.
I could set all variables on defaults/main.yml and create the user like that:
- name: Create user
user:
name: "{{my_variable}}"
But those variables are inside a really big hash and there are some hashes inside that hash that can't be a default.

You can use Jinja's default:
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"

Not totally related, but you can also check for both undefined AND empty (for e.g my_variable:) variable. (NOTE: only works with ansible version > 1.9, see: link)
- name: Create user
user:
name: "{{ ((my_variable == None) | ternary('default_value', my_variable)) \
if my_variable is defined else 'default_value' }}"

If anybody is looking for an option which handles nested variables, there are several such options in this github issue.
In short, you need to use "default" filter for every level of nested vars. For a variable "a.nested.var" it would look like:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ((a | default({})).nested | default({}) ).var | default('bar') }}"
or you could set default values of empty dicts for each level of vars, maybe using "combine" filter. Or use "json_query" filter. But the option I chose seems simpler to me if you have only one level of nesting.

In case you using lookup to set default read from environment you have also set the second parameter of default to true:
- set_facts:
ansible_ssh_user: "{{ lookup('env', 'SSH_USER') | default('foo', true) }}"
You can also concatenate multiple default definitions:
- set_facts:
ansible_ssh_user: "{{ some_var.split('-')[1] | default(lookup('env','USER'), true) | default('foo') }}"

If you are assigning default value for boolean fact then ensure that no quotes is used inside default().
- name: create bool default
set_fact:
name: "{{ my_bool | default(true) }}"
For other variables used the same method given in verified answer.
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"

If you have a single play that you want to loop over the items, define that list in group_vars/all or somewhere else that makes sense:
all_items:
- first
- second
- third
- fourth
Then your task can look like this:
- name: List items or default list
debug:
var: item
with_items: "{{ varlist | default(all_items) }}"
Pass in varlist as a JSON array:
ansible-playbook <playbook_name> --extra-vars='{"varlist": [first,third]}'
Prior to that, you might also want a task that checks that each item in varlist is also in all_items:
- name: Ensure passed variables are in all_items
fail:
msg: "{{ item }} not in all_items list"
when: item not in all_items
with_items: "{{ varlist | default(all_items) }}"

The question is quite old, but what about:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ( a | default({})).get('nested', {}).get('var','bar') }}"
It looks less cumbersome to me...

#Roman Kruglov mentioned json_query. It's perfect for nested queries.
An example of json_query sample playbook for existing and non-existing value:
- hosts: localhost
gather_facts: False
vars:
level1:
level2:
level3:
level4: "LEVEL4"
tasks:
- name: Print on existing level4
debug:
var: level1 | json_query('level2.level3.level4') # prints 'LEVEL4'
when: level1 | json_query('level2.level3.level4')
- name: Skip on inexistent level5
debug:
var: level1 | json_query('level2.level3.level4.level5') # skipped
when: level1 | json_query('level2.level3.level4.level5')

Related

How i can define conditinal value for a parameter in task defintion

I'm a newbe in Ansible ant need a help for task definition.
My current task:
- name: Add the user
user:
name: "{{ user_name }}"
comment: "{{ user_name }} -User"
groups: "{{ user_groups | default([users]) | join(',') }}"
append: False # not required. If C(yes), add the user to the groups specified in C(groups). If C(no), user will only be added to the groups specified in C(groups), removing them from all other groups. Mutually exclusive with C(local)
password: "{{ lookup('file', '{{ user_data_folder }}/{{ user_name }}/user_password.txt') | password_hash('sha512') }}" # not required. Optionally set the user's password to this crypted value. On macOS systems, this value has to be cleartext. Beware of security issues. To create a disabled account on Linux systems, set this to C('!') or C('*'). To create a disabled account on OpenBSD, set this to C('*************'). See U(https://docs.ansible.com/ansible/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module) for details on various ways to generate these password values.
state: present # not required. choices: absent;present. Whether the account should exist or not, taking action if the state is different from what is stated.
update_password: on_create # not required. choices: always;on_create. C(always) will upde passwords if they differ. C(on_create) will only set the password for newly created users.
shell: "{{ user_shell | default('/bin/bash') }}"
Works very well.
Now, I'd like to change the "groups"-Item definition.
The expectation is:
if exists the file
"{{ lookup('file', '{{ user_data_folder }}/{{ user_name }}/user_groups.txt')
take the definition
if the variable user_groups is set, so overwrite the file content
else:
use default([users])
I can describe this in other way
INIT:
USER_GROUPS_FOR_TASK=[users]
IF Fileexists THEN
get the content
USER_GROUPS_FOR_TASK = content of the file
ENDIF
IF VAR(user_groups) exists
USER_GROUPS_FOR_TASK = VAR(user_groups)
ENDIF
I tried with:
a) groups: "{{ lookup('file', '{{ user_data_folder }}/{{ user_name }}/user_groups.txt', errors='ignore' ) | user_groups | default([users]) | join(',') }}"
b) groups: "{{ lookup('file', '{{ user_data_folder }}/{{ user_name }}/user_groups.txt', errors='ignore' ) | default([users]) | join(',') }}"
but without any luck if the file '{{ user_data_folder }}/{{ user_name }}/user_groups.txt'
does not exist.
Can somebody help me?
i tried with:
a) groups: "{{ lookup('file', '{{ user_data_folder }}/{{ user_name }}/user_groups.txt', errors='ignore' ) | user_groups | default([users]) | join(',') }}"
b) groups: "{{ lookup('file', '{{ user_data_folder }}/{{ user_name }}/user_groups.txt', errors='ignore' ) | default([users]) | join(',') }}"
The expectation is:
if exists the file
"{{ lookup('file', '{{ user_data_folder }}/{{ user_name }}/user_groups.txt')
take the definition
if the variable user_groups is set, so overwrite the file content
else:
use default([users])
I can describe this in other way
INIT:
USER_GROUPS_FOR_TASK=[users]
IF Fileexists THEN
get the content
USER_GROUPS_FOR_TASK = content of the file
ENDIF
IF VAR(user_groups) exists
USER_GROUPS_FOR_TASK = VAR(user_groups)
ENDIF
I'm almost positive you'll have to either use a set_fact: (so you can tell ansible you're ok with the missing file) or first use a lookup("fileglob" in order to check for the presence of the file and skip trying to read it if it's missing. The |default is exclusively for unset or empty vars, and not a general try: except: mechanism in ansible. The choice of which to use is mostly whether any other process in your playbook would benefit from knowing that file exists or not
The set_fact: approach also has two "styles" in it, with and without the explicit ignore_errors:, depending on the style of your team
- name: maybe get groups from file
set_fact:
file_groups: '{{ lookup("file", ...) }}'
ignore_errors: true
- name: or, the implicit ignore version
set_fact:
file_groups: '{{ item }}'
# this one will still whine a warning,
# but has an implicit ignore_errors: true on it
with_file: '{{ user_data_folder }}/{{ user_name }}/user_groups.txt'
- debug:
msg: groups is '{{ file_groups | default([users]) | join(",") }}'

Can variables be use as ansible filter parameters

I am creating a list of URLs (just strings really).
I want to select the one that contains the string in a ansible variable 'blog'.
Can't seem to code the select parameter correctly.
Is this possible?
- name: get blog_urls
shell:
cmd: wp site list --field=url
chdir: "{{ blog_docroot }}"
register: blog_urls
- name: show blog value
debug:
msg: "{{ blog }}"
- name: select the correct url
set_fact:
url: "{{ blog_urls.stdout_lines | select('contains', blog ) }}"
blog_url conatins:
"http://jackson2.sjfc.edu/",
"https://jackson2.sjfc.edu/oit/",
"https://jackson2.sjfc.edu/aaforms/",
"https://jackson2.sjfc.edu/admissions/",
"https://jackson2.sjfc.edu/arts-sciences/",
"https://jackson2.sjfc.edu/pharmacy/",
"https://jackson2.sjfc.edu/grants/",
"https://jackson2.sjfc.edu/alumni/",
"https://jackson2.sjfc.edu/wson/",
"https://jackson2.sjfc.edu/education/",
"https://jackson2.sjfc.edu/images/",
"https://jackson2.sjfc.edu/oit-test/",
"https://jackson2.sjfc.edu/provost/",
"https://jackson2.sjfc.edu/registrar/",
"https://jackson2.sjfc.edu/faculty-committees/",
"https://jackson2.sjfc.edu/faculty-files/",
"https://jackson2.sjfc.edu/avdownload/",
"https://jackson2.sjfc.edu/mac/",
"https://jackson2.sjfc.edu/giddnfellowship/",
"https://jackson2.sjfc.edu/dnp-mentors/"
blog would contain 'aaforms' or 'oit-test' or 'provost'...
If I hard code the desired value of blog (see line below) it works:
- name: select the correct url
set_fact:
url: "{{ blog_urls.stdout_lines | select('contains', 'aaforms' ) }}"
But of course I want to pass the second parameter of the 'select' filter as a ansible variable.
Your example in question is an almost working one. The select filter will return an a list based on the match criteria.
For example, let's say blog variable is aaforms:
vars:
blog: aaforms
tasks:
- set_fact:
my_blogs: "{{ blog_urls.stdout_lines | select('contains', blog) | list }}"
- debug:
var: my_blogs
This gives the URLs matched by the blog variable (in this case one).
"my_blogs": [
"https://jackson2.sjfc.edu/aaforms/"
]

Use a var in a var in Ansible (lookup) - aws_ssm plugin

I'm trying to use a var in a var declaration on Ansible (2.7.10)
I'm using aws_ssm lookup plugin (https://docs.ansible.com/ansible/latest/plugins/lookup/aws_ssm.html)
Working example (hardcoded values):
var: "{{ lookup('aws_ssm', '/path/server00', region='eu-west-3') }}"
I want to use variables for the server name and the AWS region, but all my tentatives went on errors.
What I've tried so far:
var: "{{ lookup('aws_ssm', '/path/{{ server }}', region={{ region }}) }}"
var: "{{ lookup('aws_ssm', '/path/{{ server }}', region= + region) }}"
- name: xxx
debug: msg="{{ lookup('aws_ssm', '/path/{{ server }}', region='{{ region }}' ) }}"
register: var
Without any success yet, thanks for your help,
You never nest {{...}} template expressions. If you're already inside a template expression, you can just refer to variables by name. For example:
var: "{{ lookup('aws_ssm', '/path/' + server, region=region) }}"
(This assumes that the variables server and region are defined.)
You can also take advantage of Python string formatting syntax. The following will all give you the same result:
'/path/' + server
'/path/%s' % (server)
'/path/{}'.format(server)
And instead of + you can use the Jinja ~ concatenation operator, which acts sort of like + but forces arguments to be strings. So while this is an error:
'some string' + 1
This will result in the text some string1:
'some string' ~ 1

Loop over variable and concatenate with string

Suppose I have an array in ansible:
vars:
loopme: ['somesing', 'someothersing']
concatenateme: 'constant'
How do i iterate over the list and concat value from list with the variable concatenateme?
So I get somesingconstant and someothersingconstant and put the result into a field in the task? Perhaps with jinja?
You can use map to apply regex_replace filter to every element of your list and replace "end of string" ($) with your constant:
- hosts: localhost
gather_facts: no
vars:
loopme: ['somesing', 'someothersing']
concatenateme: 'constant'
tasks:
- debug:
msg: "{{ loopme | map('regex_replace','$',concatenateme) | list }}"

Ansible variable list span

When adding a variable list in Ansible how would one achieve a span of similar values? For instance "000-100" - in an Ansible hosts file this can be done by listing like so, "hostname-[a:v].com". Would this process be the similar in a variable list?
My use case is to provision many VM's within oVirt in a single go without having to make a line by line list.
---
- name: Create VM based on template
hosts: ovirt-engine
become: yes
become_method: sudo
vars:
- temp: '{{temp_fedora25}}'
- iname:
- db-aa
- db-ab
- db-ac
tasks:
- name: Giving Birth to lil Baby VM's
ovirt:
user: '{{ovirt_usr}}'
password: '{{ovirt_pass}}'
url: '{{engine_url}}'
instance_name: "{{item}}"
instance_nic: ovirtmgmt
resource_type: template
image: '{{temp}}'
zone: superblade-a
disk_alloc: preallocated
with_items: "{{iname}}"
You can use sequence lookup:
- name: numeric
debug:
msg: "{{ item }}"
with_sequence: start=1 count=10 format=server-%0d
- name: characters from small 'a'
debug:
msg: "{{ item }}"
with_sequence: start=0x61 count=10 format=server-%c
- name: save for future use
set_fact:
my_seq: "{{ lookup('sequence','start={} count={} format={}{}'.format(beg,cnt,pref,fmt),wantlist=True) }}"
vars:
beg: 1
cnt: 10
pref: host-
fmt: '%0d'
You can skip set_fact and define my_seq in vars section, but if you use my_seq much, list generation will be done internally every time. With set_fact list is generated once.
With respect to the correct answer from Konstantin, I'm adding the full solution as per my case....
My goal is to be able to reuse the sequenced values as registered variables in order to pass the instance name to host name. This works so far but Im sure it can be streamlined by nesting variables perhaps?
---
- name: Create VM based on template
hosts: ovirt-engine
become: yes
become_method: sudo
vars:
- temp: '{{temp_fedora25}}'
- host_pre: db
- host_seq: a%c
- host_cnt: 3
- host_srt: 0x61
tasks:
- name: Giving Birth to lil Baby VM's
ovirt:
user: '{{ovirt_usr}}'
password: '{{ovirt_pass}}'
url: '{{engine_url}}'
instance_name: "{{item}}"
instance_nic: ovirtmgmt
resource_type: template
image: '{{temp}}'
zone: superblade-a
disk_alloc: preallocated
with_sequence: start="{{host_srt}}" count="{{host_cnt}}" format="{{host_pre}}-{{host_seq}}"