0

I want to deploy an application via ansible that has a configurable number of application nodes running via a systemd service. I have a role that manages these services for me but expects the name of the services along with e.g. the priority.

In my playbook the user defines something like this

application_nodes:
  - name: "node1"
    api_port: 3900
  - name: "node2"
    api_port: 3910

Therefore I need to append the list that this the systemd role manages. I tried the following

service_list: "{% for node in application_nodes %}{'name': 'application_{{ node.name }}.service', 'priority': 1000}{% endfor %}"
devture_systemd_service_manager_services_list_auto: |
  {{
    ([{{ service_list }}])
  }}

This results in the following error

FAILED! => {"msg": "An unhandled exception occurred while templating '{{\n  ([{{ service_list }}])\n}}\n'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: expected token ':', got '}'. String: {{\n  ([{{ service_list }}])\n}}\n. expected token ':', got '}'"}

I cannot figure out what I did wrong here.

I the end it should work like this (except automatically for every node)

devture_systemd_service_manager_services_list_auto: |
  {{
    ([{'name': 'application_node1.service', 'priority': 1000}])
    +
    ([{'name': 'application_node2.service', 'priority': 1000}])
  }}

I also considered something like this

- name: Append services list by nodes
  set_fact:
    devture_systemd_service_manager_services_list_auto: "{{ devture_systemd_service_manager_services_list_auto + [{'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}] }}"
  loop: "{{ application_nodes }}"

- name: "Print services"
  debug:
    msg: "services {{ devture_systemd_service_manager_services_list_auto }}"

Where the error is similar to this issue but the solution does not work for me as I want to access a specific key of the dictionary

TASK [custom/base : Print services B] ********************************************************************************************************************************************************************************************************************
fatal: [mydomain]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: [{'name': 'application_node2.service', 'priority': 1000, 'groups': ['applicationname']}, {'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}]: 'item' is undefined. 'item' is undefined. [{'name': 'application_node2.service', 'priority': 1000, 'groups': ['applicationname']}, {'name': 'application_{{ item.name }}.service', 'priority': 1000, 'groups': ['applicationname']}]: 'item' is undefined. 'item' is undefined\n\nThe error appears to be in '/.../roles/custom/base/tasks/add_services.yml': line 11, 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- name: \"Print services B\"\n  ^ here\n"}

Zeitounator
  • 38,476
  • 7
  • 53
  • 66
Moanos
  • 314
  • 3
  • 11

2 Answers2

2

To start with, the var definition in the role your are using is overly complicated for something as simple as a list of dicts. This is a source of confusion. So to be clear the following expression:

devture_systemd_service_manager_services_list_auto: |
  {{
    ([{'name': 'application_node1.service', 'priority': 1000}])
    +
    ([{'name': 'application_node2.service', 'priority': 1000}])
  }}

is actually the exact equivalent of:

devture_systemd_service_manager_services_list_auto:
  - name: application_node1.service
    priority: 1000
  - name: application_node2.service
    priority: 1000

The second source of confusion IMO is that you used the term append (which is in Ansible the fact to add more elements to a list) rather than combine (i.e. adding/updating keys in one dict from an other dict).

Those precisions being made, we can fulfill your requirement by:

  1. Extracting the name attribute from each element in the original list
  2. Transform that name so that it matches your expected service name
  3. Append a dict containing that name and the expected priority in relevant keys to a result list

Note that having to transform the name makes it difficult to avoid looping inside a template for the var definition (which we generally tend to avoid in Ansible). But this can still be done in one single expression as demonstrated in the following test playbook:

---
- hosts: localhost
  gather_facts: false

  vars:
    application_nodes:
      - name: "node1"
        api_port: 3900
      - name: "node2"
        api_port: 3910

    devture_systemd_service_manager_services_list_auto: >-
      {%- set result = [] -%}
      {%- for node in application_nodes -%}
      {%- set service_name = node.name | regex_replace('^(.*)$', 'application_\\1.service') -%}
      {{ result.append({'name': service_name, 'priority': 1000}) }}
      {%- endfor -%}
      {{ result }}

  tasks:
    - name: Show our list
      ansible.builtin.debug:
        var: devture_systemd_service_manager_services_list_auto

which gives (relevant task output only)

TASK [Show our list] *********************************************************
ok: [localhost] => {
    "devture_systemd_service_manager_services_list_auto": [
        {
            "name": "application_node1.service",
            "priority": 1000
        },
        {
            "name": "application_node2.service",
            "priority": 1000
        }
    ]
}
Zeitounator
  • 38,476
  • 7
  • 53
  • 66
1

You almost had it in your first try. If you add a few things to your service_list it ends up being a list of dicts -- which is ultimately what you want to append to devture_systemd_service_manager_services_list_auto.

- hosts: all
  vars:
    application_nodes:
      - name: "node1"
        api_port: 3900
      - name: "node2"
        api_port: 3910
  tasks:
    - set_fact:
        # The square brackets before and after the loop make this an actual
        # list.
        # To make sure that the list items are separated from each other, we
        # have to add a comma after each item. Luckily Ansible / Python isn't
        # too picky about a comma without a final item. ;-)
        service_list: >
          [
          {% for node in application_nodes %}
          {'name': 'application_{{ node.name }}.service', 'priority': 1000},
          {% endfor %}"
          ]
    - set_fact:
        devture_systemd_service_manager_services_list_auto: >
          {{ devture_systemd_service_manager_services_list_auto|default([]) +
          service_list }}
    - debug:
        var: devture_systemd_service_manager_services_list_auto
RobertL
  • 76
  • 1
  • 6