Arrays in Ansible - arrays

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.

Related

Ansible: loop with using collection and role

I´m doing the first steps in Ansible this week and I break on include_tasks for looping ofer a role.
The needed task is to create Letsencrypt certificates for a bunch of domains, thanks to T-Systems-MMS, there is already a collection to do this via APIs of letsencrypt and AutoDNS (see https://github.com/T-Systems-MMS/ansible-collection-acme/blob/master/docs/dns-challenge/autodns.md).
Filling this playbook with my settings, it is working fine for one domain. My try to loop over is (hopefully there was no mistake while anonymising the code):
playbook_getsslcert_main.yml:
---
- hosts: localhost
connection: local
vars:
ansible_python_interpreter: auto
tasks:
- name: Get SSL certificate
include_tasks: playbook_getsslcert_task.yml
loop:
- sub1.domain1.com
- sub2.domain1.com
playbook_getsslcert_task.yml:
---
- name: Doing letsencrypt ACME with AutoDNS
collections:
- t_systems_mms.acme
roles:
- acme
vars:
nbb_emailadress: my.email#example.com
nbb_autodnsuser: login.user#other.com
acme_domain:
certificate_name: "{{ item }}"
zone: "domain1.com"
email_address: "{{ nbb_emailadress }}"
subject_alt_name:
- "{{ item }}"
acme_challenge_provider: autodns
acme_use_live_directory: true
acme_conf_dir: /etc/letsencrypt
acme_account_email: "{{ nbb_emailadress }}"
acme_dns_user: "{{ nbb_autodnsuser }}"
acme_dns_password: "supersecret"
The error I get is
fatal: [localhost]: FAILED! => {"reason": "conflicting action statements: hosts, roles\n\nThe error appears to be in 'playbook_getsslcert_task.yml': line 2, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n---\n- name: Doing letsencrypt ACME with AutoDNS\n ^ here\n"}
My collegues and me are experienced Linux guys, we tested a lot; also we checked the YAML with formatcheckers and so on, did different styles for looping, tried an example tasks.ym just for writing a message, checked file formats (for linefeeds, correct HEX values,...) and so on.
But Ansible doesnt like the playbook.
Thanks for all your suggestions.
Edit:
Ubuntu 18.04 LTS, Python 3.6.9, Ansible 2.9.27
Thanks to #Zeitounator (sorry for overlooing your first link), a suitable and working solution have been found:
---
- hosts: all
connection: local
vars:
ansible_python_interpreter: auto
tasks:
- name: "Doing letsencrypt ACME with AutoDNS for {{ nbb_domain }}"
collections:
- t_systems_mms.acme
include_role:
name: acme
vars:
nbb_emailadress: my.email#example.com
nbb_autodnsuser: login.user#other.com
acme_domain:
certificate_name: "{{ nbb_domain }}"
zone: "domain1.com"
email_address: "{{ nbb_emailadress }}"
subject_alt_name:
- "{{ nbb_domain }}"
acme_challenge_provider: autodns
acme_use_live_directory: true
acme_conf_dir: /etc/letsencrypt
acme_account_email: "{{ nbb_emailadress }}"
acme_dns_user: "{{ nbb_autodnsuser }}"
acme_dns_password: "supersecret"
loop:
- sub1.domain1.com
- sub2.domain1.com
loop_control:
loop_var: nbb_domain

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:

Ansible loop using ansible_hostnames

I'm trying to update a configuration file for a NiFi deployment, the inital deployment configuration needs to include the nodes to allow HTTPS connections to be established between them.
I have an ansible tasks that makes the required structural changes to the configuration files, but I can't seem to get the right details inserted.
- name: Add each host to the authorizers.xml
lineinfile:
path: /opt/nifi/conf/authorizers.xml
line: "<property name=\"Node Identity {{ item }}\">CN={{ item }}, OU=NiFi</property>"
insertafter: <!--accessPolicyProvider Node Identities-->
loop: "{{ query('inventory_hostnames', 'nifi') }}"
This puts the ip addresses for the hosts, and I need to get the ansible_hostname for each node instead.
I've played around with ansible_play_batch and loop: "{{ groups['nifi'] }}" but I'm getting the result, outputting the ip addresses instead of the short hostnames each time.
The short hostnames are not stored in my ansible configuration anywhere, they are (if I understand correctly) determined at run time via the gathering facts process. I'd really like to not have to put the node names into a list variable.
Q: "Get the ansible_hostname for each node"
A: Given the inventory
shell> cat hosts
[nifi]
10.1.0.51
10.1.0.52
The playbook below
- hosts: nifi
tasks:
- debug:
var: ansible_hostname
gives (abridged)
ok: [10.1.0.51] =>
ansible_hostname: test_01
ok: [10.1.0.52] =>
ansible_hostname: test_02
It's possible to iterate the hosts in the group and get ansible_hostname from the hostvars. For example, delegate_to localhost and run_once
- debug:
msg: "{{ hostvars[item].ansible_hostname }}"
loop: "{{ groups.nifi }}"
delegate_to: localhost
run_once: true
gives
ok: [10.1.0.51 -> localhost] => (item=10.1.0.51) =>
msg: test_01
ok: [10.1.0.51 -> localhost] => (item=10.1.0.52) =>
msg: test_02

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

ansible loop over list and dictionary at the same time

I am writing a playbook that ensure nodes appear in /etc/fstab.
I am using loops to prevent code duplication.
The logic is first to check if the line appears using grep (with perl regex because it is a multi line) and store the results in a register.
Then I want to add only the lines that are not in fstab file. To achieve that I need to loop over list (the register with the grep return codes) and a dictionary (that contains the fstab entries).
I am having errors with the parallel loop. I tried to follow these steps.
One or more undefined variables: 'str object' has no attribute 'item'
tasks/fstab.yaml:
---
- name: Make dirs
sudo: yes
file: path={{ item.value }} state=directory
with_dict:
"{{ fstab.paths }}"
- name: Check whether declared in fstab
sudo: no
command: grep -Pzq '{{ item.value }}' /etc/fstab
register: is_declared
with_dict:
"{{ fstab.regexs }}"
- name: Add the missing entries
sudo: yes
lineinfile: dest=/etc/fstab line="{{ item.1.item.value }}"
when: item.0.rc == 1
with_together:
- "{{ is_declared.results }}"
- "{{ fstab.entries }}"
vars/main.yml:
---
fstab:
paths:
a: "/mnt/a"
b: "/mnt/b"
regexs:
a: '\n# \(a\)\nfoo1'
b: '\n# \(b\)\nfoo2'
entries:
a: "\n# (a)\nfoo1"
b: "\n# (b)\nfoo2"
I am not using template on purpose (I want to add entries to existing files and not to over write them).
UPDATE: I see ansible has module "mount" which deals with fstab. However I am still looking for a solution to this issue because I might be needed it again later on.
I have a couple ideas as to why your original approach was failing, but let's scratch that for a moment. It looks like you're overcomplicating things- why not use a complex list var to tie it all together, and use the regexp arg to the lineinfile module instead of a separate regex task? (though your sample data should work fine even without the regexp param) Something like:
---
- name: Make dirs
sudo: yes
file: path={{ item.path }} state=directory
with_items: fstab
- name: Add the missing entries
sudo: yes
lineinfile: dest=/etc/fstab line={{ item.entry }} regexp={{ item.regex }}
with_items: fstab
fstab:
- path: /mnt/a
regex: '\n# \(a\)\nfoo1'
entry: "\n# (a)\nfoo1"
- path: /mnt/b
regex: '\n# \(b\)\nfoo2'
entry: '\n# (b)\nfoo2'

Resources