listdictionaryfilteransiblejinja2

Ansible: Combining dict into specific list item, nested in a dict


I have defined a main playbook, which calls another preparation playbook via import_playbook.

The preparation playbook returns credentials for an S3 repository, var is called "service_secrets" at the moment, which is a dict (see below). This dict needs to be appended into the last item of product.dependencies. The indicator to pick the specific listitem could be just "Source=S3".

---
- name: 'Preparation'
  ansible.builtin.import_playbook: 'playbook_preparation.yml'
  vars:
    product:
      name: 'mssql'
      dependencies:
        - Name: 'SqlServer'
          Source: 'psgallery'
          Type: 'psmodule'
        - Name: 'SqlServerDsc'
          Source: 'psgallery'
          Type: 'dscmodule'
        - Name: 'AWSPowershell.NetCore'
          Source: 'psgallery'
          Type: 'psmodule'
        - Name: 'ComputerManagementDsc'
          Source: 'psgallery'
          Type: 'dscmodule'
        - Name: >-
            {{
              ["mssql", ci.attributes.input_parameters.mssql.version,
              ci.attributes.input_parameters.mssql.edition,
              ci.attributes.input_parameters.mssql.language]
              | join("_")
            }}
          Source: 'S3'
          Type: 'iso'
          Bucket: 'repository'

The new fact "service_secrets" looks something like this:

        "service_secrets": {
            "Access_key_id": "abc",
            "Access_key_secret": "defghi",
            "Bucket": "repository",
            "Endpoint": "https://s3.abc.com"
        }

I tried to fiddle around with combine(), but i could not get the service_secrets into the last listitem.

    - name: DEBUG
      debug:
        msg: '{{ product | combine({"dependencies": [service_secrets]}, list_merge="append") }}'
      loop: '{{ product.dependencies }}'
      when: 'item.Source == "S3"'

This will result in:

ok: [localhost] => (item={'Name': 'mssql_2022_standard_en', 'Source': 'S3', 'Type': 'iso', 'Bucket': 'repository'}) => {
    "msg": {
        "dependencies": [
            {
                "Name": "SqlServer",
                "Source": "psgallery",
                "Type": "psmodule"
            },
            {
                "Name": "SqlServerDsc",
                "Source": "psgallery",
                "Type": "dscmodule"
            },
            {
                "Name": "AWSPowershell.NetCore",
                "Source": "psgallery",
                "Type": "psmodule"
            },
            {
                "Name": "ComputerManagementDsc",
                "Source": "psgallery",
                "Type": "dscmodule"
            },
            {
                "Bucket": "repository",
                "Name": "mssql_2022_standard_en",
                "Source": "S3",
                "Type": "iso"
            },
            {
                "Access_key_id": "abc",
                "Access_key_secret": "defgh",
                "Bucket": "repository",
                "Endpoint": "https://s3.abc.com"
            }
        ],
        "name": "mssql"
    }
}

To make it more clear: The last two listitems of product.dependencies should be one item.


Solution

  • There are several ways to achieve this, I would like to show you 2 different ones, as both have advantages and disadvantages.

    Your initial data

    vars:
      dependencies:
        - Name: 'SqlServer'
          Source: 'psgallery'
          Type: 'psmodule'
        - Name: 'SqlServerDsc'
          Source: 'psgallery'
          Type: 'dscmodule'
        - Name: 'AWSPowershell.NetCore'
          Source: 'psgallery'
          Type: 'psmodule'
        - Name: 'ComputerManagementDsc'
          Source: 'psgallery'
          Type: 'dscmodule'
        - Name: mssql_2022_standard_en
          Source: 'S3'
          Type: 'iso'
          Bucket: 'repository'
        - Name: 'AnotherDependency_notFromS3'
          Source: 'psgallery'
          Type: 'dscmodule'
    
      service_secrets:
        Access_key_id: abc
        Access_key_secret: defghi
        Bucket: repository
        Endpoint: https://s3.abc.com
    

    Variant 1 - split by Source, enrich and recombine

    Can be implemented in a single task without having to prepare the data in a separate task. First, your dependencies list is split into elements Source == 'S3' yes or no.

    The splitting works with the rejectattr() or selectattr() functions. The elements whose attribute Source == 'S3' are either rejected or filtered out (selected).

    For all elements whose source is S3, the service_secrets are added. The service_secrets are added using map(), whereby a combine(service_secrets) is performed for each element in the list.

    The two separate lists are then appended together again.

    I see this as the Ansible way to implement this. The disadvantage is that your dependencies list may then have a different sort order than at the beginning. Compare the sort order of both results. However, if the order does not matter, I would recommend this method.

    Task code

    - name: Variant 1 - split by Source, enrich and recombine
      debug:
        msg: "{{ dependencies_not_s3 + dependencies_s3 }}"
      vars:
        dependencies_not_s3: "{{ dependencies | rejectattr('Source', 'eq', 'S3') }}"
        dependencies_s3: "{{ dependencies | selectattr('Source', 'eq', 'S3') | map('combine', service_secrets) }}"
    

    Result

    TASK [Variant 1 - split by Source, enrich and recombine] ************
    ok: [localhost] => {
        "msg": [
            {
                "Name": "SqlServer",
                "Source": "psgallery",
                "Type": "psmodule"
            },
            {
                "Name": "SqlServerDsc",
                "Source": "psgallery",
                "Type": "dscmodule"
            },
            {
                "Name": "AWSPowershell.NetCore",
                "Source": "psgallery",
                "Type": "psmodule"
            },
            {
                "Name": "ComputerManagementDsc",
                "Source": "psgallery",
                "Type": "dscmodule"
            },
            {
                "Name": "AnotherDependency_notFromS3",
                "Source": "psgallery",
                "Type": "dscmodule"
            },
            {
                "Access_key_id": "abc",
                "Access_key_secret": "defghi",
                "Bucket": "repository",
                "Endpoint": "https://s3.abc.com",
                "Name": "mssql_2022_standard_en",
                "Source": "S3",
                "Type": "iso"
            }
        ]
    }
    

    Variant 2 - iterate over dependencies

    In the second variant, you iterate over the dependencies list as the initial set and create a new list using the set_fact module. Depending on the source, the new item is first enriched with the service_secrets.

    An iteration step of a task is required for each entry in the dependencies list, but this ensures that the original sort order of the dependencies list is retained.

    Be careful if you do this several times for different dependencies lists that you start with an empty new variable.

    Task code

    - name: Variant 2 - iterate over dependencies
      set_fact:
        extended_dependencies: "{{ (extended_dependencies | default([])) + [new_item] }}"
      vars:
        new_item: "{{ item | combine(service_secrets) if item.Source == 'S3' else item }}"
      with_items: "{{ dependencies }}"
    
    - name: Print result from Variant 2
      debug:
        var: extended_dependencies
    

    Result

    TASK [Variant 2 - iterate over dependencies] **************************************************************************
    ok: [localhost] => (item={'Name': 'SqlServer', 'Source': 'psgallery', 'Type': 'psmodule'})
    ok: [localhost] => (item={'Name': 'SqlServerDsc', 'Source': 'psgallery', 'Type': 'dscmodule'})
    ok: [localhost] => (item={'Name': 'AWSPowershell.NetCore', 'Source': 'psgallery', 'Type': 'psmodule'})
    ok: [localhost] => (item={'Name': 'ComputerManagementDsc', 'Source': 'psgallery', 'Type': 'dscmodule'})
    ok: [localhost] => (item={'Name': 'mssql_2022_standard_en', 'Source': 'S3', 'Type': 'iso', 'Bucket': 'repository'})
    ok: [localhost] => (item={'Name': 'AnotherDependency_notFromS3', 'Source': 'psgallery', 'Type': 'dscmodule'})
    
    TASK [Print result from Variant 2] ************************************************************************************
    ok: [localhost] => {
        "extended_dependencies": [
            {
                "Name": "SqlServer",
                "Source": "psgallery",
                "Type": "psmodule"
            },
            {
                "Name": "SqlServerDsc",
                "Source": "psgallery",
                "Type": "dscmodule"
            },
            {
                "Name": "AWSPowershell.NetCore",
                "Source": "psgallery",
                "Type": "psmodule"
            },
            {
                "Name": "ComputerManagementDsc",
                "Source": "psgallery",
                "Type": "dscmodule"
            },
            {
                "Access_key_id": "abc",
                "Access_key_secret": "defghi",
                "Bucket": "repository",
                "Endpoint": "https://s3.abc.com",
                "Name": "mssql_2022_standard_en",
                "Source": "S3",
                "Type": "iso"
            },
            {
                "Name": "AnotherDependency_notFromS3",
                "Source": "psgallery",
                "Type": "dscmodule"
            }
        ]
    }