how does one combine conditionals into one "when" statement? - ansible

Ansible 2.10.x
I looked at How to define multiple when conditions in Ansible, and similar posts.
I'm trying to test if 2 different substring are in a variable. I've tried
default/main.yml
----------------
# Default path can be overridden in task
repo_url: "https://someUrl/development"
tasks/main.yml
--------------
- debug:
msg: "URL={{ repo_url }}"
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: ('"development" not in web_version') and
('"feature" not in web_version')
- debug:
msg: "URL={{ repo_url }}"
I use above task like this for example
$ ansible-playbook ... -e web_version=development_ myTask.yml
But I get
TASK [exa-web : debug] *************************************************
ok: [10.227.x.x] => {
"msg": "URL=https://someUrl/development"
}
TASK [exa-web : Override default path] *************************************************
ok: [10.227.x.x]
TASK [exa-web : debug] *************************************************
ok: [10.227.x.x] => {
"msg": "URL=https://someUrl/releases"
}
I don't expect the set_fact task to run, but it does; hence it overrides the default repo_url. So apparently I'm setting my when condition wrong.
I've also tried this to no avail.
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development_" not in web_version and
"feature_" not in web_version'
Essentially, I need the task to run if I execute my playbook like this
$ ansible-playbook ... -e web_version=1.4.44 myTask.yml
What's the correct syntax? TIA
UPDATE
Seems like when doesn't like ()? I just simplified the condition for now, and this works
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development" not in web_version'
but not this?
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: ('"development" not in web_version')
Really???

Your second attempt...
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development_" not in web_version and
"feature_" not in web_version'
...seems syntactically correct. In a playbook like this:
- hosts: localhost
gather_facts: false
vars:
repo_url: "https://someUrl/development"
tasks:
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: '"development_" not in web_version and
"feature_" not in web_version'
- debug:
msg: "URL={{ repo_url }}"
If we run it like this:
ansible-playbook -e web_version=development_ playbook.yaml
We see as output:
TASK [Override default path] ****************************************************************************
skipping: [localhost]
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": "URL=https://someUrl/development"
}
And if we run it like this:
ansible-playbook -e web_version=1.4.44 playbook.yaml
We see:
TASK [Override default path] ****************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************
ok: [localhost] => {
"msg": "URL=https://someUrl/releases"
}
That seems to do exactly what you want. Note that you're looking for the string development_ (with a trailing underscore) in your when statement, rather than development as in the first example, but that's an easy fix.
While your code works just fine, I find it helpful to use one of YAML's quote operators for writing multi-line when statements, since it avoids me getting confused by nested quotes in the expression:
- hosts: localhost
gather_facts: false
vars:
repo_url: "https://someUrl/development"
tasks:
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: >-
"development_" not in web_version and
"feature_" not in web_version
- debug:
msg: "URL={{ repo_url }}"
Re: your update, this doesn't work...
- name: Override default path
set_fact:
repo_url: "https://someUrl/releases"
when: ('"development" not in web_version')
...because of bad quoting. You are effectively writing:
when: ("a string")
And a non-empty string evaluates as true in a boolean expression. Always put the quotes at the beginning of the expression. E.g., this works just fine:
when: >-
("development" not in web_version)
As does the syntactically identical:
when: '("development" not in web_version)'

Related

How to pass additional environment variables to the imported ansible playbook

I have a main_play.yml Ansible playbook in which I am importing a reusable playbook a.yml.
main_play.yml
- import_playbook: "reusable_playbooks/a.yml"
a.yml
---
- name: my_playbook
hosts: "{{ HOSTS }}"
force_handlers: true
gather_facts: false
environment:
APP_DEFAULT_PORT: "{{ APP_DEFAULT_PORT }}"
tasks:
- name: Print Msg
debug:
msg: "hello"
My question is: how can I pass an additional environment variable from my main_playbook.yml playbook to my re-usable playbook a.yml (if needed) so that the environment variables become like
environment:
APP_DEFAULT_PORT: "{{ APP_DEFAULT_PORT }}"
SPRING_PROFILE: "{{ SPRING_PROFILE }}"
import_playbook is not really a module but a core feature. It does not allow for any parameter to be passed to the imported playbook. You can see this keyword as a simple commodity to facilitate playing several playbooks in a row exactly as if they were defined in the same file.
So your problem comes down to:
How do I pass additional environment variables to a play ?
Here is one solution with illustrations to use it with extra_vars or setting a fact from a previous play. This far from being exhaustive but I hope it will guide you to you own best solution.
To ease readability:
I used the APP_ prefix for all environment variables in my below examples and filtered only on those for the results.
I truncated the playbook output to the only relevant debug task
We can define the following reusable.yml playbook containing a single play
---
- hosts: localhost
gather_facts: false
vars:
default_env:
APP_DEFAULT_PORT: "{{ APP_DEFAULT_PORT | d(8080) }}"
environment: "{{ default_env | combine(additionnal_env | d({})) }}"
tasks:
- name: get the output on env for APP_* vars
shell: env | grep -i app_
register: env_cmd
changed_when: false
- name: debug the output of env
debug:
var: env_cmd.stdout_lines
We can directly run this playbook as-is which will give
$ ansible-playbook reusable.yml
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_DEFAULT_PORT=8080"
]
}
We can override the default port with
$ ansible-playbook reusable.yml -e APP_DEFAULT_PORT=1234
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_DEFAULT_PORT=1234"
]
}
We can pass additional environment variables with:
$ ansible-playbook reusable.yml -e '{"additionnal_env":{"APP_SPRING_PROFILE": "/toto/pipo"}}'
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_SPRING_PROFILE=/toto/pipo",
"APP_DEFAULT_PORT=8080"
]
}
Now if we want to do this from a parent playbook, we can set the needed variable for the given host in a previous play. We can define a parent.yml playbook:
---
- hosts: localhost
gather_facts: false
tasks:
- name: define additionnal env vars for this host to be used in next play(s)
set_fact:
additionnal_env:
APP_WHATEVER: some_value
APP_VERY_IMPORTANT: "ho yes!"
- import_playbook: reusable.yml
which will give:
$ ansible-playbook parent.yml
[... truncated ...]
TASK [define additionnal env vars for this host to be used in next play(s)] ************************************************************************************************************************
ok: [localhost]
[... truncated ...]
TASK [debug the output of env] ************************************************************************************************************************************************************************************
ok: [localhost] => {
"env_cmd.stdout_lines": [
"APP_WHATEVER=some_value",
"APP_VERY_IMPORTANT=ho yes!",
"APP_DEFAULT_PORT=8080"
]
}

Use dynamic variable name

I'm trying to get the value of ip_address from the following yaml that I'm including as variables on ansible:
common:
ntp:
- time.google.com
node1:
default_route: 10.128.0.1
dns:
- 10.128.0.2
hostname: ip-10-128-5-17
device_interface: ens5
cluster_interface: ens5
interfaces:
ens5:
ip_address: 10.128.5.17
nat_ip_address: 18.221.63.178
netmask: 255.255.240.0
version: 2
However the network interface (ens5 here) may be named something else, such as eth0. My ansible code is this:
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
vars:
msg: node1.interfaces.{{ cvp_device_interface }}.ip_address
debug:
msg: "{{ msg }}"
tags: debug_info
Running the code like this I can get the key's name:
TASK [Dump Interface Settings] *************************************************
│ ok: [18.221.63.178] => {
│ "msg": "node1.interfaces.ens5.ip_address"
│ }
But what I actually need is the value (i.e: something like {{ vars[msg] }}, which should expand into {{ node1.interfaces.ens5.ip_address }}). How can I accomplish this?
Use sqare brackets.
Example: a minimal playbook, which defines a variable called "device". This variable is used to return the active status of the device.
- hosts: localhost
connection: local
vars:
device: enx0050b60c19af
tasks:
- debug: var=device
- debug: var=hostvars.localhost.ansible_facts[device].active
Output:
$ ansible-playbook example.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [localhost] *******************************************************************
TASK [Gathering Facts] *************************************************************
ok: [localhost]
TASK [debug] ***********************************************************************
ok: [localhost] => {
"device": "enx0050b60c19af"
}
TASK [debug] ***********************************************************************
ok: [localhost] => {
"hostvars.localhost.ansible_facts[device].active": true
}
PLAY RECAP *************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0
see comment
- hosts: all
tasks:
- name: Read configuration from the yaml file
include_vars: "{{ config_yaml }}"
- name: Dump Interface Settings
debug:
msg: "{{ node1['interfaces'][cvp_device_interface]['ip_address'] }}"
debug:
msg: "{{ msg }}"
tags: debug_info

Ansible only run when subelement of variable exists

I have some variables defined like this:
x_php_versions_installed:
php70:
- php70-curl
- php70-xml
- php70-xmlrpc
- php70-zip
- pecl-memcached
php71:
- php71-curl
- php71-xml
- php71-xmlrpc
- php71-zip
php72:
- php72-curl
- php72-xml
- php72-xmlrpc
- php72-zip
- pecl-memcached
And I would like to check all of the vars (php70, php71, php72 and so on) has this variable: pecl-memcached and if one has, then run a command. My playbook looks like this:
- name: memcached pecl install
pear:
executable: '/usr/local/{{ item }}/bin/pecl'
name: 'pecl/memcached'
state: 'latest'
with_items: '{{ x_php_versions_installed | list }}'
when: 'item.pecl-memcached is defined'
this should call the /usr/local/php70/bin/pecl and /usr/local/php72/bin/pecl binary to install memcached. As soon as I remove the when condition, it works very well, but it will call every variable inside x_php_versions_installed not only where pecl-memcached is defined. So I need to fix the when condition in this case, but all of my tries are gives me an error.
If you want your when to check a list properly, you'll have to use the test operator in of Jinja:
- name: memcached pecl install
pear:
executable: '/usr/local/{{ item }}/bin/pecl'
name: 'pecl/memcached'
state: 'latest'
with_items: '{{ x_php_versions_installed | list }}'
when: "'pecl-memcached' in x_php_versions_installed[item]"
Given the playbook:
- hosts: localhost
gather_facts: no
vars:
x_php_versions_installed:
php70:
- php70-curl
- php70-xml
- php70-xmlrpc
- php70-zip
- pecl-memcached
php71:
- php71-curl
- php71-xml
- php71-xmlrpc
- php71-zip
php72:
- php72-curl
- php72-xml
- php72-xmlrpc
- php72-zip
- pecl-memcached
tasks:
- debug:
msg: "{{ item }}"
with_items: "{{ x_php_versions_installed | list }}"
when: "'pecl-memcached' in x_php_versions_installed[item]"
The recap would be:
TASK [debug] *******************************************************************
ok: [localhost] => (item=php70) => {
"msg": "php70"
}
skipping: [localhost] => (item=php71)
ok: [localhost] => (item=php72) => {
"msg": "php72"
}
Your subelement is a list not a dictionary, so accessing an element key like your are trying here, i.e.:
when: "x_php_versions_installed[item]['pecl-memcached'] is defined"
would work on a dictionary like this one:
x_php_versions_installed:
php70:
php70-curl:
php70-xml:
php70-xmlrpc:
php70-zip:
pecl-memcached:
# same goes for the other versions of PHP
Given the playbook:
- hosts: localhost
gather_facts: no
vars:
x_php_versions_installed:
php70:
php70-curl:
php70-xml:
php70-xmlrpc:
php70-zip:
pecl-memcached:
php71:
php71-curl:
php71-xml:
php71-xmlrpc:
php71-zip:
php72:
php72-curl:
php72-xml:
php72-xmlrpc:
php72-zip:
pecl-memcached:
tasks:
- debug:
msg: "{{ item }}"
with_items: "{{ x_php_versions_installed | list }}"
when: "x_php_versions_installed[item]['pecl-memcached'] is defined"
The recap would be:
TASK [debug] *******************************************************************
ok: [localhost] => (item=php70) => {
"msg": "php70"
}
skipping: [localhost] => (item=php71)
ok: [localhost] => (item=php72) => {
"msg": "php72"
}

Dynamic ansible host_vars

I have a use case for a play where the installation path for tomcat changes based on the hostname and the value of a variable. Not sure how to handle this. For example, I have the following inventory:
[servers]
server1
server2
server3
I have a global_var that specifies the type of platform for my install like so:
platform: training
My platform variable could be set to training, production, development
Based on the value of platform and the hostname, my tomcat installation path will be different, so I can't just have:
host_vars/server1.yml
tomcat_path: /somepath1
host_vars/server2.yml
tomcat_path: /somepath2
host_vars/server3.yml
tomcat_path: /somepath3
I'm looking to do something akin too:
server1.yml
tomcat_path: /somepath1
when: "{{ platform }} == training"
tomcat_path: /somepath2
when: "{{ platform }} == production"
tomcat_path: /somepath3
when: "{{ platform }} == development"
How do you handle such a case in ansible?
you could define all possible platform-tomcat_path options in a dictionary variable, and then select the desired combination with several ways.
check below example (with 3 different ways to reference the variables):
- hosts: localhost
gather_facts: false
vars:
tomcat_path: { training: /somepath1, production: /somepath2, development: /somepath3 }
your_selected_mode: development
tasks:
- name: print
debug:
var: tomcat_path.training
- name: print
debug:
var: tomcat_path['production']
- name: print
debug:
var: tomcat_path.{{your_selected_mode}}
you can use the your_selected_mode to select the mode you need.
output:
TASK [print] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"tomcat_path.training": "/somepath1"
}
TASK [print] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"tomcat_path['production']": "/somepath2"
}
TASK [print] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"tomcat_path.development": "/somepath3"
}
if needed to further customize it per host, you can have this tomcat_path variable declaration in the host_vars file and alter it to fit your needs.
You could use conditional imports but it requires you to have one variables file per host and platform:
- hosts: localhost
connection: local
vars_files:
- "vars/{{ inventory_hostname }}_{{ platform }}.yml"
tasks:
- name: echo path
debug: msg="Tomcat path is {{ tomcat_path }}"
You would need to define the variable files vars/server1_training.yml, vars/server2_training.yml, vars/server3_training.yml, vars/server1_production.yml, ....

Expressions are not evaluated in the nested ansible variables

$ ls play.d/roles/debug/*
play.d/roles/debug/tasks:
main.yml
play.d/roles/debug/vars:
main.yml
$ cat play.d/roles/debug/tasks/main.yml
- debug: msg="{{ name }}"
- debug: msg="{{ vars[name]['test_var'] }}"
- debug: msg="{{ vars['nested_var']['test_var'] }}"
- debug: msg="{{ test_var }}"
$ cat play.d/roles/debug/vars/main.yml
test_var: "{{ 'value-1' if cpu == 'x64' else 'value-2' }}"
nested_var:
test_var: "{{ 'value-1' if cpu == 'x64' else 'value-2' }}"
$ cat play.d/debug.yml
- hosts: all
gather_facts: yes
roles:
- debug
$ cat inv.d/inv
[all:vars]
cpu = 'x64'
[test-srv]
52.19.xxx.xxx
With this pretty straightforward setup I expect that Ansible should evaluate inline conditionals regardless of their position (top level or nested). However it seems, that nested variables become literal strings of the expressions:
# ansible-playbook -i inv.d/inv play.d/debug.yml -e name=nested_var -l test-srv
PLAY [all] ********************************************************************
GATHERING FACTS ***************************************************************
ok: [52.19.xxx.xxx]
TASK: [debug | debug msg="{{ name }}"] ****************************************
ok: [52.19.xxx.xxx] => {
"msg": "nested_var"
}
TASK: [debug | debug msg="{{ vars[name]['test_var'] }}"] **********************
ok: [52.19.xxx.xxx] => {
"msg": "{{'value-1' if cpu == 'x64' else 'value-2'}}"
}
TASK: [debug | debug msg="{{ vars['nested_var']['test_var'] }}"] **************
ok: [52.19.xxx.xxx] => {
"msg": "{{'value-1' if cpu == 'x64' else 'value-2'}}"
}
TASK: [debug | debug msg="{{ 'value-1' if cpu == 'x64' else 'value-2' }}"] ****
ok: [52.19.xxx.xxx] => {
"msg": "value-1"
}
PLAY RECAP ********************************************************************
52.19.xxx.xxx : ok=5 changed=0 unreachable=0 failed=0
Who's doing it wrong, me or Ansible? Any ideas?
# ansible --version
ansible 1.9.2
I'm not sure what do you try to achieve, but you exploit an undocumented way of accessing variables via vars hash.
And this vars hash is special in a way that Ansible template engine will not template it, but return it's value as is.
For Ansible 2.x it's described here.
So in case of {{ vars[name]['test_var'] }} it will resolve name->'nested_var' first but will not then resolve vars['nested_var']['test_var'] and return it as literal string.
If your variables are defined as host facts (inventory-host/group variables, dynamic facts set by set_fact), you can access host_vars magic variable like host_vars[inventory_hostname][dynamic_name]['subelement'] to access variable dynamically.
If your variables are play/role bound, like in your case, I can suggest to use root-hash with predefined name, like:
known_name:
nested_var: # this key name is known known in advance
subelement: "{{ 'value-1' if cpu == 'x64' else 'value-2' }}"
Here you can access dynamic element by known_name[dynamic_name]['subelement'].

Resources