Getting the difference from two arrays Ansible - ansible

I have this array of names and a main object I want to intersect the two, the common will be removed the and the rest will be retained.
VPC Names
["A_VPC", "B_VPC"]
ECS_OBJECTS
[{
"vpc_name": "A_VPC",
"client_name": "A"
},
{
"vpc_name": "B_VPC",
"client_name": "B"
},
{
"vpc_name": "C_VPC",
"client_name": "C"
}]
The end result I want will be that every common will be removed except C because they are not in common with the two objects.
[{
"vpc_name":"C_VPC",
"client_name": "C"
}]
My code thus far is some like this..
- name: Intersect ecs_instances objects to the existing VPC created
set_fact:
vpc_to_be_created: "{{ ecs_instances | difference(vpc_names) }}"

Is this the code that you're looking for ?
- set_fact:
vpc_to_be_created: "{{ vpc_to_be_created | combine(item) }}"
loop: "{{ ecs_objects }}"
when: item.vpc_name not in vpc_names

The cleanest and fastest way would be probably using JMESPath:
- name: Intersect ecs_instances objects to the existing VPC created
set_fact:
vpc_to_be_created: "{{ ecs_instances | json_query(query) }}"
vars:
query: "#[?!contains(`{{ vpc_names|to_json }}`, vpc_name)]"
Or build a differential list from scratch, as suggested in the other answer, but with fixed code:
- set_fact:
vpc_to_be_created: "{{ vpc_to_be_created | default([]) + [item] }}"
loop: "{{ ecs_instances }}"
when: item.vpc_name not in vpc_names
This has a drawback of being less efficient (loop), a somewhat messy output because of that, and a requirement for the set_fact task (you can use the above JMESPath query directly in other modules).

Related

Escape special character in ansible

I'm working on a playbook to modify a sql script, I need to create a string with a concatenation of conditionals.
this is the conditional structure:
(server.primary_name||'.'||server.arpa_domain like '%server_name%')
This is the ansible task:
- name: Query Conditionals
set_fact:
query_conditionals_list: "{{ query_conditionals_list + [ '(server.primary_name||.||server.arpa_domain like %' + item + '%)' ] }}"
with_items: "{{ server_names }}"
when: "item not in query_conditionals"
loop_control:
label: "{{item}}"
- name: Join names
set_fact:
query_conditionals: "{{query_conditionals_list | join (' OR ')}}"
I believe that I'm not escaping correctly the parentesis and the single qoutes.
Is it possible to escape them?
Thanks
EDIT
I made some modifications.
As suggested by mdaniel, instead of creating the string from the get go, I'm creating first a list and then doing a join with the OR. So that solves my OR problem.
Although, I was not able to escape the single quotes yet
I was able to find a workaround using vars on the ansible task.
While using vars, I was able to maintain the structure I wanted to use and save the single quotes.
- name: Query Conditionals
set_fact:
query_conditionals_list: "{{ query_conditionals_list + [ server_item ] }}"
when: "item not in query_conditionals_list"
loop_control:
label: "{{item}}"
vars:
server_item: "(server.primary_name||'.'||server.arpa_domain like '%{{item}}%')"
with_items: "{{ server_names }}"

Ansible: set fact variable is getting overwritten while iterating over a loop

I am trying to create a list out of a dictionary based on condition. But when I pass it through a loop, the last value of loop overwrites the fact instead of creating a list
input.yml
execution:
pre-deploy:
post-deploy:
shell-files:
name: abc, def, gef
type: deploy
target_host: server1
check: enabled
xml-files:
name: xyz, uvw
type: deploy
target_host: server2
check: enabled
shell-files:
name: pqr
type: migrate
target_host: server1
check: enabled
My Code:
Hosts: local
vars_file:
- input.yml
vars:
post_list:"{{ lookup( 'dict', operations.post-deploy, wantList=Ture ) }}"
tasks:
- set_fact:
get_deploy_list: "{{ item.key }}: {{ item.value.name.split(',') | list }}"
get_host_list: "{{ item.value.target_host }}"
when: ( item.value.type == "deploy" and item.value.check == "enabled")
loop:"{{ post_list | items2dict }}"
- debug: msg="{{ get_deploy_list }}"
Expected Output:
debug:
[ {
shell-files: abc,
shell-files: def,
shell-files: ghi
}
{
xml-files: xyz,
xml-files: uvw
} ]
Actual output:
[{
xml-files: xyz,
xml-files: uvw
} ]
The last value of list overwrites the fact.
The situation is the same as in any programming language with loops: if you don't reference the existing list, then it is just repeatedly reassigning a variable and you will end up with the last state of the world as the loop exits
The traditional way I have seen that solved is via | default and | combine
- set_fact:
get_deploy_list: >-
{{ (get_deploy_list|default([]))
| combine({item.key: item.value.name.split(',') | list})
}}
loop: "{{ post_list | items2dict }}"
although in my playbooks, I consider that pattern a bug since jinja is perfectly capable of building up dictionaries using its looping syntax, without invoking set_fact repeatedly (which, by definition, will open connections to every host in the inventory multiple times)
be aware that I didn't get your exact output format with that code snippet, because there was already too much wrong with your playbook; this answer was just "why did the assignment overwrite the fact all the time"

Selecting nested values with a list inside a dict, when a value matches

Within the dict hostvars[inventory_hostname] I have multiple occurrences of the below, one per each server in the inventory.
The value I need to extract is proxyServer.detail.name only when the proxyServer.datail.inbound_ip matches a variable that is available to me.
Furthermore, my search cannot have proxyServer hardcoded as there are multiple types of servers and I must iterate through all of them - in order to find the match for my IP address, and then extract the corresponding "name".
"proxyServer": {
"commonName": "outbound",
"proxy.example.com": {
"ipaddress": "10.1.100.100"
},
"detail": [
{
"inbound_ip": "10.1.100.100",
"outbound_ip": "192.168.1.250",
"name": "proxy.local"
I've tried multiple variations of:
- debug:
var: "item.key"
with_dict: "{{ hostvars[inventory_hostname].proxyServer.detail[0] }}"
when: "'10.1.100.100' in item.value"
But when there is a match, I'm already "too deep" and cannot extract the name any more.
"ansible_loop_var": "item",
"item": {
"key": "internal_ip",
"value": "10.1.100.100"
},
"item.key": "internal_ip"
}
My method will also not scale as I'm hardcoding detail so it will only match that one single dict, and nothing else in the data.
Any pointers would be welcome, thank you for your time.
Use json_query. For example, the task below shall give you a list of selected "proxyServer" variables where "inbound_ip" is "10.1.100.100"
- set_fact:
proxyServer_selected: "{{ ansible_play_hosts_all|
map('extract', hostvars, 'proxyServer')|list|
json_query(query) }}"
vars:
inbound_ip: "10.1.100.100"
query: "[?detail[?inbound_ip == '{{ inbound_ip }}']]"
run_once: true
Then, process the list and extract what you want.
Q: "Doesn't this depend on the fact that I must code in the 'proxyServer' value ?"
A: No. It does not. It's possible to iterate the values and concatenate the lists of the results. For example
- set_fact:
vars_selected: "{{ vars_selected|default([]) +
[ansible_play_hosts_all|
map('extract', hostvars, item)|list|
json_query(query)] }}"
vars:
inbound_ip: "10.1.100.100"
query: "[?detail[?inbound_ip == '{{ inbound_ip }}']]"
run_once: true
loop:
- proxyServer

Set_fact dynamic variable name inside loop

I have a list of accounts, a dictionary mappedsecrets which has the accounts as keys, and a list of secrets as values and finally a dictionary secrets contains the secret-names and secret-values
#variables
accounts:
acc_one
acc_two
mappedsecrets:
acc_one:
- keyone
- keytwo
acc_two:
- keythree
- keyfour
secrets:
keyone: secret_1
keytwo: secret_2
keythree: secret_3
keyfour: secret_4
I have an include_task looping over accounts, with loop_var:account.
Inside the loop, I want to create a dict all key - secrets that the account has mapped:
so for example:
acc_one:
keyone: secret_1
keytwo: secret_2
I went the set_fact route, with the combine filter.
- name: "Create dict of account secrets"
set_fact:
account_secrets: "{{ account_secrets |default({}) | combine( {item: secrets[item]} ) }}"
with_items:
- "{{ mappedsecrets[account] }}"
The problem is:
each loop just keeps appending the account_secrets variable, which causes the last account have all the secrets from the previous iterations.
(correct me if i'm wrong) I've read that it's not possible to reset facts inside an ansible-play.
So I figure I can make dynamic names, based on the loop iteration: like account_secrets_{{ ansible_loop.index }}
But I'm stuck figuring out the correct syntax for the below:
set_fact:
account_secrets_{{ ansible_loop.index }}: "{{ account_secrets_{{ ansible_loop.index }} |default({}) | combine( {item: secrets[item]} ) }}"
I figured it out:
set_fact:
account_secrets_{{ ansible_loop.index }}: "{{ lookup('vars', 'account_secrets_' + ansible_loop.index|string) |default({}) | combine( {item: secrets[item]} ) }}"
I'm still curious tho, is there a more 'ansible' way to achieve this use-case?

How to encapsulate ansible filters?

I have an ansible variable that contains a list of win_uri responses (created by loop).
I want to create a dictionary where each single response body (json) contains a value (title) that I want to use as a key and another one as a value (id).
Right now I am lost.
My current implementation ignores the json - which obviously does not work:
- name: populate folder dictionary
set_fact:
app_folders: "{{ app_folders | default({}) | combine({item.jsonContent.title : item.id}) }}"
with_items: "{{ response.results }}"
I know, that it is possible to read JSON into a variable with the from_json - but I do not know how to combine it with the above code.
If I got your question right, try:
- name: populate folder dictionary
set_fact:
app_folders: "{{ app_folders | default({}) | combine({(item.jsonContent|from_json).title : item.id}) }}"
with_items: "{{ response.results }}"

Resources