if else syntax in ansible playbook - syntax

I am new to Ansible and trying to understand what is wrong with my syntax.
My goal is that only one of the roles will be selected. I do not want to use 'when'.
Here is what I wrote (I am using Ansible v2.9.5):
- name: Install external DB for Cloudera Manager Server
hosts: db_server
roles:
- {{% if (databases_type == "postgresql") %} role: postgresql {% else %} {% endif %}
{% if (databases_type == "mysql") %} role: mariadb {% else %} {% endif %}
{% if (databases_type == "oracle") %} role: oracledb}
When I run the playbook I get a syntax error but it is not clear enough.
Thanks in advance.

A simple dictionary might be a cleaner option. For example
shell> cat playbook.yml
- name: Install external DB for Cloudera Manager Server
hosts: db_server
vars:
my_roles:
postgresql: postgresql
mysql: mariadb
oracle: oracledb
tasks:
- include_role:
name: "{{ my_roles[databases_type] }}"
Example
Let's create the roles
shell> cat roles/postgresql/tasks/main.yml
- debug:
var: role_name
shell> cat roles/mariadb/tasks/main.yml
- debug:
var: role_name
shell> cat roles/oracledb/tasks/main.yml
- debug:
var: role_name
Next, let's create an inventory with three servers, group_vars with default databases_type and host_vars with the variables for two hosts test_01 and test_02. The third host test_03 will use the variables from group_vars.
shell> cat hosts
[db_server]
test_01
test_02
test_03
shell> cat group_vars/db_server
databases_type: mysql
shell> cat host_vars/test_01
databases_type: postgresql
shell> cat host_vars/test_02
databases_type: oracle
Then the playbook gives (abridged)
shell> ansible-playbook -i hosts playbook.yml
PLAY [Install external DB for Cloudera Manager Server] *****************
TASK [include_role : {{ my_roles[databases_type] }}] *******************
TASK [postgresql : debug] **********************************************
ok: [test_01] =>
role_name: postgresql
TASK [oracledb : debug] ************************************************
ok: [test_02] =>
role_name: oracledb
TASK [mariadb : debug] *************************************************
ok: [test_03] =>
role_name: mariadb

I believe that this is what you are looking for. In the below example, fruit is the variable name. If fruit is equal to Apple then I like it else I do not like it. Let me know if you face any issue or you need more explanation on this.
If else syntax:
- name: "[ If Else Example ]"
command: "echo {{ 'I like it' if fruit == 'Apple' else 'I do not like it'}}"
register: if_reg
- debug:
msg: "{{ if_reg.stdout }}"

It seems you have double curly bracket:
- {{% if ...
Try removing it to make single curly bracket:
e.g.
- {% if ...
also last line could remove ending curly bracket and put endif:
{% if (databases_type == "oracle") %} role: oracledb {% endif %}

Related

ansible jinja template hostvars can not get ansible_host

I am trying to iterate over hostvars in an ansible playbook and it appears to be working just fine to several hostvars, except the one I need: ansible_host
If I try:
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host] }}
{% endfor %}
It outputs this for each server:
server server2 {'ansible_verbosity': 0,, '... 'ansible_check_mode': False, 'ansible_run_tags': [u'all'], 'ansible_skip_tags': [], u'ansible_host': u'10.192.11.17', 'ansible_version': {'major': 2, 'full': '2.9.11', 'string': '2.9.11', 'minor': 9, 'revision': 11}}
Which holds all vars.
Now If I try
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host]['verbosity'] }}
{% endfor %}
it will get me the ansible_verbosity just fine. But I can not find a way to get the ansible_host
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host]['ansible_host'] }}
{% endfor %}
I think the problem maybe related to the fact ansible_host is a unicode var ( u'ansible_host' ). I say that because the other 2 unicode vars fails with the same problem while all the others work just fine.
Any idea how to get that var?
Q: "hostvars can not get ansible_host"
A: ansible_host is not included in hostvars if it is not explicitly declared. ansible_host is a Connection variable. It is used, for example, as a parameter of the ssh connection plugin (see ansible-doc -t connection ssh). It is needed if inventory_hostname does not resolve. Very probably, any of your hosts don't have an ansible_host explicitly set. This is causing the problem. If ansible_host is not explicitly declared its value is set to inventory_hostname.
For example
shell > grep test_11 hosts
test_11
- hosts: test_11
tasks:
- debug:
var: hostvars[inventory_hostname]['ansible_host']
- debug:
var: ansible_host
gives
TASK [debug] *************************************************************
ok: [test_11] =>
hostvars[inventory_hostname]['ansible_host']: VARIABLE IS NOT DEFINED!
TASK [debug] *************************************************************
ok: [test_11] =>
ansible_host: test_11
If you declare ansible_host, for example, in the inventory
shell > grep test_11 hosts
test_11 ansible_host=10.1.0.61
the same playbook gives
TASK [debug] *************************************************************
ok: [test_11] =>
hostvars[inventory_hostname]['ansible_host']: 10.1.0.61
TASK [debug] *************************************************************
ok: [test_11] =>
ansible_host: 10.1.0.61
See Inventory aliases
Quoting Connection variables
ansible_host: The ip/name of the target host to use instead of inventory_hostname.
See also What's the difference between inventory_hostname and ansible_hostname
A quick solution to the problem might be defaulting to inventory_hostname, assuming ansible_host is not needed, i.e. inventory_hostname resolves. For example
{% for host in groups['all'] %}
server {{ host }} {{ hostvars[host]['ansible_host']|default(host) }}
{% endfor %}

Ansible lineinfile append

I have a hosts inventory file. That has
url.example.com hostnames='["hostname1.example.com", "hostname2.example.com"]'
Im trying to loop over it to add all the hostnames to /etc/hosts file
I am trying to use lineinfile, but how can I get it to append all of the hostnames to one specific line
line: "{{ ansible_default_ipv4.address }} {{ inventory_hostname }} {{ inventory_hostname_short}}" + append item here
with_items: "{{ hostnames }}"
state: present
How can i append all the items in the end of the line.
The iteration is not needed, I think. Is this what you're looking for?
line: "{{ ansible_default_ipv4.address }}
{{ inventory_hostname }}
{{ inventory_hostname_short}}
{{ hostnames|join(' ') }}"
For example, given the file
shell> cat hosts
10.1.0.27 localhost localhost
The playbook
shell> cat playbook.yml
- hosts: localhost
vars:
hostnames: [hostname1.example.com, hostname2.example.com]
tasks:
- lineinfile:
path: hosts
regex: '^{{ ansible_default_ipv4.address }}\s+(.*)$'
line: "{{ ansible_default_ipv4.address }}
{{ inventory_hostname }}
{{ inventory_hostname_short}}
{{ hostnames|join(' ') }}"
works as expected
shell> ansible-playbook playbook.yml -CD
TASK [lineinfile] ***************************************************************
--- before: hosts (content)
+++ after: hosts (content)
## -1 +1 ##
-10.1.0.27 localhost localhost
+10.1.0.27 localhost localhost hostname1.example.com hostname2.example.com
changed: [localhost]

generate a host list from ansible ini file

It is not possible to read this file with the ini plugin.
$ cat hosts
[webservers]
www[01:50].example.com
The play
- hosts: localhost
tasks:
- debug:
msg: "{{ item }}"
with_ini:
- '.* section=webservers file=hosts re=True'
gives
ok: [localhost] => (item=11].example.com) => {
"msg": "11].example.com"
}
Is it possible to generate a host list like this?
[webservers]
www01.example.com
www02.example.com
www03.example.com
www04.example.com
www05.example.com
www06.example.com
Q: "Is it possible to generate a host list like ...?"
A: Yes. Use template. For example
$ cat hosts
[webservers]
www[01:50].example.com
$ cat play.yml
- hosts: localhost
vars:
my_group: webservers
tasks:
- template:
src: hosts-template.j2
dest: /etc/ansible/hosts-webservers
$ cat hosts-template.j2
[{{ my_group }}]
{% for my_host in groups[my_group] %}
{{ my_host }}
{% endfor %}
Notes
Ranges in the inventory will be expanded.
If your ansible hosts inventory file is in a non standard location (i.e. not in /etc/ansible/hosts), you will have to load it when launching your playbook: ansible-playbook -i /path/to/inventory/hosts play.yml

Ansible template with list of hosts excluding current

I'm super fresh to ansible and creating a playbook that in one of the tasks should copy templated file and replace values in 2 lines. First line should have current hostname, and in second semicolon separated list of all other hosts (used in the play) - it will be different group
First line is super easy, as it's just:
localnode={{ inventory_hostname }}
but I'm having problem with exclusion in the second line. I'd like something similar to:
{% for host in groups.nodes -%} # but without inventory_hostname
othernodes={{ host }}{% if not loop.last %};{% endif %}
{%- endfor %}
Given the inventory of:
nodes:
hosts:
hosta:
hostb:
hostc:
hostd:
I'd like to get following output (example for hostd):
localnode=hostd
othernodes=hosta,hostb,hostc
I'll be very grateful for all hints on possible solution
Create the list of hosts without inventory_hostname and use it in the template
- set_fact:
list_other_hosts: "{{ groups.nodes|difference([inventory_hostname]) }}"
Simplify the template
othernodes={{ list_other_hosts|join(';') }}
As an example, the inventory
shell> cat hosts
test_jails:
hosts:
test_01:
test_02:
test_03:
and the play
- hosts: test_jails
tasks:
- set_fact:
list_other_hosts: "{{ groups.test_jails|
difference([inventory_hostname]) }}"
- debug:
msg: "{{ msg.split('\n') }}"
vars:
msg: |-
localnode={{ inventory_hostname }}
othernodes={{ list_other_hosts|join(';') }}
give
TASK [debug] ********************************************************
ok: [test_01] => {
"msg": [
"localnode=test_01",
"othernodes=test_02;test_03"
]
}
ok: [test_02] => {
"msg": [
"localnode=test_02",
"othernodes=test_01;test_03"
]
}
ok: [test_03] => {
"msg": [
"localnode=test_03",
"othernodes=test_01;test_02"
]
}

How to set an Ansible variable for all plays/hosts?

This question is NOT answered. Someone mentioned environment variables. Can you elaborate on this?
This seems like a simple problem, but not in ansible. It keeps coming up. Especially in error conditions. I need a global variable. One that I can set when processing one host play, then check at a later time with another host. In a nutshell, so I can branch later in the playbook, depending on the variable.
We have no control over custom software installation, but if it is installed, we have to put different software on other machines. To top it off, the installations vary, depending on the VM folder. My kingdom for a global var.
The scope of variables relates ONLY to the current ansible_hostname. Yes, we have group_vars/all.yml as globals, but we can't set them in a play. If I set a variable, no other host's play/task can see it. I understand the scope of variables, but I want to SET a global variable that can be read throughout all playbook plays.
The actual implementation is unimportant but variable access is (important).
My Question: Is there a way to set a variable that can be checked when running a different task on another host? Something like setGlobalSpaceVar(myvar, true)? I know there isn't any such method, but I'm looking for a work-around. Rephrasing: set a variable in one tasks for one host, then later in another task for another host, read that variable.
The only way I can think of is to change a file on the controller, but that seems bogus.
An example
The following relates to oracle backups and our local executable, but I'm keeping it generic. For below - Yes, I can do a run_once, but that won't answer my question. This variable access problem keeps coming up in different contexts.
I have 4 xyz servers. I have 2 programs that need to be executed, but only on 2 different machines. I don't know which. The settings may be change for different VM environments.
Our programOne is run on the server that has a drive E. I can find which server has drive E using ansible and do the play accordingly whenever I set a variable (driveE_machine). It only applies to that host. For that, the other 3 machines won't have driveE_machine set.
In a later play, I need to execute another program on ONLY one of the other 3 machines. That means I need to set a variable that can be read by the other 2 hosts that didn't run the 2nd program.
I'm not sure how to do it.
Inventory file:
[xyz]
serverxyz[1:4].private.mystuff
Playbook example:
---
- name: stackoverflow variable question
hosts: xyz
gather_facts: no
serial: 1
tasks:
- name: find out who has drive E
win_shell: dir e:\
register: adminPage
ignore_errors: true
# This sets a variable that can only be read for that host
- name: set fact driveE_machine when rc is 0
set_fact:
driveE_machine: "{{inventory_hostname}}"
when: adminPage.rc == 0
- name: run program 1
include: tasks/program1.yml
when: driveE_machine is defined
# program2.yml executes program2 and needs to set some kind of variable
# so this include can only be executed once for the other 3 machines
# (not one that has driveE_machine defined and ???
- name: run program 2
include: tasks/program2.yml
when: driveE_machine is undefined and ???
# please don't say run_once: true - that won't solve my variable access question
Is there a way to set a variable that can be checked when running a task on another host?
No sure what you actually want, but you can set a fact for every host in a play with a single looped task (some simulation of global variable):
playbook.yml
---
- hosts: mytest
gather_facts: no
vars:
tasks:
# Set myvar fact for every host in a play
- set_fact:
myvar: "{{ inventory_hostname }}"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
# Ensure that myvar is a name of the first host
- debug:
msg: "{{ myvar }}"
hosts
[mytest]
aaa ansible_connection=local
bbb ansible_connection=local
ccc ansible_connection=local
result
PLAY [mytest] ******************
META: ran handlers
TASK [set_fact] ******************
ok: [aaa -> aaa] => (item=aaa) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "aaa"}
ok: [aaa -> bbb] => (item=bbb) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "bbb"}
ok: [aaa -> ccc] => (item=ccc) => {"ansible_facts": {"myvar": "aaa"}, "ansible_facts_cacheable": false, "changed": false, "failed": false, "item": "ccc"}
TASK [debug] ******************
ok: [aaa] => {
"msg": "aaa"
}
ok: [bbb] => {
"msg": "aaa"
}
ok: [ccc] => {
"msg": "aaa"
}
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#fact-caching
As shown elsewhere in the docs, it is possible for one server to reference variables about another, like so:
{{ hostvars['asdf.example.com']['ansible_os_family'] }}
This even applies to variables set dynamically in playbooks.
This answer doesn't pre-suppose your hostnames, nor how many hosts have a "drive E:". It will select the first one that is reachable that also has a "drive E:". I have no windows boxes, so I fake it with a random coin toss for whether a host does or doesn't; you can of course use your original win_shell task, which I've commented out.
---
- hosts: all
gather_facts: no
# serial: 1
tasks:
# - name: find out who has drive E
# win_shell: dir e:\
# register: adminPage
# ignore_errors: true
- name: "Fake finding hosts with drive E:."
# I don't have hosts with "drive E:", so fake it.
shell: |
if [ $RANDOM -gt 10000 ] ; then
exit 1
else
exit 0
fi
args:
executable: /bin/bash
register: adminPage
failed_when: false
ignore_errors: true
- name: "Dict of hosts with E: drives."
run_once: yes
set_fact:
driveE_status: "{{ dict(ansible_play_hosts_all |
zip(ansible_play_hosts_all |
map('extract', hostvars, ['adminPage', 'rc'] ) | list
))
}}"
- name: "List of hosts with E: drives."
run_once: yes
set_fact:
driveE_havers: "{%- set foo=[] -%}
{%- for dE_s in driveE_status -%}
{%- if driveE_status[dE_s] == 0 -%}
{%- set _ = foo.append( dE_s ) -%}
{%- endif -%}
{%- endfor -%}{{ foo|list }}"
- name: "First host with an E: drive."
run_once: yes
set_fact:
driveE_first: "{%- set foo=[] -%}
{%- for dE_s in driveE_status -%}
{%- if driveE_status[dE_s] == 0 -%}
{%- set _ = foo.append( dE_s ) -%}
{%- endif -%}
{%- endfor -%}{{ foo|list|first }}"
- name: Show me.
run_once: yes
debug:
msg:
- "driveE_status: {{ driveE_status }}"
- "driveE_havers: {{ driveE_havers }}"
- "driveE_first: {{ driveE_first }}"
It's working for me, you can directly use register var no need to use set_fact.
---
- hosts: manager
tasks:
- name: dir name
shell: cd /tmp && pwd
register: homedir
when: "'manager' in group_names"
- hosts: all
tasks:
- name: create a test file
shell: touch "{{hostvars['manager.example.io'['homedir'].stdout}}/t1.txt"
i have used "set_fact" module in the ansible tasks prompt menu, which is helped to pass user input into all the inventory hosts.
#MONITORING PLAYBOOK
- hosts: all
gather_facts: yes
become: yes
tasks:
- pause:
prompt: "\n\nWhich monitoring you want to perform?\n\n--------------------------------------\n\n1. Memory Utilization:\n2. CPU Utilization:\n3. Filesystem Utili
zation:\n4. Exist from Playbook: \n5. Fetch Nmon Report \n \nPlease select option: \n--------------------------------------\n"
register: menu
- set_fact:
option: "{{ menu.user_input }}"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
#1 Memory Monitoring
- pause:
prompt: "\n-------------------------------------- \n Enter monitoring Incident Number = "
register: incident_number_result
when: option == "1"
- name: Standardize incident_number variable
set_fact:
incident_number: "{{ incident_number_result.user_input }}"
when: option == "1"
delegate_to: "{{ item }}"
with_items: "{{ play_hosts }}"
run_once: yes
ansible playbook result is
[ansibusr#ansiblemaster monitoring]$ ansible-playbook monitoring.yml
PLAY [all] **************************************************************************
TASK [Gathering Facts] **************************************************************************
ok: [node2]
ok: [node1]
[pause]
Which monitoring you want to perform?
--------------------------------------
1. Memory Utilization:
2. CPU Utilization:
3. Filesystem Utilization:
4. Exist from Playbook:
5. Fetch Nmon Report
Please select option:
--------------------------------------
:
TASK [pause] **************************************************************************
ok: [node1]
TASK [set_fact] **************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)
[pause]
--------------------------------------
Enter monitoring Incident Number = :
INC123456
TASK [pause] **************************************************************************
ok: [node1]
TASK [Standardize incident_number variable] ****************************************************************************************************************************
ok: [node1 -> node1] => (item=node1)
ok: [node1 -> node2] => (item=node2)

Resources