Ansible with_item | first returns no items? - loops

I have an array of JSON like below:
[
{
"item": {
"item": "SERVERNAME"
}
},
{
"item": {
"item": "SERVERNAME2"
}
}
]
On a previous API call I was using
with_items: "{{ cluster_server.results }}"
And was able to grab each server name via {{ item.item.item }}
BUT on this API call I only need the first result so using
with_items: "{{ cluster_server.results | first}}"
yet it returns this error:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'item'\n\nThe error appears to be in '/home/mycomp/Documents/ansible/build-auth.yml': line 177, column 6, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - copy:\n ^ here\n"}
What am I doing wrong here? Fairly new to Ansible.

You are overwriting item, which is your loop variable. Use another name for the loop variable:
- debug:
msg: "{{ server.item.item }}"
with_items:
- "{{ cluster_server.results | first }}"
loop_control:
loop_var: server

Related

Ansible loop with array

Could someone let me know how we can create a code as below?
- name: TEST1
set_fact:
list_a: "{{ list_a + [item.json.SearchResult.resources] }}"
with_items:
- "{{ source_list.results[0] }}"
- "{{ source_list.results[1] }}"
- "{{ source_list.results[x] }}"
... (unknown how many items in result from API)
vars:
list_a: []
source_list.results[x] comes from an API result. The reason why I need to create an array is that the number of API result is maximum 100. But there are over 500 items.
Note: since we have no idea what you initial data structure looks like exactly, the below might not be 100% fitting your use case. For your next questions, please read How to ask and pay attention to the Minimal, complete and reproducible example section. Thanks
You are taking this the wrong way. Simply extract the attribute you need from each result using the map(attribute=x) Jinja2 filter.
For the below I inferred (see above note) that:
you called your API with ansible.builtin.uri in a loop to get batches of 100 results which are returned as a list in the SearchResult.ressources field
you want in the end a flattened list where all resources are at top level
- name: Show my list of single attributes
ansible.builtin.debug:
var: "source_list.results
| map(attribute='json.SearchResult.resources') | flatten"
You actually don't need to set_fact:
For a single use, just use the above expression directly in the relevant parameter (e.g. loop or a module param....) or eventually declare this in a var at task level.
If you want to reuse this in different parts of your playbook, just declare a var at play level and expand it anywhere once you have called your API and populated the source_list var. In that case, just add a default value to prevent an error if API was not yet called.
Example for the second case above in this pseudo playbook
---
- hosts: localhost
gather_facts: false
vars:
list_a: "{{ source_list.results | d([])
| map(attribute='json.SearchResult.resources') | flatten }}"
tasks:
- name: "This will return an empty list (i.e. [])
as we did not populate source_list yet"
ansible.builtin.debug:
var: list_a
- name: Call our API and register source_list
ansible.builtin.uri:
uri: https://my.api.com/api/v1/some/endpoint
# [... more parameters here ... ]
loop: "{{ my_list_of_ressources }}"
register: source_list
- name: "This will now return a populated list
after calling the API and registering source_list"
ansible.builtin.debug:
var: list_a
Now, to still give a direct answer to your initial question: you can construct that list iteratively inside a set_fact task. This is definitely not efficient as it involves a task running inside a loop (both unneeded as demonstrated above) and possibly on multiple hosts in your play. But for learning purpose, here it is:
- name: very inefficient way to get the same result as above
set_fact:
list_a: "{{ list_a | d([]) + item.SearchResult.resources }}"
loop: "{{ source_list.results }}"
After reading #Zeitounator's answer, I realized I was taking this the wrong way. I used the idea in the answer (json query and flatten) and I changed my task to the below which works as expected.
- name: Create ID list from API result data
set_fact:
list_a: "{{ source_list | json_query('results[*].json.SearchResult.resources[*].id') | flatten }}"
- name: API get again with id
uri:
url: "{{ request_url }}/{{ item }}"
...
register: result_data
with_items:
- "{{ list_a }}"

Is There a Way to Have a Value Stored in an Array Every Time the Loop Occurs in Ansible?

I have an ansible-playbook which aims to display an A Record of a particular host from a DNS Server within Domain Controller. Here’s what I did on Ansible-Playbook:
Use powershell to obtain information related to A Record on the DNS Server.
Save it as a variable named test_var.
Divide the contents of the variable test_var into line by line.
Retrieves the important line containing the string host I'm looking for.
Take the important attributes of those important lines and show it as msg.
Here's the code:
# hostname and domain are necessary
---
- hosts: all
gather_facts: no
vars:
search_name: "{{hostname}}"
tasks:
- name: powershell query
win_shell: "Get-DnsServerResourceRecord -Name '{{hostname}}' -ZoneName '{{domain}}' -RRType A"
register: result1
when: (hostname is defined) and (domain is defined)
- set_fact:
test_var: "{{ result1.stdout_lines }}"
- name: pickup lines
set_fact:
important_lines: "{{ important_lines |default([]) + [item] }}"
with_items:
- "{{ test_var }}"
- name: find the line
set_fact:
target_line: "{{item}}"
when: item|trim is search(search_name)
loop: "{{ important_lines | flatten(1) }}"
- name: get all attributes
set_fact:
attribute_record: "{{ target_line.split()[1]|trim}}"
attribute_type: "{{ target_line.split()[2]|trim}}"
attribute_timestamp: "{{ target_line.split()[3]|trim}}"
attribute_timetolive: "{{ target_line.split()[4]|trim}}"
attribute_ipaddress: "{{ target_line.split()[5]|trim}}"
- name: print results
debug:
msg: "name: {{search_name}}, Ip Address: {{attribute_ipaddress}}"
And here's my DNS Server configuration:
And the results are as follows (host=test1):
However, I have a problem. In the Find the line task which runs the loop, the target_line variable stores only the last line at the end of the task. So, when the print results task is executed, only the last host and IP address are displayed. The question is, is there some way to have each line stored in an array every time the loop occurs? Thus, I can call the contents of the array to display it one by one. Thank you.
Here's the solution that I got:
# hostname and domain are necessary
---
- hosts: all
gather_facts: no
vars:
correct_line: []
search_name: "{{hostname}}"
tasks:
- name: powershell query
win_shell: "Get-DnsServerResourceRecord -Name '{{hostname}}' -ZoneName '{{domain}}' -RRType A"
register: result1
when: (hostname is defined) and (domain is defined)
- set_fact:
test_var: "{{ result1.stdout_lines }}"
- name: pickup lines
set_fact:
important_lines: "{{ important_lines |default([]) + [item] }}"
with_items:
- "{{ test_var }}"
- name: find the line
set_fact:
correct_line: "{{correct_line + [item]}}"
when: item|trim is search(search_name)
loop: "{{ important_lines | flatten(1) }}"
- name: print results
debug:
msg: "name: {{item.split()[0]|trim}}, Ip Address: {{item.split()[5]|trim}}"
loop: "{{ correct_line | flatten(1) }}"
And here's the result:

Can I create dynamic lists within Ansible vars_files?

I have a variables file that includes important info about our databases; the server they are on, the db version, the DB_HOME directory, etc. In the variables file, I would like to dynamically create lists that capture the unique values of those properties, so they can be easily iterated through in a task.
I have equivalent functionality by creating the list on the fly in a task's loop option, but that means repeating that loop syntax (violates DRY principle) and I would like less sophisticated Ansible colleagues to be able to use a pre-defined list.
example of the variables file databases.yml:
databases:
- name: test_db1
server: ora_901
listener: LISTENER_XYZ
version: '11.2.0.4'
oracle_home: '/app/oracle/product/11.2.0.4/db_home'
- name: test_db2
server: ora_902
listener: LISTENER_ABC
version: '11.2.0.4'
oracle_home: '/app/oracle/product/11.2.0.4/db_home'
## This didn't work... was hoping I could build this list dynamically
listeners:
- name: "{{ item }}"
loop: "{{ databases | map(attribute = 'listener') | list | unique }}"
servers:
- name: "{{ item }}"
loop: "{{ databases | map(attribute = 'server') | list | unique }}"
I would then use this loop through either the 'listeners' or 'servers' lists directly with some tasks.
When I tried a task that referenced the listeners variable, it failed. Referencing databases works and all items are returned, so I know it's getting some data from the vars_file...
- vars_files:
- vars/databases.yml
tasks:
- debug:
msg: "{{ databases }}"
- debug:
msg: "{{ listeners }}"
TASK [debug] **************************************************************************
ok: [FQDN] => {
"msg": [
{
"listener": "LISTENER_XYZ",
"name": "test_db1",
"oracle_home": "/app/oracle/product/11.2.0.4/db_home",
"server": "ora_901",
"version": "11.2.0.4"
},
{
"listener": "LISTENER_ABC",
"name": "test_db2",
"oracle_home": "/app/oracle/product/11.2.0.4/dbhome_1",
"server": "ora_902",
"version": "11.2.0.4"
},
fatal: [FQDN]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'item' is undefined\n\nThe error appears to have been in '/home/xxx/test_vars.yml': line 21, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
I would really like to keep these dynamic definitions in the same place as the server definitions, and don't see why it wouldn't be possible, I'm just sure I'm using the wrong mechanism.
Check this out from Ansible doc// Just to give you an idea, about "loop_control".
You can nest two looping tasks using include_tasks. However, by default Ansible sets the loop variable item for each loop. This means the inner, nested loop will overwrite the value of item from the outer loop. You can specify the name of the variable for each loop using loop_var with loop_control:
# main.yml
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
loop_control:
loop_var: outer_item
# inner.yml
- debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c

Arrays in Ansible

I have a json like below
{
"nodes":[
{
"node_values":[
"[test1]",
"10.33.11.189",
"10.33.11.185"
]
},
{
"node_values":[
"[test2]",
"10.33.11.189",
"10.33.11.185"
]
}
]
}
I am trying to read only the node values and put it in the text files. I am using below ansible code
hosts: localhost
vars:
tmpdata1: "{{ lookup('file','test.json')|from_json }}"
tasks:
- name: Add mappings to /etc/hosts
blockinfile:
path: /home/s57232/Ansible-Install/Install_Inventory.txt
content: item
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.node_values[0] }}"
loop: "{{ tmpdata1 |json_query('nodes[*].node_values[*]') }}"
i am getting below error
**TASK [Add mappings to /etc/hosts] **********************************************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'node_values'\n\nThe error appears to have been in '/home/s57232/Ansible-Install/prepare_inventory.yml': line 14, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Add mappings to /etc/hosts\n ^ here\n"}**
When i am trying to read with with items and file format with out the blockinfile, if the same IP is there in multiple places, it is not writing, because it is looking for unique values. I am not able to proceed further. Can anyone please help me?
when i am using
- name: Add mappings to /etc/hosts
blockinfile:
path: /home/s57232/Ansible-Install/Install_Inventory.txt
content: "{{ item.node_values }}"
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.node_values[0] }}"
loop: "{{ tmpdata1 |json_query('nodes[*]') }}"
I am getting
# BEGIN ANSIBLE MANAGED BLOCK [test1]
['[test1]', '10.33.11.189', '10.33.11.185']
# END ANSIBLE MANAGED BLOCK [test1]
# BEGIN ANSIBLE MANAGED BLOCK [test2]
['[test2]', '10.33.11.189', '10.33.11.185']
# END ANSIBLE MANAGED BLOCK [test2]
my expectation is
# BEGIN ANSIBLE MANAGED BLOCK [test1]
[test1]
10.33.11.189
10.33.11.185
# END ANSIBLE MANAGED BLOCK [test1]
# BEGIN ANSIBLE MANAGED BLOCK [test2]
[test2]
10.33.11.189
10.33.11.185
# END ANSIBLE MANAGED BLOCK [test2]
Here you are:
- name: Add mappings to /etc/hosts
blockinfile:
path: /home/s57232/Ansible-Install/Install_Inventory.txt
content: "{{ item.node_values | join('\n') }}"
marker: "# {mark} ANSIBLE MANAGED BLOCK {{ item.node_values.0 }}"
loop: "{{ tmpdata1.nodes }}"
You don't need to use JMESPath unless you wanted to filter some values. Other than that, you have two lists: one to loop over, the other to join the elements with a newline character.

Ansible role checking path of dynamic variable within task

I have a couple of ansible role tasks which I am using to setup a number of configuration directories. The problem is that I have a list of configurations which need to be setup with a certain path but only if they do not exist.
- name: Ensure core configuration directories exist.
shell: "cp -r {{ service_install_path }}/conf/ {{ service_home }}/{{ item }}.conf.d"
when:
- "item in conf_indexes_current.content"
- "not {{ service_home }}/{{ item }}.conf.d.stat.exists"
with_items: "{{ conf_dirs }}"
The problem however is that you can't stat a path like this:
- "not {{ service_home }}/{{ item }}.conf.d.stat.exists"
The error I am getting is this:
The conditional check 'not {{ service_home }}/{{ item
}}.conf.d.stat.exists' failed. The error was: unexpected '/'
line 1
Is there a way to set a dynamic variable full path and then test it? It does not seem like I can set a fact here either.
[update]: Just read another question trying to do something with a very vaguely similar loop concept. Is the correct method to simply use a shell script/template at this point?
Alright I did eventually figure this out with hints from bits and pieces of other Stack questions and random sites. I don't use ansible very often and we've only been using it for roughly a year. I ended up doing the following, breaking it into two tasks:
- name: Check existence of config for each index.
stat:
path: "{{ service_home }}/{{ item }}.conf.d"
register: "{{ item }}_conf_true"
with_items: "{{ conf_dirs }}"
- name: Ensure core configuration directories exist as full templates for modification.
shell: "cp -r {{ service_install_path }}/conf/ {{ service_home }}/{{ item }}.conf.d"
when:
- "item in conf_indexes_current.content"
- "{{ item }}_conf_true is not defined"
with_items: "{{ conf_dirs }}"
Does it work? Yes. Is it proper? Maybe but there is likely a better way I have not thought of or seen yet.

Resources