I have a yaml file witch contains a instance of a key with dicts.
Here's an example:
INSTANCES:
- NAME: hosttype1
HOST_NAME: mysevername1
- NAME: hosttype2
HOST_NAME: myservername2
- NAME: hosttype2
HOST_NAME: myservername3
And the code i tried in my role to change all NAME's and HOST_NAME's are like this:
- name: Update property file {{ environment_env_property_file }}
yedit:
src: "{{ environment_env_property_file }}"
state: present
edits:
- key: INSTANCES.NAME[0]
value: "CM01"
- key: INSTANCES.NAME[0].HOST_NAME
value: "ap-2001c.domain.net"
- key: INSTANCES.NAME[1]
value: "BP01"
- key: INSTANCES.NAME[1].HOST_NAME
value: "ap-2002c.domain.net"
- key: INSTANCES.NAME[2]
value: "BP01"
- key: INSTANCES.NAME[2].HOST_NAME
value: "ap-2003c.domain.net"
Error from ansible
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: main.YeditException: Unexpected item type found while going through key path: INSTANCES.NAME[0] (at key: NAME)
So what is the correct way to change the list of dictionaries?
Or should it be?
- key: INSTANCES[0].NAME
value: "hosttype1"
- key: INSTANCES[0].HOST_NAME
value: "myservername1"
- key: INSTANCES[1].NAME
value: "hosttype2"
- key: INSTANCES[1].HOST_NAME
value: "myservername2"
- key: INSTANCES[2].NAME
value: "hosttype3"
- key: INSTANCES[2].HOST_NAME
value: "myservername3"
After just dropping the source code from yedit.py
under my local Ansible /library
, for a property_file.yml
with content of
INSTANCES:
- NAME: hosttype1
HOST_NAME: mysevername1
- NAME: hosttype2
HOST_NAME: myservername2
- NAME: hosttype2
HOST_NAME: myservername3
a minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
property_file: property_file.yml
tasks:
- name: Update property file {{ property_file }}
yedit:
src: "{{ property_file }}"
state: present
edits:
- key: INSTANCES[0].NAME
value: "CM01"
- key: INSTANCES[0].HOST_NAME
value: "ap-2001c.example.net"
- key: INSTANCES[1].NAME
value: "BP01"
- key: INSTANCES[1].HOST_NAME
value: "ap-2002c.example.net"
- key: INSTANCES[2].NAME
value: "BP01"
- key: INSTANCES[2].HOST_NAME
value: "ap-2003c.example.net"
will result into a changed YML file with content of
INSTANCES:
- HOST_NAME: ap-2001c.example.net
NAME: CM01
- HOST_NAME: ap-2002c.example.net
NAME: BP01
- HOST_NAME: ap-2003c.example.net
NAME: BP01
If that's the Use Case, changing all key value pairs, template
or copy
might be simpler.
If the Use Case is to change a subset only, a few list elements from a huge list , a minimal example playbook with loop
and based on index
---
- hosts: localhost
become: false
gather_facts: false
vars:
NEW_INSTANCES:
- TYPE: CM01
FQDN: ap-2001c.example.net
- TYPE: BP01
FQDN: ap-2002c.example.net
property_file: property_file.yml
tasks:
- name: Update property file {{ property_file }}
yedit:
src: "{{ property_file }}"
state: present
edits:
- key: "INSTANCES[{{ ansible_loop.index0 }}].NAME"
value: "{{ item.TYPE }}"
- key: "INSTANCES[{{ ansible_loop.index0 }}].HOST_NAME"
value: "{{ item.FQDN }}"
loop: "{{ NEW_INSTANCES }}"
loop_control:
extended: true
will result into an output of
TASK [Update property file property_file.yml] ****************************
ok: [localhost] => (item={'TYPE': 'CM01', 'FQDN': 'ap-2001c.example.net'})
ok: [localhost] => (item={'TYPE': 'BP01', 'FQDN': 'ap-2002c.example.net'})
and a file content of
INSTANCES:
- HOST_NAME: ap-2001c.example.net
NAME: CM01
- HOST_NAME: ap-2002c.example.net
NAME: BP01
- HOST_NAME: myservername3
NAME: hosttype2
Please take note in this second example the module in question, yedit
expect the key to edit as string:
def main():
...
module = AnsibleModule(
argument_spec=dict(
...
key=dict(default='', type='str'),
value=dict(),
...
Therefore one can't just write INSTANCES[ansible_loop.index0].HOST_NAME
. Instead it is necessary to write "INSTANCES[{{ ansible_loop.index0 }}].HOST_NAME"
which resolves to INSTANCES[0].HOST_NAME
. Also "INSTANCES[ {{ ansible_loop.index0 }} ].HOST_NAME"
would not work as that resolves to INSTANCES[ 0 ].HOST_NAME
.