"Key is not unique for value ''" thrown by read_csv - ansible

My playbook used to be able to run successfully without any issues.
But recently, I ran the playbook again (without changing anything) and got this error:
TASK [read_csv] *****************************************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Key 'username' is not unique for value ''"}
This is the playbook:
---
- name: Read User
hosts: localhost
tasks:
- read_csv:
path: opt/ansible.csv
key: username
fieldnames: username,usergroups,ipaddress,lastlogindate
delimiter: ','
register: usersdata
This is an example of the CSV file that I am using:
Username
User_groups
IP Address
Last Login Date
Apple
A
192.34.50.183
12/4/2021
Blizz
B
192.34.50.145
3/8/2021
What is value ''? And why is using 'username' as the key now wrong?
===============================================================
Update:
I tried the playbook suggested by Vladimir Botka below, and it was able to run successfully.
PLAY [Read User] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [read_csv] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.dict": {}
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.list": [
{
"IP Address": "192.169.60.220 - XXX.XXX.60.254",
"Last Login Date": "30-Jul-21",
"User_groups": "B",
"Username": "Vijay"
},
{
"IP Address": "XXX.XXX.60.146 - XXX.XXX.60.147",
"Last Login Date": "1-Jan-21",
"User_groups": "ecquaria",
"Username": "wangjcecquaria"
},
…
TASK [read_csv] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.dict": {
"Kyawryobi": {
"IP Address": "XXX.XXX.60.184 - XXX.XXX.60.185",
"Last Login Date": "28-Jul-21",
"User_groups": "Ryobi",
"Username": "Kyawryobi"
},
"Nikhil": {
"IP Address": "XXX.XXX.60.1 - XXX.XXX.60.80",
"Last Login Date": "30-Jul-21",
"User_groups": "A",
"Username": "Joshua"
},
"Vijay": {
…
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.list": []
}
TASK [read_csv] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.dict": {}
}
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.list": [
{
"ipaddress": "IP Address",
"lastlogindate": "Last Login Date",
"usergroups": "User_groups",
"username": "Username"
},
{
"ipaddress": "192.169.60.220 - XXX.XXX.60.254",
"lastlogindate": "30-Jul-21",
"usergroups": "A",
"username": "Joshua"
},
{
…
TASK [read_csv] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.dict": {
"Kyawryobi": {
"ipaddress": "XXX.XXX.60.184 - XXX.XXX.60.185",
"lastlogindate": "28-Jul-21",
"usergroups": "Ryobi",
"username": "Kyawryobi"
},
"Nikhil": {
"ipaddress": "XXX.XXX.60.1 - XXX.XXX.60.80",
"lastlogindate": "30-Jul-21",
"usergroups": "A",
"username": "Joshua"
},
"Username": {
…
TASK [debug] *******************************************************************
ok: [localhost] => {
"usersdata.list": []
}
PLAY RECAP *********************************************************************
localhost : ok=13 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Next, I tried running my playbook again without changing anything else in the settings or playbook and there is no more error.
PLAY [Read User] ***************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [read_csv] ****************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I think the error, "Key is not unique for value ''", might be the result of a bug, and running a playbook that triggers ansible to output a dictionary and list using the usersdata variable might have resolved it. I have checked and there is nothing wrong with the data in my CSV. There are no missing values in the 'Username' column, neither do I have the word 'username' duplicated anywhere else.

By default, the module community.general.read_csv takes the first line of a file as a header. You can declare fieldnames if the header is missing. Quoting:
A list of field names for every column. This is needed if the CSV does not have a header.
Given the file
shell> cat /tmp/ansible.csv
Username,User_groups,IP Address,Last Login Date
Apple,A,192.34.50.183,12/4/2021
Blizz,B,192.34.50.145,3/8/2021
You don't have to declare fieldnames because the first line is the header
- read_csv:
path: /tmp/ansible.csv
register: usersdata
gives (see the notes below on the output dictionary vs. list)
usersdata.list:
- IP Address: 192.34.50.183
Last Login Date: 12/4/2021
User_groups: A
Username: Apple
- IP Address: 192.34.50.145
Last Login Date: 3/8/2021
User_groups: B
Username: Blizz
If you do declare fieldnames all lines are treated as data
- read_csv:
path: /tmp/ansible.csv
fieldnames: username,usergroups,ipaddress,lastlogindate
register: usersdata
gives the list with renamed fields including an additional item of the original header
usersdata.list:
- ipaddress: IP Address
lastlogindate: Last Login Date
usergroups: User_groups
username: Username
- ipaddress: 192.34.50.183
lastlogindate: 12/4/2021
usergroups: A
username: Apple
- ipaddress: 192.34.50.145
lastlogindate: 3/8/2021
usergroups: B
username: Blizz
If you declare a key in addition to the fieldnames
- read_csv:
path: /tmp/ansible.csv
key: username
fieldnames: username,usergroups,ipaddress,lastlogindate
register: usersdata
you get the dictionary of this key's values
usersdata.dict:
Apple:
ipaddress: 192.34.50.183
lastlogindate: 12/4/2021
usergroups: A
username: Apple
Blizz:
ipaddress: 192.34.50.145
lastlogindate: 3/8/2021
usergroups: B
username: Blizz
Username:
ipaddress: IP Address
lastlogindate: Last Login Date
usergroups: User_groups
username: Username
Notes
The output depends on the parameter key.
a) Without a key
- read_csv:
path: /tmp/ansible.csv
register: usersdata
- debug:
var: usersdata.dict
- debug:
var: usersdata.list
you get an empty dictionary and a list of the lines
usersdata.dict: {}
usersdata.list:
- IP Address: 192.34.50.183
Last Login Date: 12/4/2021
User_groups: A
Username: Apple
- IP Address: 192.34.50.145
Last Login Date: 3/8/2021
User_groups: B
Username: Blizz
b) If you specify a key (it's case-sensitive)
- read_csv:
path: /tmp/ansible.csv
key: Username
register: usersdata
you get a dictionary of this key's values and an empty list
usersdata.dict:
Apple:
IP Address: 192.34.50.183
Last Login Date: 12/4/2021
User_groups: A
Username: Apple
Blizz:
IP Address: 192.34.50.145
Last Login Date: 3/8/2021
User_groups: B
Username: Blizz
usersdata.list: []
Example of a complete playbook for testing
- hosts: localhost
tasks:
- read_csv:
path: /tmp/ansible.csv
register: usersdata
- debug:
var: usersdata.dict
- debug:
var: usersdata.list
- read_csv:
path: /tmp/ansible.csv
key: Username
register: usersdata
- debug:
var: usersdata.dict
- debug:
var: usersdata.list
- read_csv:
path: /tmp/ansible.csv
fieldnames: username,usergroups,ipaddress,lastlogindate
register: usersdata
- debug:
var: usersdata.dict
- debug:
var: usersdata.list
- read_csv:
path: /tmp/ansible.csv
key: username
fieldnames: username,usergroups,ipaddress,lastlogindate
register: usersdata
- debug:
var: usersdata.dict
- debug:
var: usersdata.list

Related

Ansible looping over main playbook

I created a "main" playbook that create and deploy a complex application let's say an entire house.
The "main" looks like this :
- hosts: host1
roles:
- { role: role1 }
- hosts: host2
roles:
- { role: role2 }
- hosts: localhost
roles:
- { role: role3 }
- { role: role4 }
- { role: role5 }
- { role: role6 }
I would like now to be able to deploy multiple houses using a CSV as input, containing my variables.
I was expecting to just import that "main" playbook, and loop over my CSV so that I can create multiple houses.
Based on the documentation I'm not able to loop using import_playbook, so I'm a stuck on how I could use this "main" for multiple creations in series.
Has anybody been in that situation or has an idea on how I could resolve this ?
You can use the csvfile lookup to read data from CSV files.
Example CSV file roles.csv:
host1,role1
host2,role2
localhost,role3 role4 role5 role6
Create mock-up roles:
for i in {1,2,3,4,5,6}; do
mkdir -p roles/role$i/tasks &&
echo "- debug: msg=\"role$i\"" > roles/role$i/tasks/main.yaml
done
And in the playbook you use the include_role syntax instead of roles, because include_role can be used in a loop. By this you can loop over the array read from the CSV file.
---
- hosts: localhost
connection: local
tasks:
- name: Use role in loop
include_role:
name: '{{ roleinputvar }}'
loop: "{{ lookup('csvfile', inventory_hostname + ' file=roles.csv delimiter=,').split(' ') }}"
loop_control:
loop_var: roleinputvar
connection: local is just for this example.
When run the roles 3 to 6 are executed:
$ ansible-playbook roles.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the
implicit localhost does not match 'all'
PLAY [localhost] **********************************************************************
TASK [Gathering Facts] ****************************************************************
ok: [localhost]
TASK [Use role in loop] ***************************************************************
TASK [role3 : debug] ******************************************************************
ok: [localhost] => {
"msg": "role3"
}
TASK [role4 : debug] ******************************************************************
ok: [localhost] => {
"msg": "role4"
}
TASK [role5 : debug] ******************************************************************
ok: [localhost] => {
"msg": "role5"
}
TASK [role6 : debug] ******************************************************************
ok: [localhost] => {
"msg": "role6"
}
PLAY RECAP ****************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0

How can I filter certain element in a string

playbook.yml
---
hosts: local_host
connection: local
gather_facts: False
tasks:
- name: set-details
set_fact:
filter: "{{ lookup('file', 'tmp/task2.yml') | from_json }}"
- set_fact:
result: "{{ filter['msg'] }}"
- debug:
var: result
task2.yml
{
"ansible_loop_var": "item",
"_ansible_no_log": false,
"invocation": {
"module_args": {
"wait_for_task": true,
"policy_package": "Mills07_Simplified",
"version": null,
"wait_for_task_timeout": 30
}
},
"item": "Mills07_Simplified",
"changed": false,
"msg": "Task Verify policy operation with task id 01234567-89ab-cdef-928b-bef7e174fc7a failed. Look at the logs for more details",
"_ansible_item_label": "Mills07_Simplified"
}
debug message
TASK [debug] *****************************************************************************************************************************************************************************
ok: [localhost] => {
"result": "Task Verify policy operation with task id 01234567-89ab-cdef-928b-bef7e174fc7a failed. Look at the logs for more details"
}
When I did the following,
- set_fact:
task_id: "{{ result |
select('regex', my_regex)|
first|
regex_replace(my_regex, my_replace) }}"
vars:
my_regex: '^Task Verify policy operation with task id (.*)$'
my_replace: '\1'
- debug:
var: task_id
I got an error message
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'my_regex' is undefined\n\nThe error appears to be in
Goal: I want to get the task-id "01234567-89ab-cdef-928b-bef7e174fc7a"
How can I get this string 01234567-89ab-cdef-928b-bef7e174fc7a
Since you are looking for a universally unique identifier (or UUID) which has a defined format of 8-4-4-4-12 characters for a total of 36 characters (32 hexadecimal characters and 4 hyphens) source, you can use a simple regex to extract it.
It can be handled with the following regex:
[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}
You can test it there: https://regex101.com/r/4Hs7il/1
So, in a set_fact:
- set_fact:
uuid: >-
{{ filter.msg
| regex_search(
'([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})',
'\1',
ignorecase=True
)
| first
}}
As an example:
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
uuid: >-
{{ filter.msg
| regex_search(
'([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})',
'\1',
ignorecase=True
)
| first
}}
vars:
filter:
msg: "Task Verify policy operation with task id 01234567-89ab-cdef-928b-bef7e174fc7a failed. Look at the logs for more details"
- debug:
var: uuid
Would yield the recap:
PLAY [localhost] ***************************************************************
TASK [set_fact] ****************************************************************
ok: [localhost]
TASK [debug] *******************************************************************
ok: [localhost] =>
uuid: 01234567-89ab-cdef-928b-bef7e174fc7a
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Create password and write it to file

The idea is to generate user accounts over an API. Using default variables as the basic information:
---
students:
- Username: testuser1
E-Mail: student1#student.com
- Username: testuser2
E-Mail: student2#student.com
The Creating User role will then create all users with the API:
- name: "Creating User"
uri:
url: "https://URL/api/v1/users"
method: POST
headers:
Content-Type: application/json
Authorization: AUTH TOKEN
body:
name: "{{ item['Username'] }}"
email: "{{ item['E-Mail'] }}"
password: "{{ item['Password'] }}"
body_format: json
validate_certs: no
loop: "{{ students }}"
I can not find a way to generate a password for each user and write them to a file. Is there a way I can append a Password variable to each student item before the Creating User role? If so I could just write the default variable to a file as a last role.
I've played with the password module. I do not want to have 100+ files with the passwords of the users. I need to have a singe file at the end with all information.
I'm not 100% sure I understood your question.
The below will take your actual user list, create a new one with a generated password for each and store the result in a single file for all users. Bonus: if the file exists, the var will be initialized from there bypassing the password creation.
Notes:
The below can be enhanced. You can put the block tasks for file creation in a separate file and use a conditional include so that the skipped loop iteration do not take place at all when the file already exists.
I obviously did not take security into account here and I strongly suggest you secure the way your file is stored.
Demo playbook:
---
- name: Create random passwords and store
hosts: localhost
gather_facts: false
vars:
users_with_pass_file: /tmp/test_pass.txt
students:
- Username: testuser1
E-Mail: student1#student.com
- Username: testuser2
E-Mail: student2#student.com
tasks:
- name: Try to load users and passwords from file if it exists
vars:
file_content: "{{ lookup('ansible.builtin.file', users_with_pass_file, errors='ignore') }}"
ansible.builtin.set_fact:
users_with_pass: "{{ file_content }}"
when:
- file_content | length > 0
# Unfortunately, there is no test 'is list' in jinja2...
- file_content is iterable
- file_content is not mapping
- file_content is not string
ignore_errors: true
changed_when: false
register: load_from_disk
- name: Create user list with passwords and store it if it does not exists (or is malformed...)
block:
- name: Create the list
vars:
user_password: "{{ lookup('ansible.builtin.password', '/dev/null', length=12) }}"
ansible.builtin.set_fact:
users_with_pass: "{{ users_with_pass | default([]) + [item | combine({'password': user_password})] }}"
loop: "{{ students }}"
- name: Store the result
ansible.builtin.copy:
dest: "{{ users_with_pass_file }}"
content: "{{ users_with_pass | to_json }}"
when: load_from_disk is skipped or load_from_disk is failed
- name: Show the result
ansible.builtin.debug:
var: users_with_pass
first run (with used ansible version):
$ ansible-playbook --version
ansible-playbook 2.10.1
config file = None
configured module search path = ['/home/user/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python3.6/dist-packages/ansible
executable location = /usr/local/bin/ansible-playbook
python version = 3.6.9 (default, Jul 17 2020, 12:50:27) [GCC 8.4.0]
$ ansible-playbook test.yml
PLAY [Create random passwords and store] ***********************************************************************************************************************************************************************************************
TASK [Try to load users and passwords from file if it exists] **************************************************************************************************************************************************************************
[WARNING]: Unable to find '/tmp/test_pass.txt' in expected paths (use -vvvvv to see paths)
skipping: [localhost]
TASK [Create the list] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'Username': 'testuser1', 'E-Mail': 'student1#student.com'})
ok: [localhost] => (item={'Username': 'testuser2', 'E-Mail': 'student2#student.com'})
TASK [Store the result] ****************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [Show the result] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"users_with_pass": [
{
"E-Mail": "student1#student.com",
"Username": "testuser1",
"password": "5l1RG7HzqaKMWJcH:mRH"
},
{
"E-Mail": "student2#student.com",
"Username": "testuser2",
"password": "tZvLT3LVj3_60GV_WoQd"
}
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Second run:
PLAY [Create random passwords and store] ***********************************************************************************************************************************************************************************************
TASK [Try to load users and passwords from file if it exists] **************************************************************************************************************************************************************************
ok: [localhost]
TASK [Create the list] *****************************************************************************************************************************************************************************************************************
skipping: [localhost] => (item={'Username': 'testuser1', 'E-Mail': 'student1#student.com'})
skipping: [localhost] => (item={'Username': 'testuser2', 'E-Mail': 'student2#student.com'})
TASK [Store the result] ****************************************************************************************************************************************************************************************************************
skipping: [localhost]
TASK [Show the result] *****************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"users_with_pass": [
{
"E-Mail": "student1#student.com",
"Username": "testuser1",
"password": "5l1RG7HzqaKMWJcH:mRH"
},
{
"E-Mail": "student2#student.com",
"Username": "testuser2",
"password": "tZvLT3LVj3_60GV_WoQd"
}
]
}
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
If you have diceware installed, then you can generate the password:
- name: generate new password
delegate_to: localhost
command: diceware --no-caps -d ' ' -w en_eff
register: generatedpass
Then you can store in the file:
- name: store password
copy:
dest: '/path/to/password/file'
content: |
{{ generatedpass }}
If I understand your question correctly, this should do the trick.

Ansible playbook to get all private Ip of my AWS environments

I'm running a scan against all the instances on my AWS using Ansible pLaybook . I need to get their Private IP and list them
I have tried to use json query to filter the Json format. The format output look like this..............
ok: [localhost] => {
"msg": [
{
"private_dns_name": "ip-10.89.3.12.ec2.internal",
"private_ip_address": "10.89.3.12",
"public_dns_name": "",
"public_ip_address": null,
},
- hosts: localhost
connection: local
gather_facts: yes
tasks:
- name: Gather EC2 remote facts.
ec2_remote_facts:
region: "{{ region | default('us-east-1') }}"
filters:
instance-state-name: running
register: ec2_remote_facts
- set_fact:
msg: "{{ ec2_remote_facts | json_query('results[*].instances[*].private_ip_address') }} "
- debug: var=msg
I expect the output to be list of private_IP only
I tried with "ec2_instance_facts" as below :
- hosts: localhost
connection: local
gather_facts: yes
tasks:
- name: Gather EC2 remote facts.
ec2_instance_facts:
filters:
availability-zone: ap-south-1b
register: ec2_instance_facts
- set_fact:
msg: "{{ ec2_instance_facts | json_query('instances[*].private_ip_address') }} "
- debug: var=msg
and below is the output :
PLAY [localhost] **************************************************************************************************************************************************************************************************
TASK [Gathering Facts] ********************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Gather EC2 remote facts.] ***********************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] ***************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ******************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"172.31.6.87"
]
}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Which is correct as per EC2 instance I had created.

Ansible loop over dictionaries

I was trying to loop over dictionaries and I referred the following thread but it kept on failing:
How to loop over this dictionary in Ansible?
Following is my play book:
- hosts: server_hosts
tasks:
- name: Include dictionary data
include_vars:
file: vars/input_vars.yaml
- name: Show info field from data.yml
debug:
msg: "Id: {{ input_data[item]['version'] }} - info: {{ input_data[item]['name'] }}"
with_items: "{{ input_data.keys() }}"
Following is my dictionary vars/input_vars.yaml file:
input_data:
item_1:
name: "test1"
version: "18.3"
item_2:
name: "test2"
version: "18.3"
item_3:
name: "test3"
version: "18.3"
When I executed the playbook, it fails with following error:
fatal: [192.168.16.120]: FAILED! => {
"ansible_facts": {},
"ansible_included_var_files": [],
"changed": false,
"message": "Syntax Error while loading YAML.\n mapping values are not allowed here\n\nThe error appears to have been in '/git_projects/base/vars/input_vars.yaml': line 2, column 12, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n----\n input_data:\n ^ here\n"
}
I have tested it and it works, I have removed extra newlines from input_vars.yaml
➜ ~ cat input_vars.yaml
input_data:
item_1:
name: "test1"
version: "18.3"
item_2:
name: "test2"
version: "18.3"
item_3:
name: "test3"
version: "18.3"
➜ ~ cat example.yml
---
- hosts: localhost
tasks:
- name: Include dictionary data
include_vars:
file: input_vars.yaml
- name: Show info field from data.yml
debug:
msg: "Id: {{ input_data[item]['version'] }} - info: {{ input_data[item]['name'] }}"
with_items: "{{ input_data.keys() }}"
Output
➜ ~ ansible-playbook example.yml
[WARNING]: Unable to parse /etc/ansible/hosts as an inventory source
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
PLAY [localhost] ***********************************************************************************************************************
TASK [Gathering Facts] *****************************************************************************************************************
ok: [localhost]
TASK [Include dictionary data] *********************************************************************************************************
ok: [localhost]
TASK [Show info field from data.yml] ***************************************************************************************************
ok: [localhost] => (item=item_2) => {
"msg": "Id: 18.3 - info: test2"
}
ok: [localhost] => (item=item_3) => {
"msg": "Id: 18.3 - info: test3"
}
ok: [localhost] => (item=item_1) => {
"msg": "Id: 18.3 - info: test1"
}
PLAY RECAP *****************************************************************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0

Resources