I have a dictionary that is the exact same structure as below.
Where I am struggling is in Ansible code, how would I go about a user enters apple, and I identify the type is fruit?
When a user enters spinach, Ansible identifies it as veggie?
Basically, how do I reverse check the parent in a dictionary? EDIT: after using selectattr, how do i assign that to one variable to use in the future ? currently, i get food_groups | selectattr('names', 'contains', food) | first).type: fruit as output, how do i only get FRUIT assigned to a variable?
groups:
- type: fruit
names:
- apple
- oranges
- grapes
- type: veggie
names:
- broccoli
- spinach
You can use selectattr and the contains test of Ansible for this.
Important note: do not name your dictionary groups, as you risk a collision with the special variable of the same name. Here, I named it food_groups.
So, the task of giving the type of food is as simple as:
- debug:
var: (food_groups | selectattr('names', 'contains', food) | first).type
given that the food you want to assert is in a variable name food
Given the playbook:
- hosts: localhost
gather_facts: no
vars_prompt:
- name: food
prompt: What food to you want to know the group?
private: no
tasks:
- debug:
var: (food_groups | selectattr('names', 'contains', food) | first).type
vars:
food_groups:
- type: fruit
names:
- apple
- oranges
- grapes
- type: veggie
names:
- broccoli
- spinach
This yields:
What food to you want to know the group?: grapes
PLAY [localhost] *******************************************************************
TASK [debug] ***********************************************************************
ok: [localhost] =>
(food_groups | selectattr('names', 'contains', food) | first).type: fruit
Q: "How would I go about a user entering 'apple', and I identify the type is 'fruit'?"
A: Create a dictionary mapping the items to groups, e.g. given the data
my_groups:
- type: fruit
names:
- apple
- oranges
- grapes
- tomato
- type: veggie
names:
- broccoli
- spinach
- tomato
The task below
- set_fact:
my_dict: "{{ my_dict|d({'undef': 'undef'})|
combine({item.1: [item.0.type]}, list_merge='append') }}"
with_subelements:
- "{{ my_groups }}"
- names
gives
my_dict:
apple: [fruit]
broccoli: [veggie]
grapes: [fruit]
oranges: [fruit]
spinach: [veggie]
tomato: [fruit, veggie]
undef: undef
I added 'tomato' to both groups to test the membership in multiple groups. The identification of the item's groups is now trivial, e.g.
- debug:
msg: "{{ my_item|d('undef') }} is {{ my_dict[my_item|d('undef')]|d('none') }}"
gives by default
msg: undef is undef
When you enter an item not present in the dictionary you get
shell> ansible-playbook playbook.yml -e my_item=foo
...
msg: foo is none
and when you enter an item present in the dictionary you get the group(s)
shell> ansible-playbook playbook.yml -e my_item=spinach
...
msg: spinach is ['veggie']
Related
I'm trying to get some values from a dictionary in ansible playbook.
First, I build the dictionary and it runs correctly.
Then, I would compute the output_dict evaluating the values of this dictionary but I have the issue above.
- set_fact:
state_service: "{{ state_service | default({}) | combine({ item.key : item.value }) }}"
with_items:
- { 'key': 'op_state', 'value': "{{Check_Operative_State.results[0].stdout}}"}
- { 'key': 'ad_state', 'value': "{{Check_Administrative_State.results[0].stdout}}"}
- name: Display the state of service
debug: msg="{{state_service}}"
- set_fact:
output_dict: '{{output_dict | default([]) + ["CHECK ERRORS IN SERVICE ************PASSED" if ((item.op_state == "enabled" and item.ad_state == "unlocked") or (item.op_state == "disabled" and item.ad_state == "locked")) else "CHECK ERRORS IN SERVICE **********FAILED"] }}'
with_items: "{{state_service}}"
The error:
**************************************************
2022-02-04 19:04:45,341 p=4478 u=abcd n=ansible | ok: [localhost] => {
"msg": {
"ad_state": "unlocked",
"op_state": "enabled"
}
}
2022-02-04 19:04:45,366 p=4478 u=abcd n=ansible | TASK [check : set_fact] ************************************************************************************************************************************************************************
2022-02-04 19:04:45,366 p=4478 u=abcd n=ansible | fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'op_state'\n\nThe error appears to be in '/home/abcd/health/service/script/roles/health/tasks/main.yaml': line 483, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- set_fact:\n ^ here\n"}
2022-02-04 19:04:45,368 p=4478 u=abcd n=ansible | PLAY RECAP *****************************************************************************************************************************************
Given the list below to simplify the example
l1:
- {key: op_state, value: enabled}
- {key: ad_state, value: unlocked}
Use the filter items2dict to create a dictionary from the list, e.g.
state_service: "{{ l1|items2dict }}"
gives
state_service:
ad_state: unlocked
op_state: enabled
You can't iterate a dictionary, but you can use dict2items to convert the dictionary to a list, e.g.
- debug:
var: item
loop: "{{ state_service|dict2items }}"
gives (abridged)
item:
key: op_state
value: enabled
item:
key: ad_state
value: unlocked
I'm trying to generate a list with the information of hosts that match a certain condition (for instance, that NTP is synched for an inventory of Cisco devices). So that the ones matching that condition will be added to a list with say hostname and IP, for later generating a CSV.
Checking the condition is quite easy, but I'm struggling on how to generate this list.
Adding them to a list in a var doesn't seem wise, as it requires of serial execution of tasks per device.
Should I set a boolean fact for each device (i.e., ntp_synched, and then generate a list with the ansible_net_hostname and ansible_host of each device? How to do this?
- name: CHECK NTP STATUS
ios_command:
commands:
- show ntp status
register: ntp_status
- name: NTP NOT SYNCH
debug:
msg: "{{ [ansible_net_hostname] }}"
when: '"Clock is synchronized" not in ntp_status.stdout[0]'
For example, given the inventory for testing
host01 status="Clock is synchronized"
host02 status="Clock is synchronized"
host03 status="Clock is not synchronized"
Create the dictionary of the hosts and statuses
- hosts: all
tasks:
- command: "echo {{ status }}"
register: ntp_status
- set_fact:
host_status: "{{ dict(_hosts|zip(_stats)) }}"
vars:
_hosts: "{{ ansible_play_hosts }}"
_stats: "{{ ansible_play_hosts|
map('extract', hostvars, ['ntp_status','stdout'])|list }}"
run_once: true
gives
host_status:
host01: Clock is synchronized
host02: Clock is synchronized
host03: Clock is not synchronized
List the synchronized hosts
- debug:
msg: "{{ host_status|dict2items|
selectattr('value', 'search', 'Clock is synchronized')|
map(attribute='key')|list }}"
run_once: true
gives
msg:
- host01
- host02
I need to loop over a list of dicts that's 3 levels deep in my dictionary. Here's an example of my dictionary:
orgs:
- name: Org 1
templates:
- name: template 1
nodes:
- identifier: identifier 1
secondary_template: some_template
- identifier: identifier 2
secondary_template: some_other_template
- name: Org 2 (etc)
I need to have all 3 levels of vars in a single task. I've tried to do a set_fact first, but it's just grabbing the last template in the list. My single task needs to loop through all 3 levels of the dict; i.e., it needs the org name, the template name, & then needs to loop through each template's nodes, grabbing the identifier & secondary_template. Closest I've come is:
- debug:
var: item.1.nodes | map(attribute='identifier')
loop: "{{ orgs | subelements ('templates', skip_missing=True) }}"
But that spits out a list of the identifiers & not an individual value.
It's possible to iterate include_tasks. For example, create a file
shell> cat loop_nodes.yml
- debug:
msg: "{{ outer_item.0.name }} -
{{ outer_item.1.name }} -
{{ item.identifier }} -
{{ item.secondary_template }}"
loop: "{{ outer_item.1.nodes }}"
loop_control:
label: "{{ outer_item.1.name }}"
and include it in the loop
- include_tasks: loop_nodes.yml
loop: "{{ orgs | subelements ('templates', skip_missing=True) }}"
loop_control:
loop_var: outer_item
gives
msg: Org 1 - template 1 - identifier 1 - some_template
msg: Org 1 - template 1 - identifier 2 - some_other_template
...
I have this dictionary:
MyClouds:
Devwatt:
ExternalNetwork: PublicRSC
Flavors:
- Flavor_1cpu_1gb: Devwatt_1cpu_1gb
- Flavor_1cpu_2gb: Devwatt_1cpu_2gb
- Flavor_1cpu_4gb: Devwatt_1cpu_4gb
Fuga:
ExternalNetwork: Internet
Flavors:
- Flavor_1cpu_1gb: Fuga_1cpu_1gb
- Flavor_1cpu_2gb: Fuga_1cpu_2gb
- Flavor_1cpu_4gb: Fuga_1cpu_4gb
- Flavor_1cpu_8gb: Fuga_1cpu_8gb
I have to migrate from one Openstack cloud to another, and one of my problem is to find correspondances between flavors.
I want to find which flavor (key) has the value "Devwatt_1cpu_2gb" in "Devwatt", and after get the value of the same key in "Fuga"
I tried a lot of solution (with-dict, when, jija filters, json_query) but I can't find a way to do that.
Please, may you help me ?
Inspired by Eric's answer and this usefull resource, I, finally, used this solution:
I changed a little bit my data structure and put it in a file matrice.yml:
MyClouds:
Devwatt:
ExternalNetwork: PublicRSC
Flavors:
- name: Flavor_1cpu_1gb
FlavorName: Devwatt_1cpu_1gb
- name: Flavor_2cpu_1gb
FlavorName: Devwatt_2cpu_1gb
- name: Flavor_1cpu_2gb
FlavorName: Devwatt_1cpu_2gb
Fuga:
ExternalNetwork: Internet
Flavors:
- name: Flavor_1cpu_1gb
FlavorName: Fuga_1cpu_1gb
- name: Flavor_2cpu_1gb
FlavorName: Fuga_2cpu_1gb
- name: Flavor_1cpu_2gb
FlavorName: Fuga_1cpu_2gb
then I used these filters in my playbook:
---
- hosts: localhost
connection: local
gather_facts: false
vars:
SourceFlavorName: "Devwatt_2cpu_1gb"
tasks:
- name: get flavors matrice
include_vars:
file: matrice.yml
- name: Get generic name from flavor name of source cloud
debug:
msg: "{{ MyClouds.Devwatt.Flavors | selectattr('FlavorName','search','^'+ SourceFlavorName +'$') |map (attribute='name') | list }}"
register: result
- name: Get flavor name for target cloud from generic name
debug:
msg: "{{ MyClouds.Fuga.Flavors | selectattr('name','search','^'+ result.msg[0] +'$') |map (attribute='FlavorName') | list }}"
With this solution I can have any number of clouds and find easily correspondances between flavor from one source cloud to target cloud.
Why not using a simple mapping using a dict where keys are "Devwatt" flavors and values are "Fuga" flavors, like this :
---
- hosts: localhost
vars:
FlavorsMapping:
Devwatt_1cpu_1gb: Fuga_1cpu_1gb
Devwatt_1cpu_2gb: Fuga_1cpu_2gb
Devwatt_1cpu_4gb: Fuga_1cpu_4gb
tasks:
- debug:
var: FlavorsMapping['Devwatt_1cpu_2gb']
Consider my SLS file,
state1:
cmd.run:
- order: 1
- name: |
USER_NAME='username'
USERPWD='password'
DB_NAME='test'
USER_TOBE_CREATED='new_user'
PASSWORD='newpass'
mysql_user.present:
- order: 2
- host: localhost
- username: USER_TOBE_CREATED
- password: PASSWORD
- connection_user: USER_NAME
- connection_pass: USERPWD
- connection_charset: utf8
- saltenv:
- LC_ALL: "en_US.utf8"
mysql_grants.present:
- order: 3
- grant: all privileges
- database: DB_NAME.*
- user: USER_TOBE_CREATED
In the states mysql_user.present and mysql_grants.present I am using the variables USER_TOBE_CREATED,USER_NAME,USERPWD etc whose values are assigned in state cmd.run. How will I make these two following states to use the actual values of those variables?. Here it's taking variable name itself as the value.
You may want to declare the variables in the state file itself, i.e.:
{% set user_name = "name" %}
and:
state1:
cmd.run:
- order: 1
- name: |
USER_NAME='{{ user_name }}'
You can re-use the variable as many times as you want inside the state file.
Let me know if this helped.