Ansible: Extract json objects from file based on a list - ansible

I have a json file like so
{
"service_accounts": {
"sql-automation": "Automation Service account for Cloud SQL Instances",
"gke-proxy": "GKE proxy account",
"gke-node": "Default GKE nodes service account",
"syncdata": "Data sync between environments",
}
}
Then I have another json file with the default access the accounts need (this is a template file)
{
"key_gen_auths": {
"worker": [{
"role": "roles/iam.serviceAccountKeyAdmin",
"members": [
"serviceAccount:dataprovisioning#{{ project_id }}.iam.gserviceaccount.com"
]
}],
"gke-node": [{
"role": "roles/iam.serviceAccountUser",
"members": [
"serviceAccount:deployment-automation#{{ project_id }}.iam.gserviceaccount.com"
]
}],
"sql-automation": [{
"role": "roles/iam.serviceAccountUser",
"members": [
"serviceAccount:deployment-automation#{{ project_id }}.iam.gserviceaccount.com"
]
}]
}
}
Then in the playbook, i would like to say:
If the service account exists in the service_accounts file, extract that object from key_gen_auths if it exists there (if it doesnt exist in key_gen_auths ignore). Ive looked at several options, like selectattr, combine, difference, but im no further than when i started really, and wonder if im just better with a when statement if i can find the right syntax, any help would be appreciated.
- name: Play for reading json
hosts: localhost
vars:
project_id: 123
tasks:
- name: acct_list
set_fact:
acct_list: "{{ lookup('template', 'acct_list.json') }}"
- name: key_auth_default
set_fact:
keyauth_default: "{{ lookup('template', 'key_auths.json') }}"
- name:
set_fact:
access_file: "{{ item }}"
with_items: "{{ keyauth_default.key_gen_auths }}"
when: item in acct_list.service_accounts
- debug:
var: access_file

You can read the files without lookup
vars_files:
- acct_list.json
- key_auths.json
vars:
project_id: 123
give
service_accounts:
gke-node: Default GKE nodes service account
gke-proxy: GKE proxy account
sql-automation: Automation Service account for Cloud SQL Instances
syncdata: Data sync between environments
key_gen_auths:
gke-node:
- members:
- serviceAccount:deployment-automation#123.iam.gserviceaccount.com
role: roles/iam.serviceAccountUser
sql-automation:
- members:
- serviceAccount:deployment-automation#123.iam.gserviceaccount.com
role: roles/iam.serviceAccountUser
worker:
- members:
- serviceAccount:dataprovisioning#123.iam.gserviceaccount.com
role: roles/iam.serviceAccountKeyAdmin
Q: "If the service account exists in service_accounts extract that object from key_gen_auths."
A: Get the lists of the keys from the dictionaries. Then the intersect of these lists gives the existing service accounts
service_accounts_list: "{{ service_accounts.keys()|list }}"
key_gen_auths_list: "{{ key_gen_auths.keys()|list }}"
service_account_exists: "{{ service_accounts_list|intersect(key_gen_auths_list) }}"
give
service_accounts_list: [sql-automation, gke-proxy, gke-node, syncdata]
key_gen_auths_list: [worker, gke-node, sql-automation]
service_account_exists: [sql-automation, gke-node]
Use the list of existing accounts to extract the objects and create a dictionary
access_file: "{{ dict(service_account_exists|
zip(service_account_exists|map('extract', key_gen_auths))) }}"
give
access_file:
gke-node:
- members:
- serviceAccount:deployment-automation#123.iam.gserviceaccount.com
role: roles/iam.serviceAccountUser
sql-automation:
- members:
- serviceAccount:deployment-automation#123.iam.gserviceaccount.com
role: roles/iam.serviceAccountUser
Example of the complete playbook for testing
- hosts: localhost
vars_files:
- acct_list.json
- key_auths.json
vars:
project_id: 123
service_accounts_list: "{{ service_accounts.keys()|list }}"
key_gen_auths_list: "{{ key_gen_auths.keys()|list }}"
service_account_exists: "{{ service_accounts_list|intersect(key_gen_auths_list) }}"
access_file: "{{ dict(service_account_exists|
zip(service_account_exists|map('extract', key_gen_auths))) }}"
tasks:
- debug:
var: service_accounts
- debug:
var: key_gen_auths
# If the service account exists in service_accounts
# extract those objects from key_gen_auths
- debug:
var: service_accounts_list|to_yaml
- debug:
var: key_gen_auths_list|to_yaml
- debug:
var: service_account_exists|to_yaml
- debug:
var: access_file

Well, I think this shows the data you want, in debug, and you can use that with set_fact as you see fit:
- name: Play for reading json
hosts: localhost
vars_files:
- acct_list.json
- key_auths.json
vars:
project_id: 123
tasks:
- debug:
msg: "{{ key_gen_auths[item] | default('Not Found') }}"
loop: "{{ service_accounts.keys() }}"
ignore_errors: yes

Related

Ansible when condition registered from csv

I'm using csv file as ingest data for my playbooks, but im having trouble with my when condition. it's either both task will skipped or both task will be ok, my objective is if ansible see the string in when condition it will skipped for the specific instance.
here is my playbook
- name: "Read ingest file from CSV return a list"
community.general.read_csv:
path: sample.csv
register: ingest
- name: debug ingest
debug:
msg: "{{ item.AWS_ACCOUNT }}"
with_items:
- "{{ ingest.list }}"
register: account
- name: debug account
debug:
msg: "{{ account.results | map(attribute='msg') }}"
register: accountlist
- name:
become: yes
become_user: awx
delegate_to: localhost
environment: "{{ proxy_env }}"
block:
- name: "Assume role"
community.aws.sts_assume_role:
role_arn: "{{ item.ROLE_ARN }}"
role_session_name: "pm"
with_items:
- "{{ ingest.list }}"
register: assumed_role
when: "'aws-account-rnd' not in account.results | map(attribute='msg')"
here is the content of sample.csv
HOSTNAME
ENVIRONMENT
AWS_ACCOUNT
ROLE_ARN
test1
dev
aws-account-rnd
arn:aws:iam::XXXX1
test2
uat
aws-account-uat
arn:aws:iam::XXXX2
my objective is to skipped all items in the csv file with aws-acount-rnd
Your condition does not mention item so it will have the same result for all loop items.
Nothing you've shown requires the weird abuse of debug + register that you're doing, and it is in fact getting in your way.
- name: Read CSV file
community.general.read_csv:
path: sample.csv
register: ingest
- name: Assume role
community.aws.sts_assume_role:
role_arn: "{{ item.ROLE_ARN }}"
role_session_name: pm
delegate_to: localhost
become: true
become_user: awx
environment: "{{ proxy_env }}"
loop: "{{ ingest.list }}"
when: item.AWS_ACCOUNT != 'aws-account-rnd'
register: assumed_role
If you'll always only care about one match you can also do this without a loop or condition at all:
- name: Assume role
community.aws.sts_assume_role:
role_arn: "{{ ingest.list | rejectattr('AWS_ACCOUNT', '==', 'aws-account-rnd') | map(attribute='ROLE_ARN') | first }}"
role_session_name: pm
delegate_to: localhost
become: true
become_user: awx
environment: "{{ proxy_env }}"
register: assumed_role
my objective is to skipped all items in the csv file with aws-acount-rnd
The multiple debug you have with register, seems to be a long-winded approach IMHO.
A simple task to debug the Role ARN, only if the account does not match aws-acount-rnd.
- name: show ROLE_ARN when account not equals aws-account-rnd
debug:
var: item['ROLE_ARN']
loop: "{{ ingest.list }}"
when: item['AWS_ACCOUNT'] != 'aws-account-rnd'
This results in:
TASK [show ROLE_ARN when account not equals aws-account-rnd] **********************************************************************************************************************
skipping: [localhost] => (item={'HOSTNAME': 'test1', 'ENVIRONMENT': 'dev', 'AWS_ACCOUNT': 'aws-account-rnd', 'ROLE_ARN': 'arn:aws:iam:XXXX1'})
ok: [localhost] => (item={'HOSTNAME': 'test2', 'ENVIRONMENT': 'uat', 'AWS_ACCOUNT': 'aws-account-uat', 'ROLE_ARN': 'arn:aws:iam:XXXX2'}) => {
"ansible_loop_var": "item",
"item": {
"AWS_ACCOUNT": "aws-account-uat",
"ENVIRONMENT": "uat",
"HOSTNAME": "test2",
"ROLE_ARN": "arn:aws:iam:XXXX2"
},
"item['ROLE_ARN']": "arn:aws:iam:XXXX2"
}
The same logic can be used to pass the item.ROLE_ARN to community.aws.sts_assume_role task.

Ansible how to remove groups value by key

I am having a play where i will collect available host names before running a task, i am using this for a purpose,
My play code:
--
- name: check reachable side A hosts
hosts: ????ha???
connection: local
gather_facts: no
roles:
- Juniper.junos
vars:
credentials:
host: "{{ loopback_v4 }}"
username: "test"
ssh_keyfile: "/id_rsa"
port: "{{ port }}"
timeout: 60
tasks:
- block:
- name: "Check netconf connectivity with switches"
juniper_junos_ping:
provider: "{{ credentials }}"
dest: "{{ loopback_v4 }}"
- name: Add devices with connectivity to the "reachable" group
group_by:
key: "reachable_other_pairs"
rescue:
- debug: msg="Cannot ping to {{inventory_hostname}}. Skipping OS Install"
When i print this using
- debug:
msg: "group: {{ groups['reachable_other_pairs'] }}"
i am getting below result
"this group : ['testha1', 'testha2', 'testha3']",
Now if again call the same play with different hosts grouping with the same key i am getting the new host names appending to the existing values, like below
- name: check reachable side B hosts
hosts: ????hb???
connection: local
gather_facts: no
roles:
- Juniper.junos
vars:
credentials:
host: "{{ loopback_v4 }}"
username: "test"
ssh_keyfile: "/id_rsa"
port: "{{ port }}"
timeout: 60
tasks:
- block:
- name: "Check netconf connectivity with switches"
juniper_junos_ping:
provider: "{{ credentials }}"
dest: "{{ loopback_v4 }}"
- name: Add devices with connectivity to the "reachable" group
group_by:
key: "reachable_other_pairs"
rescue:
- debug: msg="Cannot ping to {{inventory_hostname}}. Skipping OS Install"
if i print the reachable_other_pairs i am getting below results
"msg": " new group: ['testhb1', 'testhb2', 'testhb3', 'testha1', 'testha2', 'testha3']"
All i want is only first 3 entries ['testhb1', 'testhb2', 'testhb3']
Can some one let me know how to achieve this?
Add this as as task just before your block. It will refresh your inventory and clean up all groups that are not in there:
- meta: refresh_inventory

"search" a string in "when:" conditional statement?

In the following playbook, I have to use search in a when: statement. Can you please tell me what is wrong with the statement when: item.name is search (domain_list)? domain_list is an array variable defined in files.yml as shown below.
---
- hosts: localhost
gather_facts: False
connection: local
vars_files:
- files.yml
vars:
infra:
- name: ironman.vcloud-lab.com
connection_state: CONNECTED
- name: hulk.vcloud-lab.com
connection_state: CONNECTED
- name: captain.vcloud-lab.com
connection_state: DISCONNECTED
- name: hawkeye.vcloud-lab.com
connection_state: DISCONNECTED
tasks:
- name: Filter list of only connected esxi
set_fact:
esxilist: "{{esxilist | default([]) + [item]}}"
with_items: "{{infra}}"
when: item.name is search (domain_list) ## <= please correct me here, it doesn't work
- name: Show connected esxi list information
debug:
var: esxilist
files.yml
---
domain_list:
- ironman
- captain
Select the short name and test the presence in the list. For example, use regex_replace
- name: Filter list of only connected esxi
set_fact:
esxilist: "{{ esxilist|default([]) + [item] }}"
loop: "{{ infra }}"
when: _short in domain_list
vars:
_short: "{{ item.name|regex_replace('^(.*?)\\.(.*)$', '\\1') }}"
gives
esxilist:
- connection_state: CONNECTED
name: ironman.vcloud-lab.com
- connection_state: DISCONNECTED
name: captain.vcloud-lab.com
The next option is to split the string and select the first item. For example
vars:
_short: "{{ item.name.split('.')|first }}"
gives the same result.
See How to create a Minimal, Reproducible Example. For example
- hosts: localhost
vars:
domain_list: [a, b]
infra:
- {name: a.vcloud-lab.com, state: CONNECTED}
- {name: b.vcloud-lab.com, state: DISCONNECTED}
- {name: c.vcloud-lab.com, state: CONNECTED}
tasks:
- set_fact:
l1: "{{ l1|default([]) + [item] }}"
with_items: "{{ infra }}"
when: _short in domain_list
vars:
_short: "{{ item.name.split('.')|first }}"
- debug:
var: l1

How to create a list of dictionary of users and list of folders for each user?

I would like to create a dictionary of users which has list of folders in it.
users:
name: user1
folders:
- user1-1
- user1-2
name: user2
folders:
- user2-1
- user2-2
name: userN
folders:
- userN-1
- userN-2
I am trying to create this user dictionary list dynamically.
- hosts: localhost
gather_facts: no
vars:
userCount: 5
folderCount: 5
tasks:
- set_fact:
user_lists: []
- name: creating list of users
set_fact:
user_lists: "{{ user_lists + [ 'user-' ~ item ] }}"
with_sequence: count="{{userCount|int}}"
- debug:
var: user_lists
- set_fact:
usersObj: []
userDict: {}
- name: add each user to dict
set_fact:
usersObj: "{{ usersObj + [userDict| combine({'name': item})] }}"
with_items: "{{user_lists}}"
- debug: var=usersObj
I have created 5 users in dictionary. Now each user should have folder list created based on folderCount. How to resolve this? Can Jinja2 templates be used to simplify this?
The task below does the job
- set_fact:
user_lists: []
- name: creating dictionary of users with lists of folders
set_fact:
user_lists: "{{ user_lists|combine({key: val}) }}"
with_sequence: start=1 end="{{ userCount }}"
vars:
folder_range: "{{ range(1, folderCount|int + 1)|list }}"
key: "{{ 'user-' ~ item }}"
val: "{{ [key]|
product(folder_range)|
map('join', '-')|
list }}"
- debug:
var: user_lists
gives (abridged)
"user_lists": {
"user-1": [
"user-1-1",
"user-1-2",
"user-1-3",
"user-1-4",
"user-1-5"
],
"user-2": [
"user-2-1",
"user-2-2",
"user-2-3",
"user-2-4",
"user-2-5"
],
"user-3": [
"user-3-1",
"user-3-2",
"user-3-3",
"user-3-4",
"user-3-5"
],
"user-4": [
"user-4-1",
"user-4-2",
"user-4-3",
"user-4-4",
"user-4-5"
],
"user-5": [
"user-5-1",
"user-5-2",
"user-5-3",
"user-5-4",
"user-5-5"
]
}

How to pass results back from include_tasks

Started to learn ansible yesterday, so I believe I may risk XY problem here, but still…
The main yml:
- hosts: localhost
vars_files:
[ "users.yml" ]
tasks:
- name: manage instances
#include_tasks: create_instance.yml
include_tasks: inhabit_instance.yml
with_dict: "{{users}}"
register: res
- name: print
debug: msg="{{res.results}}"
inhabit_instance.yml:
- name: Get instance info for {{ item.key }}
ec2_instance_facts:
profile: henryaws
filters:
"tag:name": "{{item.key}}"
instance-state-name: running
register: ec2
- name: print
debug:
msg: "IP: {{ec2.instances.0.public_ip_address}}"
So that's that IP that I'd like to have on the top level. Haven't found anything right away about return values of the include block…
Well, I've found some way that suits me, maybe it's even canonical?
main.yml:
- hosts: localhost
vars_files:
[ "users.yml" ]
vars:
ec_results: {}
tasks:
- name: manage instances
#include_tasks: create_instance.yml
include_tasks: inhabit_instance.yml
with_dict: "{{users}}"
register: res
inhabit_instance.yml:
- name: Get instance info for {{ item.key }}
ec2_instance_facts:
profile: henryaws
filters:
"tag:name": "{{item.key}}"
instance-state-name: running
register: ec2
- name: update
set_fact:
ec_results: "{{ ec_results|combine({ item.key: ec2.instances.0.public_ip_address }) }}"
Thanks for you answer - this helped me to find an answer to my issue when my task was called in a loop - my solution was just to use lists so for your example above, the solution would be:
The main yml:
- hosts: localhost
vars_files:
[ "users.yml" ]
vars:
ec_results: []
tasks:
- name: manage instances
#include_tasks: create_instance.yml
include_tasks: inhabit_instance.yml
with_dict: "{{users}}"
- name: print
debug: msg="{{ec_results}}"
inhabit_instance.yml:
- name: Get instance info for {{ item.key }}
ec2_instance_facts:
profile: henryaws
filters:
"tag:name": "{{item.key}}"
instance-state-name: running
register: ec2
- name: Add IP to results
set_fact:
ec_results: "{{ ec_results + [ec2.instances.0.public_ip_address] }}"
In my code, include_tasks was in a loop so then ec_results contained a list of the results from each iteration of the loop

Resources