How do I test in a var matches against a list of substrings in ansible? - 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

Related

Ansible - List of dictionaries

Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables

Multiple 'and' operators in Ansible 'when' condition

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.

In Ansible, how to query hostvars to get a specific value of a key from a list item based on the value of a different key?

EDIT-UPDATE:
I found a way to achieve what was trying to do, using the index_of plugin. The following code outputs what I need.
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- ansible.builtin.set_fact:
mac_address: "{{ hostvars[inventory_hostname]['interfaces'][int_idx|int]['mac_address'] }}"
vars:
int_name: 'PCI1.1'
int_idx: "{{ lookup('ansible.utils.index_of', hostvars[inventory_hostname]['interfaces'], 'eq', int_name, 'name') }}"
- debug:
var: mac_address
Output:
PLAY [CASPOSR1BDAT003] ***********************************************************************************************************************************************************************************************
TASK [ansible.builtin.set_fact] **************************************************************************************************************************************************************************************
ok: [CASPOSR1BDAT003]
TASK [debug] *********************************************************************************************************************************************************************************************************
ok: [CASPOSR1BDAT003] =>
mac_address: 20:67:7C:00:36:A0
What I am trying to do:
Use the Netbox dynamic inventory plugin (this works, brings back all the info I need)
Query hostvars for a particular host, and get the value of the MAC address for a particular interface called PCI1.1
What I have tried:
Converting the hostvars to JSON and using json_query: this hasn't worked, and having looked at some issues on GitHub, hostvars isn't a "normal" dictionary. I've logged a couple of issues anyway (https://github.com/ansible/ansible/issues/76289 and https://github.com/ansible-collections/community.general/issues/3706).
Use a sequence loop and conditional "when" to get the value - this sort of works when using the debug module, but still not just returning the value
What works:
I have tried the following, which outputs the mac_address variable as expected. The length of the list is found, and then the conditional matches the name. I do get an warning about using jinja2 templating delimiters but that's not the target of this question.
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- debug:
var: hostvars[inventory_hostname]['interfaces'][{{ item }}]['mac_address']
with_sequence: start=0 end="{{ end_at }}"
vars:
- end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
The result is:
TASK [debug] *************************************************************************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found:
hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
skipping: [CASPOSR1BDAT003] => (item=0)
skipping: [CASPOSR1BDAT003] => (item=1)
skipping: [CASPOSR1BDAT003] => (item=2)
skipping: [CASPOSR1BDAT003] => (item=3)
skipping: [CASPOSR1BDAT003] => (item=4)
ok: [CASPOSR1BDAT003] => (item=5) =>
ansible_loop_var: item
hostvars[inventory_hostname]['interfaces'][5]['mac_address']: 20:67:7C:00:36:A0
item: '5'
skipping: [CASPOSR1BDAT003] => (item=6)
skipping: [CASPOSR1BDAT003] => (item=7)
skipping: [CASPOSR1BDAT003] => (item=8)
skipping: [CASPOSR1BDAT003] => (item=9)
I'm trying to use set_fact to store this mac_address variable as I need to use it in a couple of different ways. However, I am unable to use set_fact on this (or any other hostvars data, it seems). For example, the following:
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item]['mac_address'] }}"
with_sequence: start=0 end="{{ end_at }}"
vars:
- end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
- debug:
var: interfaces
results in:
fatal: [CASPOSR1BDAT003]: FAILED! =>
msg: |-
The task includes an option with an undefined variable. The error was: 'list object' has no attribute '5'
The error appears to be in '/Users/kivlint/Documents/GitHub/vmware-automation/ansible/prepare-pxe.yml': line 19, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
# when: hostvars[inventory_hostname]['interfaces'][{{ item }}]['name'] == "PCI1.1"
- ansible.builtin.set_fact:
^ here
If I hard-code the number 5 in, it works fine:
TASK [ansible.builtin.set_fact] ******************************************************************************************************************
ok: [CASPOSR1BDAT003]
TASK [debug] *************************************************************************************************************************************
ok: [CASPOSR1BDAT003] =>
interfaces: 20:67:7C:00:36:A0
If I use '5' as a var for the task, it also works.
---
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
tasks:
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][int_index]['mac_address'] }}"
vars:
- int_index: 5
So I'm wondering, is this a "bug/feature" in how set_fact does or doesn't work with loops (meaning, the same loop worked fine with debug? Or do I need to re-think the approach and consider trying to use set_fact to set a variable with the index of the list (e.g. 5 in the above example)? Or something else?
There's a lot going on in your code, and achieving the result you want is simpler than you've made it.
Firstly, don't use hostvars[inventory_hostname]; plain variables are the ones belonging to the current host, and going through hostvars introduces some exciting opportunities for things to go wrong. hostvars is for accessing variables belonging to other hosts.
Secondly, using Jinja's built-in filtering capabilities avoids the need to worry about the index of the item that you want.
- hosts: CASPOSR1BDAT003
connection: local
gather_facts: no
become: false
vars:
int_name: PCI1.1
mac_address: "{{ interfaces | selectattr('name', 'eq', int_name) | map(attribute='mac_address') | first }}"
tasks:
- debug:
var: mac_address
there is a confusion between the [5] (6th item of a list) and ['5'] (a key named "5") ,
you see in your error: The error was: 'list object' has no attribute '5'.
with the module debug you have not error because [{{item}}] is replaced by [5] and not by ['5']. Its not the same thing with set_fact.
its the reason you have to use filter int to clarify the situation.
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item|int]['mac_address'] }}"
with_sequence: start=0 end="{{ end_at }}"
vars:
end_at: "{{ (hostvars[inventory_hostname]['interfaces'] | length) - 1 }}"
when: hostvars[inventory_hostname]['interfaces'][item|int]['name'] == "PCI1.1"
so i suggest you to use loop instead with_sequence:
- ansible.builtin.set_fact:
interfaces: "{{ hostvars[inventory_hostname]['interfaces'][item]['mac_address'] }}"
loop: "{{ range(0, end_at|int, 1)|list }}"
vars:
end_at: "{{ hostvars[inventory_hostname]['interfaces'] | length }}"
when: hostvars[inventory_hostname]['interfaces'][item]['name'] == "PCI1.1"
set_fact works with loops, but not in a way you expect.
This example constructs list with loop from lists of dicts:
- set_fact:
foo: '{{ foo|d([]) + [item.value] }}'
loop:
- value: 1
- value: 2
Basically, each execution of set_fact creates a fact. You may refer to the same fact in jinja expression for set_fact, but you can't expect it to automatically build lists or something like that.

Issue with using Omit option

I am trying configure a attribute only if my_int_http is defined else I dont want it. So I coded it like below:
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(omit) }}"
However my execution fail and when check the arguments passed by the code in actual configuration it shows like below:
"profiles": [
"my_example.internal_tcp",
"__omit_place_holder__ef8c5b99e9707c044ac07fda72fa950565f248a4"
So how to pass absolutely no value where it is passing __omit_place_holder_****?
Q: "How to pass absolutely no value where it is passing omit_place_holder ?"
A1: Some filters also work with omit as expected. For example, the play
- hosts: localhost
vars:
test:
- "{{ var1|default(false) }}"
- "{{ var1|default(omit) }}"
tasks:
- debug:
msg: "{{ {'a': item}|combine({'b': true}) }}"
loop: "{{ test }}"
gives
msg:
a: false
b: true
msg:
b: true
As a sidenote, default(omit) is defined type string
- debug:
msg: "{{ item is defined }}"
loop: "{{ test }}"
- debug:
msg: "{{ item|type_debug }}"
loop: "{{ test }}"
give
TASK [debug] *************************************************************
ok: [localhost] => (item=False) =>
msg: true
ok: [localhost] => (item=__omit_place_holder__6e56f2f992faa6e262507cb77410946ea57dc7ef) =>
msg: true
TASK [debug] *************************************************************
ok: [localhost] => (item=False) =>
msg: bool
ok: [localhost] => (item=__omit_place_holder__6e56f2f992faa6e262507cb77410946ea57dc7ef) =>
msg: str
A2: No value in Ansible is YAML null. Quoting:
This is typically converted into any native null-like value (e.g., undef in Perl, None in Python).
(Given my_int_L4=bob). If the variable my_int_http defaults to null instead of omit
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(null) }}"
the list profiles will be undefined
profiles: VARIABLE IS NOT DEFINED!
Use None instead
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(None) }}"
The variable my_int_http will default to an empty string
profiles:
- bob
- ''
See also section "YAML tags and Python types" in PyYAML Documentation.
You can try something like this,
profiles:
- "{{ my_int_L4 }}"
- "{{ my_int_http | default(None) }}"
This will give you an empty string. And you can add a check while iterating over the profiles.
Please have a look at this GitHub Issue to get more understanding.

iteration using with_items and register

Looking for help with a problem I've been struggling with for a few hours. I want to iterate over a list, run a command, register the output for each command and then iterate with debug over each unique registers {{ someregister }}.stdout
For example, the following code will spit out "msg": "1" and "msg": "2"
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- name: "first"
int: "1"
- name: "second"
int: "2"
tasks:
- name: Register output
command: "/bin/echo {{ item.int }}"
register: result
with_items: "{{ numbers }}"
- debug: msg={{ item.stdout }}
with_items: "{{ result.results }}"
If however, I try and capture the output of a command in a register variable that is named using with_list, I am having trouble accessing the list or the elements within it. For example, altering the code slightly to:
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- name: "first"
int: "1"
- name: "second"
int: "2"
tasks:
- name: Register output
command: "/bin/echo {{ item.int }}"
register: "{{ item.name }}"
with_items: "{{ numbers }}"
- debug: var={{ item.name.stdout }}
with_items: "{{ numbers }}"
Gives me:
TASK [debug]
> ******************************************************************* fatal: [localhost]: FAILED! => {"failed": true, "msg": "'unicode
> object' has no attribute 'stdout'"}
Is it not possible to dynamically name the register the output of a command which can then be called later on in the play? I would like each iteration of the command and its subsequent register name to be accessed uniquely, e.g, given the last example I would expect there to be variables registered called "first" and "second" but there aren't.
Taking away the with_items from the debug stanza, and just explicitly defining the var or message using first.stdout returns "undefined".
Ansible version is 2.0.2.0 on Centos 7_2.
Thanks in advance.
OK so I found a post on stackoverflow that helped me better understand what is going on here and how to access the elements in result.results.
The resultant code I ended up with was:
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- name: "first"
int: "1"
- name: "second"
int: "2"
tasks:
- name: Register output
command: "/bin/echo {{ item.int }}"
register: echo_out
with_items: "{{ numbers }}"
- debug: msg="item.item={{item.item.name}}, item.stdout={{item.stdout}}"
with_items: "{{ echo_out.results }}"
Which gave me the desired result:
"msg": "item.item=first, item.stdout=1"
"msg": "item.item=second, item.stdout=2"
I am not sure if I understand the question correctly, but maybe this can help:
- debug: msg="{{ item.stdout }}"
with_items: echo_out.results
Please note that Ansible will print each item and the msg both - so you need to look carefully for a line that looks like "msg": "2".

Resources