Ansible - set variable based on other item in list - ansible

I have configured this list as a variable:
my_list:
- name: whatever
url: http://example.com
- name: dummy
url: http://example.com
Can I configure a variable based on a name in my_list? Something I've tried, but fails because the variable doesn't loop over the list:
other_list:
- a_name: "{{ ip_list.name if my_list.name == 'whatever' }}"
a_url: "{{ ip_list.url if my_list.name == 'whatever' }}"
The expected outcome would be:
other_list:
- a_name: whatever
a_url: http://example.com
I don't want to run Ansible to set the variable during runtime, but I'd rather configure these variable in the variable files.

Related

Ansible set fact: evaluate a json path with a variable

I need to fetch data with conditions. This piece of code worked for me.
- name: retriving passwords...
uri:
...........
register: this
- name: Setting ssh secret fact...
set_fact:
ssh_secret: "{{ this.json.data.data.devuservalue }}"
when: ansible_user == "{{ user_dev }}"
The value of {{ user_dev }} is also devuservalue.
How can I remove this devuservalue hardcoded value from ssh_secret: line. Because I'll have to add several set facts with conditions. If I could add variable within variable.

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.

How to use a dictionary of registered ansible variables in vars?

I want to pass multiple variables to a task using vars. Currently, I am doing it like below
vars:
var1_name: "var1_value"
var2_name: "var2_value"
As the number of variables can grow in size, I'd rather prefer to pass the dictionary of variables to the task using vars. I have constructed a dictionary of variables like below
- name: set fact
hosts: localhost
tasks:
- set_fact:
variables: "{{ variables|default({}) | combine( {item.variable: item.value} ) }}"
with_items:
- variable: var1_name
value: "var1_value"
- variable: var2_name
value: "var2_name"
Dictionary looks something like this:
"variables": {
"var1_name": "var1_value",
"var2_name": "var2_value",
}
Now, I want to make variables in this dictionary available to roles executing on other hosts.
But, when I tried to pass dictionary to vars like below
vars: "{{ variables }}"
Ansible throws the error:
ERROR! Vars in a Play must be specified as a dictionary, or a list of dictionaries
How to pass a dictionary variable in vars?
After doing some searching through the Ansible source code, it looks like this is an issue even the developers of Ansible face. In some integration tests, there are specific tests that are commented out because of this same error.
https://github.com/ansible/ansible/blob/devel/test/integration/targets/include_import/role/test_include_role.yml#L96
## FIXME Currently failing with
## ERROR! Vars in a IncludeRole must be specified as a dictionary, or a list of dictionaries
# - name: Pass all variables in a variable to role
# include_role:
# name: role1
# tasks_from: vartest.yml
# vars: "{{ role_vars }}"
I also found out that this is the underlying function that is being called to include the variables:
https://github.com/ansible/ansible/blob/devel/lib/ansible/playbook/base.py#L681
def _load_vars(self, attr, ds):
'''
Vars in a play can be specified either as a dictionary directly, or
as a list of dictionaries. If the later, this method will turn the
list into a single dictionary.
'''
def _validate_variable_keys(ds):
for key in ds:
if not isidentifier(key):
raise TypeError("'%s' is not a valid variable name" % key)
try:
if isinstance(ds, dict):
_validate_variable_keys(ds)
return combine_vars(self.vars, ds)
elif isinstance(ds, list):
all_vars = self.vars
for item in ds:
if not isinstance(item, dict):
raise ValueError
_validate_variable_keys(item)
all_vars = combine_vars(all_vars, item)
return all_vars
elif ds is None:
return {}
else:
raise ValueError
except ValueError as e:
raise AnsibleParserError("Vars in a %s must be specified as a dictionary, or a list of dictionaries" % self.__class__.__name__,
obj=ds, orig_exc=e)
except TypeError as e:
raise AnsibleParserError("Invalid variable name in vars specified for %s: %s" % (self.__class__.__name__, e), obj=ds, orig_exc=e)
Seems as if since "{{ }}" is actually just a YAML string, Ansible doesn't recognize it as a dict, meaning that the vars attribute isn't being passed through the Jinja2 engine but instead being evaluated for what it actually is.
The only way to pass YAML objects around would be to use anchors, however this would require that the object be defined in whole instead of dynamically.
var: &_anchored_var
attr1: "test"
att2: "bar"
vars:
<<: *_anchored_var
I recommend using a structured way of managing variables like:
File myvars1.yml
myvars:
var1_name: "var1_value"
var2_name: "var2_value"
Then read the variables like
- name: Read all variables
block:
- name: Get All Variables
stat:
path: "{{item}}"
with_fileglob:
- "/myansiblehome/vars/common/myvars1.yml"
- "/myansiblehome/vars/common/myvars2.yml"
register: _variables_stat
- name: Include Variables when found
include_vars : "{{item.stat.path}}"
when : item.stat.exists
with_items : "{{_variables_stat.results}}"
no_log : true
delegate_to: localhost
become: false
After that, use like:
- name: My Running Module
mymodule:
myaction1: "{{ myvars.var1_name }}"
myaction2: "{{ myvars.var2_name }}"
Hope it helps

How to Make Ansible variable mandatory

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."

Resources