How to Make Ansible variable mandatory - ansible

I'm looking for the way how to make Ansible to analyze playbooks for required and mandatory variables before run playbook's execution like:
- name: create remote app directory hierarchy
file:
path: "/opt/{{ app_name | required }}/"
state: directory
owner: "{{ app_user | required }}"
group: "{{ app_user_group | required }}"
...
and rise error message if variable is undefined, like:
please set "app_name" variable before run (file XXX/main.yml:99)

As Arbab Nazar mentioned, you can use {{ variable | mandatory }} (see Forcing variables to be defined) inside Ansible task.
But I think it looks nicer to add this as first task, it checks is app_name, app_user and app_user_group exist:
- name: 'Check mandatory variables are defined'
assert:
that:
- app_name is defined
- app_user is defined
- app_user_group is defined

You can use this:
{{ variable | mandatory }}

Usually inside a role I perform checking input variables like in the example:
- name: "Verify that required string variables are defined"
assert:
that:
- "{{ ahs_var }} is defined"
- "{{ ahs_var }} | length > 0"
- "{{ ahs_var }} != None"
fail_msg: "{{ ahs_var }} needs to be set for the role to work"
success_msg: "Required variable {{ ahs_var }} is defined"
loop_control:
loop_var: ahs_var
with_items:
- ahs_item1
- ahs_item2
- ahs_item3
by the way there are some tricks:
Don't use global variables inside a role.
If you want use global variables define the role specific variable & set global variable to it i.e. some_role_name__db_port: "{{ db_port | default(5432) }}".
It makes sense to use role name as the prefix for variable. it helps to understand the source inventory easier.
Your role might be looped some how, so it makes sense to override the default item.

One way to check if mandatory variables are defined is:
- fail:
msg: "Variable '{{ item }}' is not defined"
when: item not in vars
with_items:
- app_nam
- var2

There are 2 approaches:
Specify |mandatory filter
Beware dictionaries - if a key with mandatory value fails to eval, you may have a hard time uh understanding which is it.
Use assert module

If using a role-based architecture, check out the docs on role argument validation.
It allows for you to specify a roles/<role_name>/meta/argument_specs.yml file which allows you to make variables required. Here is an example taken from their sample specification:
# roles/myapp/meta/argument_specs.yml
---
argument_specs:
# roles/myapp/tasks/main.yml entry point
main:
short_description: The main entry point for the myapp role.
options:
myapp_int:
type: "int"
required: false
default: 42
description: "The integer value, defaulting to 42."
myapp_str:
type: "str"
required: true
description: "The string value"
# roles/myapp/tasks/alternate.yml entry point
alternate:
short_description: The alternate entry point for the myapp role.
options:
myapp_int:
type: "int"
required: false
default: 1024
description: "The integer value, defaulting to 1024."

Related

How to restrict scope of set_fact variable in ansible

below is my ansible playbook for validation of objects - I am first using validateip role and under that executing login,validation and then logout tasks.
- name: validate object
vars:
mserver: [1.1.1.1,2.2.2.2]
domain: [3.3.3.3,4.4.4.4]
tasks:
- include_role:
name: validateip
when: object_type == "ip"
with_together:
- "{{ mserver_hostname }}"
- "{{ domain }}"
- name: Checking Network objects
uri:
url: "https://{{item.0}}/web_api/show-objects"
validate_certs: False
method: POST
headers:
x-chkp-sid: "{{ login.json.sid }}"
body:
type: host
filter: "{{ip}}"
ip-only: true
body_format: json
register: check_host_result
when: item.0 != ""
- debug:
var: check_host_result
- name: Checking if Network Object exists
set_fact:
item_ip_exists: true
obj_name: "{{ item2['name'] }}"
loop: "{{ check_host_result.json.objects }}"
loop_control:
loop_var: item2
when:
- item2['ipv4-address'] is defined and item2['ipv4-address'] == ip
- debug:
msg: "Network Object exists with name [{{obj_name}}]"
when: item_ip_exists is defined
- debug:
msg: " Network Object ({{ip}}) will be created"
when: item_ip_exists is not defined
I am facing issue for set_fact variable like obj_name and item_ip_exists
so when loop runs on first item and if object is present so it set both the variable (obj_name and item_ip_exists ) and print the correct debug messages.
but when 2nd item executed and there if object is not present so it is printing the wrong debug message due to the set_fact variables( obj_name and item_ip_exists) which has already the value from the first items execution
so how i can restrict the scope of set_fact variables ( obj_name and item_ip_exists ) so when second item execute the variables take the value from there not from previously set_fact values.
I am totally stuck here.
Please help me. Thanks in advance.
Alas, the variable that is set by set_fact stays defined (for the same host) and keeps the value that had been assigned on the previous loop's pass.
As a solution (which is pretty ugly, to be frank) you may want to redefine the variable's value in the very beginning of the loop and set it to None by something like this:
- set_fact:
my_variable:
Then you'll have to check if it's equal or unequal to None. Alas, no one could undefine a previously defined variable in Ansible, that's why your condition won't be my_variable is undefined, but will be my_variable | default(None) == None.
Ugly, right.

Ansible variable precedence - Reset overriden variable value back to the group_vars value

How Can I reset an overriden variable in a playbook back to the value that is defined in group_vars for that variable
So for example, If I have variable name: "Hello from group_vars" defined in group vars, but during the executin playbook I override it base on some condition with set fact
set_fact:
name: "Overriden somewhere in playbook only for the next task"
when: << some condition that is ture >>
name: Next task
debug:
msg: "Name is {{ name }}" # This will show value from the set_fact
but for the next task I would want to reset the name to the value to the one that has beeen set in group_vars while still in the same playbook execution
set_fact:
name: << How to reset it to group_vars value logic >>
Name: Show the value
debug:
msg: "Nanme is {{ namne }}" # This now should show the value set in group_vars "Hello from group vars"
Any ideas on how to achieve this. Thanks
Generally, once you set_fact for a host, you can't go back, unless you store a copy of the original value and set_fact again later, or re-gather the facts layer (by using a different play in your play book, for example).
If you can, use a specialized fact (and maybe defaults) to achieve a similar goal.
For example:
- name: conditionally set fact
set_fact:
special_name: "overridden value"
when: my_condition
- name: use fact or default
debug:
msg: "Name is {{ special_name | default(name)}}"
If you want to use your overridden value more frequently, you might use a second variable to handle the assignment:
- name: conditionally set fact
set_fact:
special_name: "overridden value"
when: my_condition
- name: use default globally
set_fact:
special_name: "{{ special_name | default(name) }}"
- name: use fact or default
debug:
msg: "Name is {{ special_name }}"
It's a little longer, but it gets you a value you can count on in multiple locations without having to put the default in multiple places.

What does conditional "when: var | d()" mean in Ansible 2.5

I am unable to source from Ansible documents a clear meaning of a conditional such as when: var | d(). Is someone able give a clear explanation?
E.g. Below works whether inputing extra-var value from cli or defaulting to local ENV variable value:
vars:
my_var: "{{ e_var | default(ansible_env.USER | default(False,true)) }}"
tasks:
- name: Conditional check
debug:
msg: "{{ my_var }}"
when: my_var | d()
But this fails:
vars:
my_var: "{{ e_var | default(ansible_env.USER | default(false,true)) }}"
tasks:
- name: Conditional check
debug:
msg: "{{ my_var }}"
when: my_var
What is when: my_var | d() exactly doing? How how does it interplay with the | default(false,true) part in the variable declaration?
d is an alias to the default filter. It is a Jinja2 filter, so head for the Jinja2 docs. They work the same:
default(value, default_value=u'', boolean=False)
[ ]
Aliases: d
Regarding the problem you are facing, it is because Ansible processes a condition consisting of only a variable name differently from a more complex expression (which is passed directly to Jinja2/Python) (the actual code starts here):
If the my_var variable has a value of user01, the conditional will try to find a value of user01 variable and fail because it doesn't exist.
If you just add a logical conjunction (which in common sense is redundant), Ansible will process the whole expression differently and it will work:
when: my_var and true
In your case using another default filter in the expression is also redundant, but it prevents Ansible from trying to resolve a "nested" variable value.

Ansible - Use default if a variable is not defined

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')
You can also use an if statement:
# Firewall manager: firewalld or ufw
firewall: "{{ 'firewalld' if ansible_os_family == 'RedHat' else 'ufw' }}"

Conditionally define variable in Ansible

I want to conditionally define a variable in an Ansible playbook like this:
my_var: "{{ 'foo' if my_condition}}"
I would like the variable to remain undefined if the condition does not resolve to true.
Ansible gives the following error if I try to execute the code:
fatal: [foo.local] => {'msg': 'AnsibleUndefinedVariable: One or more undefined
variables: the inline if-expression on line 1 evaluated
to false and no else section was defined.', 'failed': True}
Why is this an error anyway?
The complete case looks like this:
{role: foo, my_var: "foo"}
If my_var is defined, the role does something special. In some cases, I don't want the role to do this. I could use when: condition, but then I would have to copy the whole role block. I could also use an extra bool variable, but I would like a solution without having to change the "interface" to the role.
Any ideas?
You could use something like this:
my_var: "{{ 'foo' if my_condition else '' }}"
The 'else' will happen if condition not match, and in this case will set a empty value for the variable. I think this is a short, readable and elegant solution.
This code may help you to define a variable with condition.
- hosts: node1
gather_facts: yes
tasks:
- name: Check File
shell: ls -ld /etc/postfix/post-install
register: result
ignore_errors: yes
- name: Define Variable
set_fact:
exists: "{{ result.stdout }}"
when: result|success
- name: Display Variable
debug: msg="{{ exists }}"
ignore_errors: yes
So here the exists will display only if the condition is true.
My example, after https://stackoverflow.com/a/43403229/5025060:
vars:
sudoGroup: "{{ 'sudo' if ansible_distribution == 'Ubuntu' else 'wheel' }}"
Because of the different sudo conventions used by Ubuntu versus other platforms, here I am telling Ansible to set a variable named sudoGroup to sudo if the platform is Ubuntu, otherwise set it to wheel.
Later in my playbook, I combine the variable with Ansible's user module to add either sudo or wheel to an account's secondary groups depending on the OS Ansible is running on:
- name: Add or update bob account
user:
name: bob
uid: 3205
groups: "{{ sudoGroup }}"
append: yes
NOTES:
Double quotes around the {{ variable }} are required in the user: groups: definition above.
Once I define sudoGroup as above in my playbook's global vars: section, Ansible configures it at run time (based on ansible_distribution) for each target I define in my hosts: section.
I believe you're after the default(omit) filter. (Reference).
As per the example, mode will behave like it wasn't set at all for the first two items in the loop.
- name: touch files with an optional mode
file:
dest: "{{item.path}}"
state: touch
mode: "{{item.mode|default(omit)}}"
loop:
- path: /tmp/foo
- path: /tmp/bar
- path: /tmp/baz
mode: "0444"
This can be set as with bool:
- name: Conditional (true and false)
set_fact:
my_boolean_set_to_be: "{{ 'true' if my_var == 'foo' else 'false' }}"
- name: Display Variable
debug: msg="{{ my_boolean_set_to_be }}"
This can be set as for more conditionals like 'if-ifelse-else' statements:
- name: Conditional for 'my_var' (2 options and one default)
set_fact:
my_var_set_to_be: "{{ 'breakfast' if my_var == 'morning' else 'lunch' if my_var == 'afternoon' else 'dinner' }}"
- name: Display Variable
debug: msg="{{ my_var_set_to_be }}"

Resources