Run infra.controller_configuration role using multiple variable files to configure AAP

Scenario:

I want to use different roles in the Collection infra.controller_configuration to configure AAP - Ansible Automation Platform.

If possible I would like to use a structure as below for defining the variables of the objects that I want to create:

$ tree
.
|-- credentials
|   |-- credential_1.yml
|   |-- credential_2.yml
|-- inventories
|   |-- inventory_1.yml
|   |-- inventory_2.yml
|-- job_templates
|   |-- job_template_1.yml
|   |-- job_template_2.yml
|-- projects
|   |-- project_1.yml
|   |-- project_2.yml

It seems like the default behaviour for the roles in the infra.controller_configuration collection is to use one file as input for the configuration. Of course we can concatenate the configuration files and just use one variable file per role. But from a configuration perspective I think it is easier to maintain a structure were each object corresponds to a single file. Most files are quite small but if you look at for instance job_templates which includes a survey, these files can be big quite big. And if you have +100 job_templates it is not so nice to manage them in a single file.

Is it possible to use the roles to configure AAP using this kind of variable structure and have it to parse all files relevant per role?

Can you run a role in a loop where it is parsing each variable file as input?

I was first looking into include_vars to see if there was a way to merge multiple variable files and get a concatenated variable.

If you take the the two inventory files as an example:

$ cat inventories/inventory_1.yml
---
controller_inventories:
- name: "Inventory1"
  description: "Test"
  organization: "Org1"
...

$ cat inventories/inventory_2.yml
---
controller_inventories:
- name: "Inventory2"
  description: "Test"
  organization: "Org1"
...

The goal is either to be able to merge them so you get this as output:

---
controller_inventories:
- name: "Inventory1"
  description: "Test"
  organization: "Org1"
- name: "Inventory2"
  description: "Test"
  organization: "Org1"
...

This can be used as input for role infra.controller_configuration.inventories:

The official docs suggest that you can do like this to import vars:

pre_tasks:
    - name: Include vars from controller_configs directory
      ansible.builtin.include_vars:
        dir: ./yaml
        ignore_files: [controller_config.yml.template]
        extensions: ["yml"]

When I use it the result is that I only get the vars in the second inventory file:

ok: [bastion] => {
    "ansible_facts": {
        "controller_inventories": [
            {
                "description": "Test",
                "name": "Inventory2",
                "organization": "Org1"
            }
        ]
    },
    "ansible_included_var_files": [
        "/orgs/org1/inventories/inventory_1.yml",
        "/orgs/org1/inventories/inventory_2.yml"
    ],
    "changed": false
}

Any suggestions for how to move forward with this is very appriciated!

Testing on RHEL8 server using:
ansible-playbook [core 2.15.5]
config file = /etc/ansible/ansible.cfg
ansible python module location = /usr/lib/python3.9/site-packages/ansible
executable location = /bin/ansible-playbook
python version = 3.9.16 (main, May 31 2023, 12:21:58) [GCC 8.5.0 20210514 (Red Hat 8.5.0-18)] (/usr/bin/python3.9)
jinja version = 3.1.2
libyaml = True

You could use the ansible.builtin.combine filter with individual file contents. For example:

    - set_fact:
        controller_inventories: "{{ ( previous_vars | combine(new_vars, recursive=True, list_merge='append') ).controller_inventories }}"
      loop:
        # Could use a lookup to avoid manually listing
        - inventories/inventory_1.yml
        - inventories/inventory_2.yml
      vars:
        previous_vars: "{{ result.ansible_facts | default(ansible_facts) }}"
        new_vars: "{{ lookup('file', item) | from_yaml }}"
      register: result

Outputs:

...
ok: [localhost] => (item=inventories/inventory_2.yml) => {
    "ansible_facts": {
        "controller_inventories": [
            {
                "description": "Test",
                "name": "Inventory1",
                "organization": "Org1"
            },
            {
                "description": "Test",
                "name": "Inventory2",
                "organization": "Org1"
            }
        ]
    },
    "ansible_loop_var": "item",
    "changed": false,
    "item": "inventories/inventory_2.yml"
}
1 Like

Thanks @shertel for your response, I will investigate this.

What I would like is to do is to only give the path to the directory that has the yaml files and not have to hard code them. I was looking into the ansible find module but one drawback is that you have to use a fully qualified path as input. In my case I would like to use a relative path to the playbook directory since the fully qualified path is different depending on which user is running the playbook using their own git repository.

Is there is a way to create a var from the output of include_vars and ansible_included_vars_files and use that as input to the “combine-loop”:

    "ansible_included_var_files": [
        "/orgs/org1/inventories/inventory_1.yml",
        "/orgs/org1/inventories/inventory_2.yml"
    ],

Or does anyone has another suggestion how you just can point to the directory using a relative path and let ansible resolve which files are present and create a list of them.

My suggestion was to use a lookup (for example, ansible.builtin.fileglob) rather than hardcode, but you could also use include_vars result.

pre_tasks:
    - name: Include vars from controller_configs directory
      ansible.builtin.include_vars:
        dir: ./yaml
        ignore_files: [controller_config.yml.template]
        extensions: ["yml"]
      register: included_vars_files

    - name: Handle include_vars limitation with directories by remerging a var we need combined differently
      set_fact:
        controller_inventories: "{{ ( previous_vars | combine(new_vars, recursive=True, list_merge='append') ).controller_inventories }}"
      vars:
        previous_vars: "{{ result.ansible_facts | default(ansible_facts) }}"
        new_vars: "{{ lookup('file', item) | from_yaml }}"
      register: result
      loop: "{{ included_vars_files.ansible_included_var_files }}"