Multiple 'and' operators in Ansible 'when' condition - ansible

This is the code that I am trying to run
- name: Read and Register Contents of .bash_profile
shell: grep -E 'AB_AG_HOME|AB_AG_LOCAL_ROOT|AB_AG_LOCAL_DIR|AB_AG_CONFIG_DIR|AB_AG_LOG_DIR' /home/username/.bash_profile
register: output
- debug:
msg: "{{ output.stdout_lines }}"
- name: Append AG environment variables in .bash_profile
shell: cat /home/{{ admin_user }}/tmp_bash.profile >> /home/{{ admin_user }}/.bash_profile
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
with_items: "{{ output.stdout_lines }}"
- name: Delete the temporary tmp_bash.profile"
file:
path: /home/{{ abinitio_admin_user }}/tmp_bash.profile
state: absent
when I run this code, all the values are repeated 4 times.
Is there anything that is missing?

By looping over the stdout_lines via
with_items: "{{ output.stdout_lines }}"`
the condition
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
would only be true if all strings are in one line (item) together.
You may have a look into the following minimal example
---
- hosts: localhost
become: false
gather_facts: false
vars:
result:
stdout_lines:
- "A1"
- "A2"
- "A3"
- "A4"
- "A5"
tasks:
- name: Debug one-line conditional
debug:
msg: "Not in stdout_lines"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in result.stdout_lines"
Whereby the first given example get skipped because of the condition, the second example
- name: Debug loop conditional
debug:
msg: "Not in {{ result.stdout_lines[ansible_loop.index0] }}"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in item"
loop: "{{ result.stdout_lines }}"
loop_control:
extended: true
will result into an output of
TASK [Debug loop conditional] ******
ok: [localhost] => (item=A1) =>
msg: Not in A1
ok: [localhost] => (item=A2) =>
msg: Not in A2
ok: [localhost] => (item=A3) =>
msg: Not in A3
ok: [localhost] => (item=A4) =>
msg: Not in A4
For further debugging you could also use the assert module – Asserts given expressions are true.

Related

How do you iterate/parse through a CSV file to use in Ansible playbook?

I am new to Ansible and don't have much coding experience in general. I am trying to figure out how to provision RHEL servers using the DellEMC OpenManage modules.
The first step to this is figuring out how to parse a CSV, that we are putting necessary information for the later templating. (IP, hostname, MAC, etc. ...) I can get it to print the data in general, but cant figure out how to parse/iterate through it.
CSV Sample
server_name,idrac_ip,idrac_user,idrac_pwd,idrac_nw,idrac_gw,idrac_mac,mgmt_ip,mgmt_nw,mgmt_gw,mgmt_mac,bond0_100_ip,bond0_200_ip,dns_1,bond0_100_nw,bond0_100_gw,bond0_100_vip,bond0_200_nw,bond0_200_gw,bond0_200_vip,os,fs,sio_type,mdm_name
test1,1.1.1.10,root,Password1234,1.1.1.0/24,1.1.1.1,00:00:00:00:00:01,1.1.1.142,1.1.62.0/24,1.1.2.1,98:03:9B:46:25:B3,1.1.1.22,1.1.1.21,1.1.1.26,1.1.61.15/24,1.1.61.1,1.1.61.29,1.1.66.0/24,1.1.66.1,1.1.1.29,RHEL 7.6,1,Master,MDM-1
Here is my playbook to print the info in general.
---
- name: Parse
hosts: localhost
connection: local
tasks:
- name: Load CSV Data into object
read_csv:
path: 'Test_Lab_Servers.csv'
fieldnames: server_name,idrac_ip,idrac_user,idrac_pwd,idrac_nw,idrac_gw,idrac_mac,mgmt_ip,mgmt_nw,mgmt_gw,mgmt_mac,bond0_100_ip,bond0_200_ip,dns_1,bond0_100_nw,bond0_100_gw,bond0_100_vip,bond0_200_nw,bond0_200_gw,bond0_200_vip,os,fs,sio_type,mdm_name
delimiter: ','
register: csv_output
delegate_to: localhost
- name: Print data
debug:
msg: "{{ csv_output }}"
Any advice?
According the read_csv module parameter documentation fieldnames are
needed if the CSV does not have a header
only. Therefore you could remove it to get a list object which might be easier to parse.
- name: Print data
debug:
msg: "{{ csv_output.list }}"
As already mentioned within the comments, to iterate over the list elements you may use loop.
- name: Print data
debug:
msg: "{{ item }}"
loop: "{{ csv_output.list }}"
With extended loop variables you will have a better control about the loop output.
- name: Print data
debug:
msg: "{{ item }}"
loop: "{{ csv_output.list }}"
loop_control:
extended: yes
label: "{{ ansible_loop.index0 }}"
Resulting into an output of
TASK [Print data] ***************
ok: [localhost] => (item=0) =>
msg:
bond0_100_gw: 1.1.61.1
bond0_100_ip: 1.1.1.22
bond0_100_nw: 1.1.61.15/24
bond0_100_vip: 1.1.61.29
bond0_200_gw: 1.1.66.1
bond0_200_ip: 1.1.1.21
bond0_200_nw: 1.1.66.0/24
bond0_200_vip: 1.1.1.29
dns_1: 1.1.1.26
fs: '1'
idrac_gw: 1.1.1.1
idrac_ip: 1.1.1.10
idrac_mac: 00:00:00:00:00:01
idrac_nw: 1.1.1.0/24
idrac_pwd: Password1234
idrac_user: root
mdm_name: MDM-1
mgmt_gw: 1.1.2.1
mgmt_ip: 1.1.1.142
mgmt_mac: 98:03:9B:46:25:B3
mgmt_nw: 1.1.62.0/24
os: RHEL 7.6
server_name: test1
sio_type: Master
...
Specific items (fields) like server_name you could get simply by using
msg: "{{ item.server_name }}"
resulting into an output of
TASK [Print data] ************
ok: [localhost] => (item=0) =>
msg: test1
ok: [localhost] => (item=1) =>
msg: test2
ok: [localhost] => (item=2) =>
msg: test3
...

ansible Iterate var only if var is not empty

I m trying to iterate over some variables in an ansible role. However, I want to ignore if the var is empty ex:ns3 from below code? I m trying when item length is greater than 0 but it seems not working? any ideas on how to do it?
---
- hosts: localhost
gather_facts: no
vars:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: print namespace loop
with_items:
- "{{ ns1 }}"
- "{{ ns2 }}"
- "{{ ns3 }}"
- "{{ ns4 }}"
include_role:
name: verify_pod_status
vars:
NAMESPACE: "{{ item }}"
when: "{{ item | lenght > 0 }}"
You may have a look into the documentation about Conditionals.
There are some typos in your when clause.
---
- hosts: localhost
gather_facts: false
vars:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: Print namespace loop
debug:
msg: "{{ item }}"
when: item | length > 0
with_items:
- "{{ ns1 }}"
- "{{ ns2 }}"
- "{{ ns3 }}"
- "{{ ns4 }}"
result into an output of
TASK [Print namespace loop] **************
ok: [localhost] => (item=adm_analytics) =>
msg: adm_analytics
ok: [localhost] => (item=adm_snap) =>
msg: adm_snap
ok: [localhost] => (item=adm_eck) =>
msg: adm_eck
when: condition is expanded by default. Fix the syntax
when: item|length > 0
Make your life easier and put the ns* variables into a dictionary. Then you can simply reference the values in the loop instead of listing the variables again. For example, the playbook
shell> cat playbook.yml
- hosts: localhost
gather_facts: no
vars:
ns:
ns1: adm_analytics
ns2: adm_snap
ns3: ""
ns4: adm_eck
tasks:
- name: print namespace loop
include_role:
name: verify_pod_status
loop: "{{ ns.values()|list }}"
vars:
NAMESPACE: "{{ item }}"
when: item|length > 0
and the role
shell> cat roles/verify_pod_status/tasks/main.yml
- debug:
var: NAMESPACE
give (abridged)
TASK [verify_pod_status : debug] ***********************************
skipping: [localhost] => (item=)
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_analytics
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_snap
TASK [verify_pod_status : debug] ***********************************
ok: [localhost] =>
NAMESPACE: adm_eck

Ansible: Stop a loop when the conditional is true the first time

Is there a way to stop a loop when the conditional is true the first time? In the example below, I want the value of student to be betty. But it currently will be set to brad. If I add "and student is undefined" to the when condition then it will work, but is there a better way? Thanks!
---
- hosts: localhost
vars:
marks:
- name: bob
grade: a
- name: betty
grade: c
- name: jenny
grade: d
- name: brad
grade: c
tasks:
- set_fact:
student: '{{ item.name }}'
loop: '{{ marks }}'
when: item.grade == 'c'
- debug:
var: student
Given the list
marks:
- {grade: a, name: bob}
- {grade: c, name: betty}
- {grade: d, name: jenny}
- {grade: c, name: brad}
Q: " ... when the conditional is true the first time ... I want the value of student to be betty."
A: Use filters instead of a loop. There are more options:
Select the first item that meets the condition
student: "{{ marks|
selectattr('grade', 'eq', 'c')|
map(attribute='name')|
first }}"
gives
student: betty
The filter json_query gives the same result
student: "{{ marks|
json_query('[?grade==`c`].name')|
first }}"
Example of how to find when the conditional is true for the first time in a loop
For example, let's simulate the use case of iterating a command until it succeeds. The task below iterates the list [1,2,3,4,5] and the command test the item is greater than 3. The loop will skip the rest of the list after the condition is met
- command: '[ "{{ item }}" -gt "3" ]'
loop: "{{ range(1, 5 + 1)|list }}"
register: result
ignore_errors: true
when: not condition
vars:
condition: "{{ (result|default({'rc': 1})).rc == 0 }}"
- debug:
msg: "{{ result.results|
json_query('[?rc==`0`].item')|
first }}"
gives (selected relevant part)
failed: [localhost] (item=3) => changed=true
...
item: 3
msg: non-zero return code
rc: 1
...
changed: [localhost] => (item=4)
skipping: [localhost] => (item=5)
...ignoring
TASK [debug] ****
ok: [localhost] =>
msg: '4'
See details in the thread Looping a task until it succeeds.

How to pair hosts with variables in ansible

I have an inventory like:
all:
children:
server_group1:
hosts:
host1:
server_group2:
children:
app1:
hosts:
host2:
host3:
app2:
hosts:
host4:
host5:
server_group3:
...
I have organized my server variables like so:
> cat group_vars/server_group2/app1
app1:
name1: value1
name2: value2
> cat group_vars/server_group2/app2
app2:
name1: value11
name2: value21
I am trying to name my dict after the group (thus making them unique) and access it in my playbook:
hosts: server_group2
tasks:
- name: check file
local_action: stat path=path/to/test/{{hostvars[0].name1}}
register: payld_txt
- name: conditional transfer
copy:
src: path/to/test/{{hostvars[0].name1}}
dest: /svr/path/{{hostvars[0].name2}}
when: payld_txt.stat.exists
I end up with this error:
The task includes an option with an undefined variable. The error was: 'name1' is undefined
Where am I going wrong?
Before you go any further, you need to fix your inventory which does not respect ansible's structure for yaml sources. A simple command as the following can give you some hints:
$ ansible -i inventories/test.yml all --list-hosts
[WARNING]: Skipping unexpected key (server_group1) in group (all), only "vars", "children" and "hosts" are valid
[WARNING]: Skipping unexpected key (server_group2) in group (all), only "vars", "children" and "hosts" are valid
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
hosts (0):
The correct syntax is:
---
all:
children:
server_group1:
hosts:
host1:
server_group2:
children:
app1:
hosts:
host2:
host3:
app2:
hosts:
host4:
host5:
Which now gives:
$ ansible -i inventories/test.yml all --list-hosts
hosts (5):
host1
host2
host3
host4
host5
``hostvars[0].name1`` The error was: 'name1' is undefined
Q: "Where am I going wrong?"
A: The variable name1 is item of the dictionary app1 or app2. It must be referenced app1.name1 or app2.name1. In addition to this,
hostvars is a dictionary not an array. hostvars[0] does not exist. An item of a dictionary must be referenced by a key. For example the play below
- hosts: server_group2
tasks:
- set_fact:
my_keys: "{{ hostvars.keys()|list }}"
run_once: true
- debug:
var: my_keys
run_once: true
- debug:
msg: "{{ hostvars[item].app1.name1 }}"
loop: "{{ my_keys }}"
when: "item in groups['app1']"
run_once: true
- debug:
msg: "{{ hostvars[item].app2.name1 }}"
loop: "{{ my_keys }}"
when: "item in groups['app2']"
run_once: true
gives
ok: [host5] =>
my_keys:
- host5
- host4
- host3
- host2
- host1
ok: [host5] => (item=host3) =>
msg: value1
ok: [host5] => (item=host2) =>
msg: value1
ok: [host5] => (item=host5) =>
msg: value11
ok: [host5] => (item=host4) =>
msg: value11
Optionally use json_query to create the list of the keys
- set_fact:
my_keys: "{{ hostvars|dict2items|json_query('[].key') }}"
run_once: true
The simplified version of the playbook
- hosts: server_group2
tasks:
- debug:
msg: "{{ hostvars[inventory_hostname].app1.name1 }}"
when: "inventory_hostname in groups['app1']"
- debug:
msg: "{{ hostvars[inventory_hostname].app2.name1 }}"
when: "inventory_hostname in groups['app2']"
gives
skipping: [host5]
skipping: [host4]
ok: [host3] =>
msg: value1
ok: [host2] =>
msg: value1
ok: [host5] =>
msg: value11
ok: [host4] =>
msg: value11
skipping: [host3]
skipping: [host2]
In fact, addressing hostvars[inventory_hostname] is not necessary. The simplified tasks below give the same output.
- debug:
msg: "{{ app1.name1 }}"
when: "inventory_hostname in groups['app1']"
- debug:
msg: "{{ app2.name1 }}"
when: "inventory_hostname in groups['app2']"

How do I test in a var matches against a list of substrings in ansible?

I am fairly new to ansible and I am trying to determine how to test is a variable passed to my playbook matches against a list of substrings.
I have tried something like the following. Looping through my list of badcmds and then testing whether it is in the variable passed.
vars:
badcmds:
- clear
- no
tasks:
- name: validate input
debug:
msg: " {{ item }}"
when: item in my_command
with_items: "{{ badcmds }}"
I am getting the following error:
"msg": "The conditional check 'item in my_command' failed.
The error was: Unexpected templating type error occurred on
({% if item in my_command %} True {% else %} False {% endif %}):
coercing to Unicode: need string or buffer, bool found
Many thanks.
one problem with your playbook is that - no is automatically translated to boolean false. you should use "no" to make Ansible consider the variable as a string. without quotes:
---
- hosts: localhost
connection: local
gather_facts: false
vars:
badcmds:
- clear
- no
my_command: clear
tasks:
- name: print variable
debug:
msg: "{{ item }}"
with_items:
- "{{ badcmds }}"
output:
TASK [print variable] ***********************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "clear"
}
ok: [localhost] => (item=None) => {
"msg": false
}
I guess you should enclose no in quotes, because this behavior was not your intention.
to make a loop and check if the variable matches any item from the badcmds list, you can use:
---
- hosts: localhost
connection: local
gather_facts: false
vars:
badcmds:
- "clear"
- "no"
tasks:
- name: validate input
debug:
msg: "{{ item }}"
when: item == my_command
with_items:
- "{{ badcmds }}"
hope it helps

Resources