Ansible: How to 'read_csv' into a dynamic variable name? - csv

I'm trying to read from a list of CSV files (fileglob) and the files have a standard naming convention. Just wanted to read the CSV files using read_csv and register/set_fact them to the filename, which should be the variable name.
The files are
apples.csv
pears.csv
grapes.csv
What I've tried, (its close but not correct)
- name: "Read into dynamic variables"
read_csv:
path: "{{ item }}"
with_fileglob:
- "/tmp/fruits/*.csv"
fruit_name: "{{ item | basename | regex_replace('.csv') }}"
register: "fruit_{{ fruit_name }}"
So ideally want to get the contents of each CSV to be part of variable, e.g. fruit_apples which can be later re-used in other plays.

For example, given the files
shell> tree fruits/
fruits/
├── apples.csv
├── grapes.csv
└── pears.csv
0 directories, 3 files
shell> cat fruits/apples.csv
red,big,20
green,small,10
shell> cat fruits/grapes.csv
red,big,20
black,small,10
shell> cat fruits/pears.csv
green,big,30
yellow,small,20
Read the files
- read_csv:
fieldnames: color,size,price
path: "{{ item }}"
with_fileglob: "fruits/*.csv"
register: fruit
Instead of creating the variables fruit_*, it's simpler to create a dictionary of the fruits. For example, put the declarations below as appropriate
fruits: "{{ dict(f_keys|zip(f_vals)) }}"
f_vals: "{{ fruit.results|map(attribute='list')|list }}"
f_keys: "{{ fruit.results|map(attribute='item')|
map('basename')|
map('splitext')|
map('first')|list }}"
gives
fruits:
apples:
- {color: red, price: '20', size: big}
- {color: green, price: '10', size: small}
grapes:
- {color: red, price: '20', size: big}
- {color: black, price: '10', size: small}
pears:
- {color: green, price: '30', size: big}
- {color: yellow, price: '20', size: small}
Example of a complete playbook
- hosts: localhost
vars:
fruits: "{{ dict(f_keys|zip(f_vals)) }}"
f_vals: "{{ fruit.results|map(attribute='list')|list }}"
f_keys: "{{ fruit.results|map(attribute='item')|
map('basename')|
map('splitext')|
map('first')|list }}"
tasks:
- read_csv:
fieldnames: color,size,price
path: "{{ item }}"
with_fileglob: "fruits/*.csv"
register: fruit
- debug:
var: fruits
- debug:
var: fruits.apples

This will not be possible. According Registering variables
" When you register a variable in a task with a loop, the registered variable contains a value for each item in the loop. The data structure placed in the variable during the loop will contain a results attribute, that is a list of all responses from the module."
Therefore you need to extract the parts which you are interested in within the next tasks.
To get a better understanding in the behavior I've created a small test
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Create multiple results and register them
debug:
msg: "{{ item }}"
loop: [A, B, C]
register: result
- name: Show result
debug:
msg: "{{ result }}"
which is showing an equivalent behavior since with_fileglob is a with_X style loop.
TASK [Create multiple results and register them] ***
ok: [localhost] => (item=A) =>
msg: A
ok: [localhost] => (item=B) =>
msg: B
ok: [localhost] => (item=C) =>
msg: C
TASK [Show result] *********************************
ok: [localhost] =>
msg:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
changed: false
failed: false
item: A
msg: A
- ansible_loop_var: item
changed: false
failed: false
item: B
msg: B
- ansible_loop_var: item
changed: false
failed: false
item: C
msg: C
Further Q&A
Ansible: How to register multiple variables within a single task?
Registering multiple variables in a loop

Related

Syntax to check if var

I have a playbook to get all disks letter configured on my server and I need a task to verify if extra var letter is on the list.
For example I need to check if "F" is on the json data below.
Could you please help me on the best best syntax?
Thanks
{
"disks_drives_letter": [
[
"C"
],
[
"D"
],
[
"E"
],
[]
]
}
You can use setup module to get your host information like disks. For more information about the setup module https://docs.ansible.com/ansible/latest/collections/ansible/builtin/setup_module.html
Example of playbook:
- hosts: localhost
tasks:
gather_facts: false
vars:
my_disk_drives: ['sda', 'sdb']
tasks:
- name: Collect host hardware information
setup:
gather_subset:
- hardware
- name: Output if disk exist
debug:
msg: "{{ item }} exists"
loop: "{{ my_disk_drives }}"
when: item in hostvars[inventory_hostname].ansible_devices.keys() | list
- name: Output if disks does not exist
debug:
msg: "{{ item }} does not exist"
loop: "{{ my_disk_drives }}"
when: not item in hostvars[inventory_hostname].ansible_devices.keys() | list
Output:
TASK [Output if disk exist]
ok: [localhost] => (item=sda) => {
"msg": "sda exists"
}
skipping: [localhost] => (item=sdb)
TASK [Output if disks does not exist]
skipping: [localhost] => (item=sda)
ok: [localhost] => (item=sdb) => {
"msg": "sdb does not exist"
}
Use filters intersect and difference, and declare the lists
my_disks_exist: "{{ ansible_devices.keys()|intersect(my_disks) }}"
my_disks_not_exist: "{{ my_disks|difference(my_disks_exist) }}"
Example of a complete playbook for testing
- hosts: localhost
vars:
my_disks: [sda, sdb, sdc]
my_disks_exist: "{{ ansible_devices.keys()|intersect(my_disks) }}"
my_disks_not_exist: "{{ my_disks|difference(my_disks_exist) }}"
tasks:
- setup:
gather_subset: devices
- debug:
var: ansible_devices.keys()
- debug:
var: my_disks_exist
- debug:
var: my_disks_not_exist
| flatten help me thanks #vladimir-botka
- name: Get all disks letter from the disks infos
set_fact:
disks_drives_letters: "{{ win_disk_facts | json_query(query) | flatten }}"
- name: Check if disk_letter is used on server fail:
msg: "The disk letter already exist on the VM" when: '"{{ drive_letter }}" in "{{ disks_drives_letters}}"'

Ansible append values from multiple yaml files that contain the same key to a list variable

Multiple yaml files contain a value with key "id" that I need to appended to a list variable.
---
name: somename
group: somegroup
id: someid
I've tried using ansible.builtin.command to cat and register the contents of the files but have not been successful in finding a way to filter the stdout_lines returned to get to the value I'm after. My attempt looks something like the following but there's been so many iterations at this point that it's all a bit jumbled.
- name: Store job definition content
local_action:
module: ansible.builtin.command
cmd: "cat {{ item.path }}"
loop:
"{{ definition_files.files }}"
register:
job_definitions
- name: Get a list of existing jobs ids
ansible.builtin.set_fact:
existing_jobs: "{{ existing_jobs + (item.stdout | to_yaml | map(attribute='id')}}"
loop:
"{{ job_definitions.results }}"
Another example that's also incorrect and doesn't work but demonstrates what I've been messing around with to try to get my result:
- name: Define empty list
ansible.builtin.set_fact:
uuid_list: []
- name: Create a list of job ids from template files
ansible.builtin.set_fact:
uuid_list: "{{uuid_list + lookup('file', item.path | to_yaml).id }}"
loop:
"{{ definition_files.files }}"
Given the following tree
.
├── definition_files
│   ├── def1.yaml
│   └── def2.yaml
└── test.yaml
Content of the test definition files
definition_files/def1.yaml
name: name1
group: group1
id: abc
definition_files/def2.yaml
name: name2
group: group2
id: def
The following test.yaml playbook
---
- hosts: localhost
gather_facts: false
tasks:
- name: append ids from each files
set_fact:
my_id_list: "{{ my_id_list | d([]) + [(lookup('file', item) | from_yaml).id] }}"
loop: "{{ q('fileglob', 'definition_files/*.yaml' ) }}"
- debug:
var: my_id_list
Gives:
$ ansible-playbook test.yaml
PLAY [localhost] **************************************************************************************************************************************************************************************************
TASK [append ids from each files] *********************************************************************************************************************************************************************************
ok: [localhost] => (item=/tmp/test/definition_files/def2.yaml)
ok: [localhost] => (item=/tmp/test/definition_files/def1.yaml)
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"my_id_list": [
"def",
"abc"
]
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

I'm trying to get ansible output formatted like item1 - item2 instead of two separate lists of items

I have a functioning ansible script that connects to AWS and prints out tags from instances in a specified state.
The problem I'm having is the print out is two separate lists e.g.
What I get is:
name1
name2
name3
description1
description2
description3
What I would like is:
name1 - description1
name2 - description2
name3 - description3
I've tried pushing this all into a dictionary, but got lost in the attempt. There must be an easier way.
Here's my code:
- name: print stopped systems
hosts: all
become: false
tasks:
- name: Gather ec2_metadata_facts (use -vv to show all)
action: ec2_metadata_facts
- name: pull instance info with ec2_instance_info
ec2_instance_info:
region: "{{ lookup('env','AWS_DEFAULT_REGION') }}"
aws_access_key: "{{ lookup('env','AWS_ACCESS_KEY_ID') }}"
aws_secret_key: "{{ lookup('env','AWS_SECRET_ACCESS_KEY') }}"
filters:
instance-state-name: [ "shutting-down", "stopping", "stopped" ]
register: ec2_info
- name: print Instance Info
debug:
msg:
- "{{ ec2_info | json_query(name_tag) }}"
- "{{ ec2_info | json_query(description_tag) }}"
vars:
name_tag: "instances[*].tags.Name"
description_tag: "instances[*].tags.Description"
The output of the debug statement looks like this:
TASK [print Instance Info] **********************************************************************************************************************
ok: [tools-server-01] =>
msg:
- - server-01
- server-02
- - Description for server one
- Description for server two
Thanks #Rickkwa
I've got this mostly working.
here's my current code
- name: Print stopped systems
hosts: all
become: false
tasks:
- name: Gather ec2_metadata_facts (use -vv to show all)
action: ec2_metadata_facts
- name: pull instance info with ec2_instance_info
ec2_instance_info:
region: "{{ lookup('env','AWS_DEFAULT_REGION') }}"
aws_access_key: "{{ lookup('env','AWS_ACCESS_KEY_ID') }}"
aws_secret_key: "{{ lookup('env','AWS_SECRET_ACCESS_KEY') }}"
filters:
instance-state-name: [ "shutting-down", "stopping", "stopped" ]
register: ec2_info
- name: Create Name list
no_log: true
set_fact:
name_l: "{{ec2_info | json_query(name_tag)}}"
vars:
name_tag: "instances[*].tags.Name"
- name: Create Description list
no_log: true
set_fact:
desc_l: "{{ ec2_info | json_query(description_tag) }}"
vars:
description_tag: "instances[*].tags.Description"
- name: print together
debug:
msg: "{{item.0}} --- {{item.1}}"
loop: "{{ name_l|zip(desc_l)|list}}"
Unfortunately, the last debug statement includes some 'garbage" I'd like to remove...anyone have a suggestion to get rid of the stuff following the "u"s?
here's the output :'
TASK [print together] ***************************************************************************************************************************
ok: [dev-bsd-01] => (item=[u'server-01', u'server one description']) =>
msg: server-01 --- server one description
ok: [dev-bsd-01] => (item=[u'server-02', u'server two description']) =>
msg: server-02 --- server two description

Ansible parse json and read result into different variables

I've set up a task which queries the github api meta endpoint and returns the following
{
"verifiable_password_authentication": true,
"github_services_sha": "f9e3a6b98d76d9964a6613d581164039b8d54d89",
"hooks": [
"192.30.252.0/22",
"185.199.108.0/22",
"140.82.112.0/20"
],
"git": [
"192.30.252.0/22",
"185.199.108.0/22",
"140.82.112.0/20",
"13.229.188.59/32",
"13.250.177.223/32",
"18.194.104.89/32",
"18.195.85.27/32",
"35.159.8.160/32",
"52.74.223.119/32"
],
"pages": [
"192.30.252.153/32",
"192.30.252.154/32",
"185.199.108.153/32",
"185.199.109.153/32",
"185.199.110.153/32",
"185.199.111.153/32"
],
"importer": [
"54.87.5.173",
"54.166.52.62",
"23.20.92.3"
]
}
What I need to do is get the 3 hook IPs and read them each into their own variable.
I've tried a couple of solutions i've found around but nothing is seeming to work for me.
I've got as far as drilling down into the json so i'm being returned only the 3 IPs, but how do I get them out and into variables individually?
i gave it a shot using j2 syntax in the variable name part, and - TIL - looks like the jinja2 syntax is allowed in that part as well!
please see playbook to process the hooks list variable and assign to variables variable_1, variable_2, variable_3 and so on:
- hosts: localhost
gather_facts: false
vars:
counter: 1
hooks:
- 192.30.252.0/22
- 185.199.108.0/22
- 140.82.112.0/20
tasks:
- name: populate vars
set_fact:
variable_{{counter}}: "{{ item }}"
counter: "{{ counter | int + 1 }}"
with_items:
- "{{ hooks }}"
- name: print vars
debug:
msg: "variable_1: {{variable_1}}, variable_2: {{variable_2}}, variable_3: {{variable_3}}"
and the output:
[root#optima-ansible ILIAS]# ansible-playbook 50257063.yml
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [populate vars] *******************************************************************************************************************************************************************************************************************
ok: [localhost] => (item=192.30.252.0/22)
ok: [localhost] => (item=185.199.108.0/22)
ok: [localhost] => (item=140.82.112.0/20)
TASK [print vars] **********************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "variable_1: 192.30.252.0/22, variable_2: 185.199.108.0/22, variable_3: 140.82.112.0/20"
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
[root#optima-ansible ILIAS]#
hope it helps
UPDATE:
something weird i noticed - also TIL - is that if you reverse the lines:
variable_{{counter}}: "{{ item }}"
counter: "{{ counter | int + 1 }}"
to:
counter: "{{ counter | int + 1 }}"
variable_{{counter}}: "{{ item }}"
you still end up with the same variable names, _1 to _3, while i would expect to get _2 to _4.
I guess ansible loops behave differently than expected from other programming languages.
---
- name: Query Github Meta API and get Hook Ips
hosts: local
connection: local
vars:
counter: 1
tasks:
- name: Query API
uri:
url: https://api.github.com/meta
return_content: yes
register: response
- name: Populate Hook Variables
set_fact:
webhook_ip_{{counter}}: "{{ item }}"
counter: "{{ counter | int + 1 }}"
with_items:
- "{{ response['json']['hooks'] }}"
- name: print vars
debug:
msg: "Variable_1: {{ webhook_ip_1 }}, Variable_2: {{ webhook_ip_2 }}, Variable_3: {{ webhook_ip_3 }}"
Works with GitHub Webhook IPs in a loop
- name: get request to github
uri:
url: "https://api.github.com/meta"
method: GET
return_content: yes
status_code: 200
headers:
Content-Type: "application/json"
#X-Auth-Token: "0010101010"
body_format: json
register: json_response
- name: GitHub webhook IPs
debug:
msg: "{{ item }}"
with_items: "{{ (json_response.content | from_json).hooks }}"

How to run Ansible task only when item in complex JSON is defined?

I have two hosts:
foo
bla
foo has no host_vars. bla has a host_vars file with this content:
---
# host_vars/bla
'a': {
'b': {
'c': 'string'
}
}
my task should only run when the complex JSON exists and contains the item b. My logic would be: when a exists and has item b then it has also c.
My task looks like:
---
- name: example task
file:
dest: "{{ item.dest }}"
owner: "{{ item.owner }}"
state: directory
with_items:
- { dest: "/tmp/{{ a['b']['c'] }}", owner: root }
when: a['b'] is defined
result
this task succeeds for bla (cause it has the correct JSON) but fails for foo which misses the JSON.
fatal: [foo]: FAILED! => {"failed": true, "msg": "'dict object' has no attribute 'b'"}
ok: [bla] => (item={u'dest': u'/tmp/string', u'owner': u'root'})
expected
skipping: [foo]: => (item={u'dest': u'/tmp/', u'owner': u'root'})
ok: [bla] => (item={u'dest': u'/tmp/string', u'owner': u'root'})
notice
Now I know that the when statement is interpreted for each item therefore it will not work. Could someone provide a solution that works?
Ansible version is 2.2.0
edit
---
- name: example task
file:
dest: "{{ item.dest }}"
owner: "{{ item.owner }}"
state: directory
with_items:
- { dest: "/tmp/{{ a['b']['c'] | default([]) }}", owner: root }
when: a['b'] is defined
this will not work because when is evaluated after with_items and as long as a['b'] is not defined it will fail.
fatal: [foo]: FAILED! => {"failed": true, "msg": "'dict object' has no attribute 'b'"}
found something that works:
- name: example task
file:
dest: "{{ item.dest }}"
owner: "{{ item.owner }}"
state: directory
with_items:
- { dest: "/tmp/{{ (a['b'] | default({}))['c'] | default('nA') }}", owner: root }
when: a['b'] is defined