How can I search json output from tasks - ansible

I have a playbook pulling users and groups from windows 2012 using powershell and I convert it to json.
I get the following output
[
{
"Group": "Appsupp",
"Members": [
"WIN-U97DIQUENUM\\appsuport",
"WIN-U97DIQUENUM\\userfirst"
]
},
{
"Group": "DBAdministrators",
"Members": {}
},
{
"Group": "Techsupp",
"Members": {}
},
{
"Group": "Access Control Assistance Operators",
"Members": {}
},
{
"Group": "Administrators",
"Members": "WIN-U97DIQUENUM\\Administrator"
},
{
"Group": "Backup Operators",
"Members": {}
},
{
"Group": "Certificate Service DCOM Access",
"Members": {}
},
{
"Group": "Cryptographic Operators",
"Members": {}
},
{
"Group": "Distributed COM Users",
"Members": {}
},
{
"Group": "Event Log Readers",
"Members": {}
},
{
"Group": "Guests",
"Members": "WIN-U97DIQUENUM\\Guest"
},
{
"Group": "Hyper-V Administrators",
"Members": {}
},
{
"Group": "IIS_IUSRS",
"Members": "NT AUTHORITY\\IUSR"
},
{
"Group": "Network Configuration Operators",
"Members": {}
},
{
"Group": "Performance Log Users",
"Members": {}
},
{
"Group": "Performance Monitor Users",
"Members": {}
},
{
"Group": "Power Users",
"Members": {}
},
{
"Group": "Print Operators",
"Members": {}
},
{
"Group": "RDS Endpoint Servers",
"Members": {}
},
{
"Group": "RDS Management Servers",
"Members": {}
},
{
"Group": "RDS Remote Access Servers",
"Members": {}
},
{
"Group": "Remote Desktop Users",
"Members": {}
},
{
"Group": "Remote Management Users",
"Members": {}
},
{
"Group": "Replicator",
"Members": {}
},
{
"Group": "Users",
"Members": [
"NT AUTHORITY\\Authenticated Users",
"NT AUTHORITY\\INTERACTIVE",
"WIN-U97DIQUENUM\\userfirst",
"WIN-U97DIQUENUM\\usersecond",
"WIN-U97DIQUENUM\\appsupport",
"WIN-U97DIQUENUM\\techsupport",
"WIN-U97DIQUENUM\\sqlserveruser"
]
}
]
I would like to have user compared to that data I pulled from the server. For example, I want to know if appsupport user exists and which group it belongs to.
I have tried the below json_query. FYI, getgroup.stdout is the registered result from the task pulling info from the server.
- debug:
msg: "{{ getgroup.stdout | from_json | json_query('guser') }}"
vars:
guser: "[?Members.contains(#, `appsupport`)].Group"
What I get is an empty result (while this method worked with other json). What am I doing wrong?
I assume that the problem is the Members key in my object as it my contain an empty dictionnary, a single string or a list of strings as recapped in the chosen examples below.
Single string:
{
"Group": "IIS_IUSRS",
"Members": "NT AUTHORITY\\IUSR"
},
List:
{
"Group": "Users",
"Members": [
"NT AUTHORITY\\Authenticated Users",
"NT AUTHORITY\\INTERACTIVE",
"WIN-U97DIQUENUM\\userfirst",
"WIN-U97DIQUENUM\\usersecond",
"WIN-U97DIQUENUM\\appsupport",
"WIN-U97DIQUENUM\\techsupport",
"WIN-U97DIQUENUM\\sqlserveruser"
]
}
Empty dict:
{
"Group": "Replicator",
"Members": {}
}
When I use json_query('[].Members[*]') it only returns the latest member, not all Members.
How can I select the objects from my json result having a particular user in the Members field in this kind of situation?

Without json_query.
For testing, given the output in the file, read it and convert the Members to lists, e.g.
- set_fact:
output: "{{ lookup('file', 'test.data')|from_yaml }}"
- set_fact:
mlist: "{{ mlist|d([]) +
[item|combine({'Members': _Members|from_yaml})] }}"
loop: "{{ output }}"
vars:
_Members: |-
{% if item.Members is mapping %}
{{ item.Members.keys()|list }}
{% elif item.Members is string %}
[{{ item.Members }}]
{% else %}
{{ item.Members }}
{% endif %}
- debug:
var: mlist
gives
mlist:
- Group: Appsupp
Members:
- WIN-U97DIQUENUM\appsuport
- WIN-U97DIQUENUM\userfirst
- Group: DBAdministrators
Members: []
- Group: Techsupp
Members: []
- Group: Access Control Assistance Operators
Members: []
- Group: Administrators
Members:
- WIN-U97DIQUENUM\Administrator
- Group: Backup Operators
Members: []
- Group: Certificate Service DCOM Access
Members: []
- Group: Cryptographic Operators
Members: []
- Group: Distributed COM Users
Members: []
- Group: Event Log Readers
Members: []
- Group: Guests
Members:
- WIN-U97DIQUENUM\Guest
- Group: Hyper-V Administrators
Members: []
- Group: IIS_IUSRS
Members:
- NT AUTHORITY\IUSR
- Group: Network Configuration Operators
Members: []
- Group: Performance Log Users
Members: []
- Group: Performance Monitor Users
Members: []
- Group: Power Users
Members: []
- Group: Print Operators
Members: []
- Group: RDS Endpoint Servers
Members: []
- Group: RDS Management Servers
Members: []
- Group: RDS Remote Access Servers
Members: []
- Group: Remote Desktop Users
Members: []
- Group: Remote Management Users
Members: []
- Group: Replicator
Members: []
- Group: Users
Members:
- NT AUTHORITY\Authenticated Users
- NT AUTHORITY\INTERACTIVE
- WIN-U97DIQUENUM\userfirst
- WIN-U97DIQUENUM\usersecond
- WIN-U97DIQUENUM\appsupport
- WIN-U97DIQUENUM\techsupport
- WIN-U97DIQUENUM\sqlserveruser
Now, you can search for the members, e.g.
- debug:
msg: "{{ mlist|selectattr('Members', 'contains', my_user) }}"
vars:
my_user: 'WIN-U97DIQUENUM\Administrator'
gives
msg:
- Group: Administrators
Members:
- WIN-U97DIQUENUM\Administrator
Next, create a dictionary of all members and the groups they belong to
- set_fact:
members: "{{ members|d({})|combine({item: _groups}) }}"
loop: "{{ mlist|map(attribute='Members')|flatten|unique }}"
vars:
_groups: "{{ mlist|selectattr('Members', 'contains', item)|
map(attribute='Group')|
list }}"
- debug:
var: members
gives
members:
NT AUTHORITY\Authenticated Users:
- Users
NT AUTHORITY\INTERACTIVE:
- Users
NT AUTHORITY\IUSR:
- IIS_IUSRS
WIN-U97DIQUENUM\Administrator:
- Administrators
WIN-U97DIQUENUM\Guest:
- Guests
WIN-U97DIQUENUM\appsuport:
- Appsupp
WIN-U97DIQUENUM\appsupport:
- Users
WIN-U97DIQUENUM\sqlserveruser:
- Users
WIN-U97DIQUENUM\techsupport:
- Users
WIN-U97DIQUENUM\userfirst:
- Appsupp
- Users
WIN-U97DIQUENUM\usersecond:
- Users
Then, the searching is trivial, e.g.
- debug:
msg: "{{ my_user }} is member of the group(s): {{ members[my_user] }}"
vars:
my_user: 'WIN-U97DIQUENUM\Administrator'
gives
msg: 'WIN-U97DIQUENUM\Administrator is member of the group(s): [''Administrators'']'
It's possible to search among the members, e.g.
- debug:
msg: "{{ my_user }} is member of the group(s): {{ _groups }}"
vars:
my_user: appsuport
_keys: "{{ members.keys()|list|select('search', my_user)|list }}"
_groups: "{{ _keys|map('extract', members)|flatten }}"
gives
msg: 'appsuport is member of the group(s): [''Appsupp'']'

I see multiple problems here:
Problem 1:
Your data has three different types for Members, depending on what is in there:
An empty dict ({}) if there are no members
A string, if there is exactly one member
A list of strings, if there are multiple members
As far as I can see, this will not break, but it is not particularly good style. You should always return a list. An empty one, if there is no member, or a list with one element if there is exactly one member or a list of multiple strings, as you already do, if there are multiple members.
Problem 2:
In group Appsupp, the user is misspelled: WIN-U97DIQUENUM\\appsuport (there is a 'p' missing in 'support'), so this will not match.
Problem 3:
This is what actually breaks here. Your query has several problems, it should look like this: [?Members[?contains(#, 'appsupport')]].Group
The result is
[
"Users"
]
Appsupp is not in there due to the spelling mistake mentioned earlier.
You can test your json-path on this website.

Related

Ansible nested loop

Is it possible for Ansible to do a nested loop as I want to do a filtering and set fact.
The first fact that I have is
"username_and_role": [
{
"Name": "Jack",
"Role": "Admin User"
},
{
"Name": "Tom",
"Role": "Buyer"
},
{
"Name": "Jill",
"Role": "Seller"
}
]
The second fact that I have is
"username_and_status": [
{
"isLockedOut": "no",
"Name": "Jack"
},
{
"isLockedOut": "no",
"Name": "Tom"
},
{
"isLockedOut": "no",
"Name": "Jill"
},
{
"isLockedOut": "no",
"Name": "Brody"
},
{
"isLockedOut": "no",
"Name": "Steve"
}
]
My objective is that I want to retrieve isLockedOut from second data. If name in username_and_role is equal to name in username_and_status, retrieve the isLockedOut from username and status. and set a fact that show name, role and isLockedOut.
I've tried a few methods and it doesn't work as it didn't do the checking.
set_fact:
my_new_list: "{{[username_and_role, {'status': nameIsMatch}] }}"
cacheable: yes
with_items: "{{username_and_role}}"
vars:
nameIsMatch: "{% if item.Name == username%}item.isLockedOut{% else %}Name Does not Match{%endif %}"
loop: "{{username_and_status}}"
This is code has no error but it just did not do the checking between username_and_role & username_and_status
Convert the lists to dictionaries, e.g.
- set_fact:
username_and_role: "{{ username_and_role|
items2dict(key_name='Name',
value_name='Role') }}"
gives
username_and_role:
Jack: Admin User
Jill: Seller
Tom: Buyer
- set_fact:
username_and_status: "{{ username_and_status|
items2dict(key_name='Name',
value_name='isLockedOut') }}"
gives
username_and_status:
Brody: 'no'
Jack: 'no'
Jill: 'no'
Steve: 'no'
Tom: 'no'
Now you can retrieve the status by using the dictionary, e.g.
- debug:
msg: >
name: {{ item }}
role: {{ username_and_role[item]|d('NA') }}
status: {{ username_and_status[item]|d('NA') }}
loop: "{{ username_and_status.keys()|list }}"
gives
msg: |-
name: Jack role: Admin User status: no
msg: |-
name: Tom role: Buyer status: no
msg: |-
name: Jill role: Seller status: no
msg: |-
name: Brody role: NA status: no
msg: |-
name: Steve role: NA status: no
just to precise the nice answer of #vladimir-botka, if you just want the persons which are in both variables,
you could add a condition when
- debug:
msg: >
name: {{ item }}
role: {{ username_and_role[item] }}
status: {{ username_and_status[item] }}
when: username_and_role[item] is defined and username_and_status[item] is defined
loop: "{{ username_and_status.keys()|list }}"
- debug:
var: username_and_role | to_nice_json
result:
ok: [localhost] =>
username_and_role | to_nice_json: |-
{
"Jack": "Admin User",
"Jill": "Seller",
"Tom": "Buyer"
}

How to pull deeply nested data from a dictionary in ansible

I am trying to print nested data from an ansible dictionary. However, I cannot figure out how to pull the nested values. Specifically, in the data below, I am trying to print the two values returned under "ipv4". Currently, the output of item.value.ipv4 is the portion in brackets below:
"msg": "GigabitEthernet0/0/0,[{'address': '192.168.3.1', 'subnet': '28'}]"
I would like to just use the value like so:
"msg": "GigabitEthernet0/0/0, 192.168.3.1, 28"
I cannot figure out how to pull this nested data out of to do this. To put it simply, it would be nice if something like this worked: item.value.ipv4['address']. How is this done?
tasks:
- name: get config for Cisco IOS
ios_facts:
gather_subset: all
gather_network_resources: interfaces
- name: create dictionary with ansible_net_interfaces
set_fact:
foo_value: "{{ query('dict', ansible_net_interfaces) }}"
- name: display the results of foo_value
debug:
msg: "{{ foo_value }}"
- name: display certain detalys values from foo_value
debug:
msg: "{{ item.key }},{{ item.value.ipv4 }}"
with_items: "{{ foo_value }}"
These tasks produce the following:
TASK [display the results of foo] **********************************************************************************************************************
ok: [192.168.3.1] => {
"msg": [
{
"key": "GigabitEthernet0/0/0",
"value": {
"bandwidth": 1000000,
"description": "This is an interface description",
"duplex": "Full",
"ipv4": [
{
"address": "192.168.3.1",
"subnet": "28"
}
],
"lineprotocol": "up",
"macaddress": "50f7.123c.b0c0",
"mediatype": "RJ45",
"mtu": 1500,
"operstatus": "up",
"type": "ISR4331-3x1GE"
}
},
{
"key": "GigabitEthernet0/0/1",
"value": {
"bandwidth": 1000000,
"description": "This is another interface description",
"duplex": "Full",
"ipv4": [
{
"address": "192.168.3.33",
"subnet": "30"
}
],
"lineprotocol": "up",
"macaddress": "50f7.123c.b0c0",
"mediatype": "RJ45",
"mtu": 1500,
"operstatus": "up",
"type": "ISR4331-3x1GE"
}
},
]
}
ipv4 is a list of dictionaries. Assuming you need only the first dictionary,
- name: display certain detalys values from foo_value
debug:
msg: "{{ item.key }},{{ item.value.ipv4[0].address }},{{ item.value.ipv4[0].subnet }}"
when: item.value.ipv4 is defined and item.value.ipv4[0].subnet is defined and item.value.ipv4[0].address is defined
with_items: "{{ foo_value }}"

jinja2 Ansible filter dictionary

I've been doing ansible for a while, but have recently started doing some more advanced things such as pulling data to drive actions from outside sources. This has resulted in me having to dig a little deeper into how ansible allows logic and parsing of variables, requiring me to dig a little into jinja2.
In my playbook I am attempting to pull data in from etcd, allowing me to construct authorized sudo spec files, which I then pass onto a role to add to the appropriate systems.
My datasource, in addition to storing data that is needed to construct the specs has metadata used for auditing and logging purposes.
A key aspect of my datasource is that no metadata, i.e. that user XYZ had password less sudo for a period of 10 days, should be deleted when access is removed. So many aspects have a state field that may be active, inactive or in the case of sudo specs grant or revoke.
I have successfully constructed a lookup that pulls back a dictionary similar to below - which I then parse using the subsequent ansible statements. I am able to succcessfully process and extract all data except for groups/users that have their specs in a grant state.
When specs are in a "grant" state, I need to extract the linuxName field, to be passed onto the role that configures sudo.
I have tried a number of variations of filters, most of which end in me getting a reject or similar message, or a NULL value instead of the list of values that I want.
Anyone have an idea on how to accomplish this?
Thanks in advance.
Sample Data
ok: [serverName] => {
"sudoInfraSpecs": [
{
"infra_admins": {
"addedBy": "someUser",
"commands": "FULL_SUDO",
"comment": "platform support admins",
"dateAdded": "20180720",
"defaults": "!requiretty",
"hosts": "SERVERS",
"name": "infra_admins",
"operators": "ROOT",
"state": "active",
"tags": "PASSWD",
"users": {
"admingroup1": {
"addedBy": "someUser",
"dateAdded": "20180719",
"linuxName": "%admingroup1",
"name": "admingroup1",
"state": "grant"
},
"admingroup2": {
"addedBy": "someUser",
"dateAdded": "20180719",
"linuxName": "%admingroup2",
"name": "admingroup2",
"state": "grant"
}
}
},
"ucp_service_account": {
"addedBy": "someUser",
"commands": "FULL_SUDO",
"comment": "platform service account",
"dateAdded": "20180720",
"defaults": "!requiretty",
"hosts": "SERVERS",
"name": "platform_service_account",
"operators": "ROOT",
"state": "active",
"tags": "NOPASSWD,LOG_OUTPUT",
"users": {
"platformUser": {
"addedBy": "someUser",
"dateAdded": "20180719",
"linuxName": "platformUser",
"name": "platformUser",
"state": "grant"
}
}
}
}
]
}
Ansible snippet
- name: Translate infraAdmins sudoers specs from etcd into a list for processing [1]
set_fact:
tempInfraSpecs:
name: "{{ item.value.name}}"
comment: "{{ item.value.comment }}"
users: "{{ item.value.users | list }}"
hosts: "{{ item.value.hosts.split(',') }}"
operators: "{{ item.value.operators.split(',') }}"
tags: "{{ item.value.tags.split(',') }}"
commands: "{{ item.value.commands.split(',') }}"
defaults: "{{ item.value.defaults.split(',') }}"
with_dict: "{{ sudoInfraSpecs }}"
when: item.value.state == 'active'
register: tempsudoInfraSpecs
- name: Translate infraAdmins sudoers specs from etcd into a list for processing [2]
set_fact:
sudoInfraSpecs_fact: "{{ tempsudoInfraSpecs.results | selectattr('ansible_facts','defined')| map(attribute='ansible_facts.tempInfraSpecs') | list }}"
Rough Desired output dictionary:
sudoInfraSpecs:
- infra_admins:
addedBy: someUser
commands: FULL_SUDO
comment: platform support admins
dateAdded: '20180720'
defaults: "!requiretty"
hosts: SERVERS
name: infra_admins
operators: ROOT
state: active
tags: PASSWD
users:
"%admingroup1"
"%admingroup2"
- ucp_service_account:
addedBy: someUser
commands: FULL_SUDO
comment: platform service account
dateAdded: '20180720'
defaults: "!requiretty"
hosts: SERVERS
name: platform_service_account
operators: ROOT
state: active
tags: NOPASSWD,LOG_OUTPUT
users:
"platformUser"
I ended up doing this by creating a custom filter for use in my playbook that parsed the nested dictionary that makes up users:
#!/usr/bin/python
def getSpecActiveMembers(my_dict):
thisSpecActiveMembers = []
for i, value in my_dict.iteritems():
if value['state'] == 'grant':
thisSpecActiveMembers.append(value['linuxName'])
return thisSpecActiveMembers
class FilterModule(object):
def filters(self):
return {
'getSpecActiveMembers': getSpecActiveMembers
}
This ends up flattening the users from the source listed above, to the desired output.

how to access ansible newly created dictionary

I am following the example here http://docs.ansible.com/ansible/iam_policy_module.html to try to create a new AWS IAM user and then use this new user's username to attach an IAM policy to it.
The example:
task:
- name: Create Two Groups, Mario and Luigi
iam:
iam_type: group
name: "{{ item }}"
state: present
with_items:
- Mario
- Luigi
register: new_groups
- name: Apply READ-ONLY policy to new groups that have been recently created
iam_policy:
iam_type: group
iam_name: "{{ item.created_group.group_name }}"
policy_name: "READ-ONLY"
policy_document: readonlypolicy.json
state: present
with_items: "{{ new_groups.results }}"
I have adapted that to work with one user:
- hosts: 127.0.0.1
gather_facts: no
connection: local
tasks:
- name: Create user lamda_ecr_delete
iam:
iam_type: user
name: "{{ item }}"
state: present
with_items:
- lambda_ecr_delete
register: new_user
- name: Apply ecr delete policy to newly created user
iam_policy:
iam_type: user
iam_name: "{{ item.created_user.user_name }}"
policy_name: "lambda_ecr_delete"
policy_document: assets/aws-policies/lambda_ecr_delete.json
state: present
with_items: "{{ new_user.results }}"
But when I try to retrieve the username in the dictionary, item.created_user does not exist.
When I use debug to see the content of {{ new_user.results }} I can identify that it's a python list that contains a dict so I can probably access it with [0] and then call invocation.module_args.name which is a valid key.
This is the output for debug: msg="{{ new_user.results }}" when run with --check:
ok: [127.0.0.1] => {
"changed": false,
"msg": [
{
"_ansible_item_result": true,
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": false,
"invocation": {
"module_args": {
"iam_type": "user",
"name": "lambda_ecr_delete",
"state": "present"
}
},
"item": "lambda_ecr_delete",
"msg": "remote module (iam) does not support check mode",
"skipped": true
}
]
}
But that seems hackish. Is there a shortcut to access those module_args directly? Something as shown in the example with a .created_user?
Use item.user_meta.created_user.user_name instead.
You could note that created_user is nested into user_meta if you inspect debug output of new_user.results. Looks like:
"user_meta": {
"access_keys": null,
"created_user": {
"arn": "arn:aws:iam::<yourid>:user/test-ansible",
"create_date": "2017-04-03T16:31:53.530Z",
"path": "/",
"user_id": "EXAMPLEKAJHFEXAMPLE",
"user_name": "test-ansible"
},
"password": null
}
But be warned that on the second run iam module returns different output:
"user_name": "test-ansible"
instead of user_meta dictionary.

Get Volume group attributes to be used in condition using Ansible

I would like to be able to get list of volume groups available and then use the attributes of the volume group to conditionally create the filesystem on volume which meets the Criteria
Criteria
1: Volume must not have name root in it
2: Volume must be at-least 2 GB
I have this logic
- name: list volume groups
debug: msg="echo volume groups is {{ item }}"
with_items: "{{ ansible_lvm.vgs }}
But am struggling to get that attribute of the vgs.
Here is snippet of ansible_lvm
"ansible_lvm": {
"lvs": {
"lvud01": {
"size_g": "0.50",
"vg": "vgud01"
},
"root": {
"size_g": "79.01",
"vg": "precise32"
},
"swap_1": {
"size_g": "0.75",
"vg": "precise32"
}
},
"vgs": {
"precise32": {
"free_g": "0",
"num_lvs": "2",
"num_pvs": "1",
"size_g": "79.76"
},
"vgud01": {
"free_g": "0.50",
"num_lvs": "1",
"num_pvs": "1",
"size_g": "1.00"
}
}
Just for everyones benefit and thanks #fishi
- name: list volume groups
debug: msg="echo volume groups is {{ item.key }} has {{ item.value.free_g }}"
with_dict: "{{ ansible_lvm.vgs }}"
when: not item.key == "root"
For those like me taken here by Google and wondering where this ansible_lvm dict comes from :
it is part of the facts gathered by the setup module
it is collected only when executed with super-user privileges
In other words, to get ansible_lvm :
with an ad-hoc command (the ✨magic✨ lies in the -b) :
ansible myManagedNode -m setup -kK -b
within a playbook :
- hosts: myManagedNode
become: yes
tasks:
- debug:
var: ansible_lvm

Resources