How to encapsulate ansible filters? - filter

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 }}"

Related

ansible Iterate over list in list

I'm trying to adopt the task in the last answer from this post Ansible - managing multiple SSH keys for multiple users & roles
My variable looks like:
provisioning_user:
- name: ansible
state: present
ssh_public_keyfiles:
- ansible.pub
- user.pub
- name: foo
state: present
ssh_public_keyfiles:
- bar.pub
- key.pub
and my code looks like
- name: lookup ssh pubkeys from keyfiles and create ssh_pubkeys_list
set_fact:
ssh_pubkeys_list: "{{ lookup('file', item.ssh_public_keyfiles) }}"
loop: "{{ provisioning_user }}"
register: ssh_pubkeys_results_list
I want to store keys under the files directory and assign them with the variable to different users so that when a key changes, i only have to change the file and run the playbook instead of changing it in any hostvars file where the old key is used. But I get the following error and dont know how to solve it. I want to do this for every user defined in provisioning_user
fatal: [cloud.xxx.xxx]: FAILED! =>
msg: 'An unhandled exception occurred while running the lookup plugin ''file''. Error was a <class ''AttributeError''>, original message: ''list'' object has no attribute ''startswith''. ''list'' object has no attribute ''startswith'''
Can anyone please help me?
The file lookup reads the content of a single file, but you're passing it a list. That's not going to work, and is the direct cause of the error message you've reported.
You're also using both set_fact and register in the same task, which doesn't make much sense: the whole point of a set_fact task is to create a new variable; you shouldn't need to register the result.
Your life is going to be complicated by the fact that each user can have multiple key files. We need to build a data structure that maps each user name to a list of keys; we can do that like this:
- name: lookup ssh pubkeys from keyfiles
set_fact:
pubkeys: >-
{{
pubkeys |
combine({
item.0.name: pubkeys.get(item.0.name, []) + [lookup('file', item.1)]})
}}
vars:
pubkeys: {}
loop: "{{ provisioning_user|subelements('ssh_public_keyfiles') }}"
This creates the variable pubkeys, which is a dictionary that maps usernames to keys. Assuming that our provisioning_user variable looks like this:
provisioning_user:
- name: ansible
state: present
ssh_public_keyfiles:
- ansible.pub
- user.pub
- name: foo
state: present
ssh_public_keyfiles:
- bar.pub
- key.pub
- name: bar
state: present
ssh_public_keyfiles: []
After running the above task, pubkeys looks like:
"pubkeys": {
"ansible": [
"ssh-rsa ...",
"ssh-rsa ..."
],
"foo": [
"ssh-rsa ...",
"ssh-rsa ..."
]
}
We can use pubkeys in our authorized_keys task like this:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ '\n'.join(pubkeys[item.name]) }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
loop: "{{ provisioning_user }}"
when: item.name in pubkeys
I think your life would be easier if you were to rethink how you're managing keys. Instead of allowing each user to have a list of multiple key files, have a single public key file for each user -- named after the username -- that may contain multiple public keys.
That reduces your provisioning_user data to:
provisioning_user:
- name: ansible
state: present
- name: foo
state: present
- name: bar
state: present
And in our files/ directory, we have:
files
├── ansible.keys
└── foo.keys
You no longer need the set_fact task at all, and the authorized_keys task looks like:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ keys }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
when: >-
('files/%s.keys' % item.name) is exists
vars:
keys: "{{ lookup('file', '%s.keys' % item.name) }}"
loop: "{{ provisioning_user }}"
Note that in the above the when condition requires an explicit path, while the file lookup will implicitly look in the files directory.
These changes dramatically simplify your playbook.

how to set default value if key is not available in dict in ansible play

I am setting fact as dict using host names like below,
set_fact:
processed_hosts: "{{ processed_hosts | default([]) + [dict(host_name=item, result=hostvars[item]['_OS_upgraded'])] }}"
with_items:
- "{{ groups['reachable_a_hosts'] }}"
Its working good when all the host has "_OS_upgraded".
But when task failed in any of the host then "_OS_upgraded" is not set, on that scenario i am getting error when i call this hostvars[item]['_OS_upgraded']
so want to set it false by default if that item is not in hostvars,
Can some one let me know how to do that?
It worked after adding | default(False)
set_fact:
processed_hosts: "{{ processed_hosts | default([]) + [dict(host_name=item, result=hostvars[item]['_OS_upgraded']|default(False))] }}"
with_items:
- "{{ groups['reachable_a_hosts'] }}"

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 filter a dictionary using regex on value

I need to filter a dictionary in ansible based on regex matching it's values.
mydict:
some_value: /my/path
another_value: 2345
anotherpath: /my/other/path
What I tried is:
set_fact:
newvar: "{{ mydict | dict2items | selectattr('value', 'match', '^/my/.+$') | list }}"
But what i get is:
expected string or buffer
How can I filter all values based on part of their string for using them later in with_items?
The issue you currently have is that the regex match cannot handle integers. This will solve your issue, though it's slightly more verbose:
- set_fact:
file_list: []
- set_fact:
file_list: "{{ file_list + [item.value] }}"
when: item.value|string | match('^/my/.+$')
loop: "{{ mydict | dict2items }}"
If you can ensure that all values will always be strings, then you could use:
- set_fact:
newvar: "{{ mydict | dict2items | selectattr('value', 'match', '^/my/.+$') | map(attribute='value') | list }}"
Thanks #matt-p for clarifying :-) I didn't know this was actually caused by the match function itself. Is this more like an issue I should report to the Ansible community?
BTW I changed your code a little bit to fit the actual Ansible standard (the when condition is using deprecated syntax):
- set_fact:
file_list: []
- set_fact:
file_list: "{{ file_list + [item.value] }}"
when:
- item.value is string
- item.value is match('^/my/.+$')
with_items: "{{ mydict | dict2items }}"

loop control with digital_ocean ansible module

Thanks to other stackoverflow users, I have managed to pull some data out of a variable registered by the digital_ocean ansible module. I attempted to use loop_control to print only part of the huge variable that is registered. Here is an extract from the role:
- name: Add droplet
digital_ocean: >
{ some parameters }
with_dict: "{{ droplets_up }}"
register: my_droplet
- debug: msg="Droplet IP is {{ item.droplet.ip_address }}"
with_items: "{{ my_droplet.results }}"
loop_control:
label: "{{ item }}"
I'm obviously doing it wrong here, as it prints the whole variable as well as the debug message. I don't quite understand loop_control at this point, but does anyone know if it's possible to use it in this manner with this module?
debug action has result['_ansible_verbose_always'] = True, so it will always print full item, no matter what your label is (although label: "{{item}}" doesn't change anything, try label: "{{ item.droplet.ip_address }}").
If you just need to list all your IP addresses, use map filter and single debug statement:
- name: Print droplets IP
debug:
msg: "{{ my_droplet.results | map(attribute='droplet.ip_address') | list }}"

Resources