I've currently many of this tasks. The file module here is just an example.
- file:
path: "{{ datapath }}"
state: "directory"
when:
- "storage is not defined"
- file:
path: "{{ datapath }}"
state: "directory"
delegate_to: "{{ storage.host }}"
when:
- "storage is defined"
- "storage.host is defined"
Which either creates a directory on the inventory_host or on a different host, when the fact is defined.
I wonder, if it is possible to reduce the number of tasks here. Normally I would use the omit filter. But because I've several conditions, I'm not sure what kind of syntax to use here for delegate_to.
You can also use the omit special variable in an inline expression
- file:
path: "{{ datapath }}"
state: directory
delegate_to: "{{ storage.host if storage.host is defined else omit }}"
With this, and because you can chain inline-if's, then you could have multiple conditions that ends in an omit, e.g.
delegate_to: >-
{{
storage.host
if storage.host is defined
else 'localhost'
if for_localhost | default(false)
else omit
}}
Would be:
delegated to storage.host when defined
delegated to localhost when for_localhost is truthy
omitted, otherwise
I would like to omit the dns_nameservers variable from the following Openstack function if the value does not appear in the variable file:
os_subnet:
cloud: "{{ item.cloud }}"
state: present
validate_certs: no
no_gateway_ip: yes
dns_nameservers:
- "{{ item.dns | default(None) }}"
enable_dhcp: yes
name: "{{ item.subnet }}"
network_name: "{{ item.network }}"
cidr: "{{ item.cidr }}"
allocation_pool_start: "{{ item.allocation_pool_start }}"
allocation_pool_end: "{{ item.allocation_pool_end }}"
host_routes: "{{ item.host_routes | default(omit) }}"
with_items:
- "{{ subnets }}"
tags: subnets
Until now, I have tried to omit it with | default(omit) and | default(None), but it is not working. Is any filter that might help or any other way?
EDIT:
Variable file:
- cloud: tenant1
network: nw
subnet: nw_subnet
cidr: 172.12.17.64/26
dns:
- 8.8.8.8
- 8.8.8.9
allocation_pool_start: 172.12.17.68
allocation_pool_end: 172.12.17.70
host_routes:
- destination: 0.0.0.0/0
nexthop: 172.12.17.65
I am getting the following error:
Reason: '[u'8.8.8.8', u'8.8.8.9']' is not a valid
nameserver. '[u'8.8.8.8', u'8.8.8.9']' is not a valid
IP address.\", \"type\": \"HTTPBadRequest\", \"detail\": \"\"}}"}
You want to either pass a list with a single element or pass an omit keyword (placeholder object), which tells Ansible not to pass the whole parameter (dns_nameservers here) to the module:
dns_nameservers: "{{ [item.dns] if item.dns is defined else omit }}"
In your example, if item.dns was undefined, you passed a list with a single element being an omit placeholder. In such case the dns_nameservers parameter is defined (that list which is hardcoded in the code) and behaviour is undefined (likely depends on module).
Hi I am trying to get a task setup which is as below.
- name: Create a route53 record for RDS instance.
route53:
state: present
aws_access_key: "{{ aws_create_route53_record.access_key }}"
aws_secret_key: "{{ aws_create_route53_record.secret_key }}"
zone: "{{ aws_create_route53_record.zone }}"
hosted_zone_id: "{{ aws_create_route53_record.id }}"
type: "{{ aws_create_route53_record.type }}"
value: "{{ aws_create_route53_record.value }}"
record: "{{ aws_create_route53_record.record }}"
private_zone: "{{ aws_create_route53_record.private_zone }}"
ttl: 30
Now I know passing variable from group_vars/all.yml I can define aws_create_route53_record.value in group_vars/all.yml and use it however in certain cases there is possibility where I am going to use this task as role and want to pass aws_create_route53_record.value runtime from previous task to this one and use it in creation of route53 record. for e.g. creating a rds instance using ansible task and use rds endpoint as value for route53_record
Any suggestion would help a lot. Thanks
Note: I've checked rds module page for return values which I thought would use instantly to solve this however there is no provision for endpoint return in it.
I was able to solve my problem by using a variable in my task and initially assigning it the value of group_vars variable and putting a condition that if after assigning a value of group_vars variable, variable is still blank then take a value from last task. Below is the code snippet which works fine. You would need to register variable from previous task as endpoint_host
---
-
name: "Set facts of record value"
set_fact:
record_value: "{{ aws_create_route53_record.value }}"
-
name: "If record value not present, Look for endpoint-host variable"
set_fact:
record_value: "{{ endpoint_host }}"
when: "aws_create_route53_record.value == \"\""
-
name: "Create a route53 record for RDS instance."
route53:
aws_access_key: "{{ aws_create_route53_record.access_key }}"
aws_secret_key: "{{ aws_create_route53_record.secret_key }}"
hosted_zone_id: "{{ aws_create_route53_record.hosted_zone_id }}"
private_zone: "{{ aws_create_route53_record.private_zone }}"
record: "{{ aws_create_route53_record.record }}"
state: present
ttl: 30
type: "{{ aws_create_route53_record.type }}"
value: "{{ record_value }}"
zone: "{{ aws_create_route53_record.zone }}"
Hope this helps.
Thanks to other stackoverflow users, I have managed to pull some data out of a variable registered by the digital_ocean ansible module. I attempted to use loop_control to print only part of the huge variable that is registered. Here is an extract from the role:
- name: Add droplet
digital_ocean: >
{ some parameters }
with_dict: "{{ droplets_up }}"
register: my_droplet
- debug: msg="Droplet IP is {{ item.droplet.ip_address }}"
with_items: "{{ my_droplet.results }}"
loop_control:
label: "{{ item }}"
I'm obviously doing it wrong here, as it prints the whole variable as well as the debug message. I don't quite understand loop_control at this point, but does anyone know if it's possible to use it in this manner with this module?
debug action has result['_ansible_verbose_always'] = True, so it will always print full item, no matter what your label is (although label: "{{item}}" doesn't change anything, try label: "{{ item.droplet.ip_address }}").
If you just need to list all your IP addresses, use map filter and single debug statement:
- name: Print droplets IP
debug:
msg: "{{ my_droplet.results | map(attribute='droplet.ip_address') | list }}"
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' }}"