1

Can anyone help ?

I am trying to add an additional item to a dictionary in yaml using Ansible.

Here is the current file (site_vars.yaml)

test: hello

sites:
  - name: site 1
    site: www.site1.com
  - name: site 2
    site: www.site2.com

And I am trying to add

sites:
  - name: site 3
    site: www.site3.com

So it would become

sites:
  - name: site 1
    site: www.site1.com
  - name: site 2
    site: www.site2.com
  - name: site 3
    site: www.site3.com

Here is what I have for my playbook, but it isn't working.

  tasks:
    - name: Setup temporary dictionary item to be merged into yaml
      ansible.builtin.set_fact:
        new_item:
          sites:
            - name: site 3
              site: www.site3.com
      delegate_to: localhost

    - name: read in the yaml file
      ansible.builtin.set_fact:
        current_file: "{{ lookup('file', inventory_dir +'/group_vars/all/site_vars.yaml') | from_yaml }}"

    - name: Add items to dictionary
      set_fact:
        current_file: "{{ current_file | default({}) | combine (new_item) }}"

    - name: Make new file
      copy:
        dest: "{{ inventory_dir +'/group_vars/all/vars_new.yaml'  }}"
        content: "{{ current_file | to_nice_yaml }}"
      delegate_to: localhost

What would be ideal is if the token "sites:" doesn't exist then it is created, otherwise it is added to.

Above the "new_item", it just for testing, this will actually come from template. ALthough I thought if I get it working via a standard variable first then the rest would be easy.

I am also writing the contents "copy module" to a new file, this will eventually will override the original file but for the sake of testing - I decided to copy it to a new file.

Maybe I am approaching this the wrong way.

What I end up with my new file is the following, also it loses the white space, although I can live with that.

sites:
-   name: site 3
    site: www.site3.com
test: hello

Any ideas ?

Thanks in advance.

Ian Gregson
  • 199
  • 2
  • 8

2 Answers2

2

I think your life becomes much easier if you move that sites variable into its own file, so that you have:

group_vars/
  all/
    sites.yaml
    other_vars.yaml

Then you can do something like this:

- hosts: localhost
  gather_facts: false
  vars:
    add_sites:
      - name: site 3
        site: www.site3.com
      - name: site 4
        site: www.site4.com

  tasks:
    - set_fact:
        sites: "{{ sites + [item] }}"
      when: not sites|selectattr("name", "eq", item.name)
      loop: "{{ add_sites }}"

    - copy:
        content: '{{ {"sites": sites} | to_yaml }}'
        dest: '{{ inventory_dir + "/group_vars/all/sites.yaml" }}'

The above playbook is written to be idempotent -- it won't add a new site if it already exists in the sites variable.


You could achieve the same thing like this:

- hosts: localhost
  gather_facts: false
  vars:
    add_sites:
      - name: site 3
        site: www.site3.com
      - name: site 4
        site: www.site4.com

  tasks:
    - copy:
        content: |
          sites:
          {% for site in sites %}
          - name: {{ site.name }}
            site: {{ site.site }}
          {% endfor %}
          {% for site in add_sites if not sites|selectattr("name", "eq", site.name) %}
          - name: {{ site.name }}
            site: {{ site.site }}
          {% endfor %}
        dest: '{{ inventory_dir + "/group_vars/all/sites.yaml" }}'
larsks
  • 277,717
  • 41
  • 399
  • 399
2

Include the variables from the file in a dictionary

    - include_vars:
        file: /tmp/site_vars.yaml
        name: site_vars

Add the lists

sites: "{{ site_vars.sites + new_item.sites }}"

gives what you want

  sites:
  - name: site 1
    site: www.site1.com
  - name: site 2
    site: www.site2.com
  - name: site 3
    site: www.site3.com

Write the file

    - copy:
        dest: /tmp/site_vars.yaml
        content: |
          {{ site_vars|combine({'sites': sites})|to_nice_yaml }}

gives

shell> cat /tmp/site_vars.yaml 
sites:
-   name: site 1
    site: www.site1.com
-   name: site 2
    site: www.site2.com
-   name: site 3
    site: www.site3.com
test: hello

Example of a complete playbook for testing

- hosts: localhost

  vars:

    new_item:
      sites:
        - name: site 3
          site: www.site3.com
    sites: "{{ site_vars.sites + new_item.sites }}"

  tasks:

    - include_vars:
        file: site_vars.yaml
        name: site_vars

    - debug:
        var: sites

    - copy:
        dest: /tmp/site_vars.yaml
        content: |
          {{ site_vars|combine({'sites': sites})|to_nice_yaml }}
Vladimir Botka
  • 58,131
  • 4
  • 32
  • 63