Maros Mitucha
Maros Mitucha

Reputation: 85

Ansible - vars are not correctly propagated to handlers when role run in loop

I am asking for help with a problem of deploying multiple versions (different variables) of the app with the same role run from one playbook.

We have an app with multiple product families, which are different code versions. Each version has separate uWSGI vassal config and Nginx virtualhost config(/api/v2, /api/v3, ...).

The desired state would be to run playbook and configure the server with all versions specified.

Sadly, ansible's import_role/import_tasks can't be used with with_items, so include_role/include_tasks must be used (pitty because they do not honor role tags).

The include_role method would not be the biggest problem, but we use handlers to notify uWSGI touch to reload - on a code change, link change, virtualenv change, app_config change, ...).
But when using loop (with_items), the variables passed from the loop does not correctly propagate to handlers.

I tried this scenarios

playbook.yml - with_items loop inside the playbook

PROBLEM: Handler is run only for the first iteration of the loop.

#!/usr/bin/env ansible-playbook

# HAndler is run only once, from first notifier
- hosts: localhost
  gather_facts: no
  vars:
    app_root: "/tmp/test_ansible"
    app_versions:
      - app_product_family: 1
        app_release: "v1.0.2"
      - app_product_family: 3
        app_release: "v4.0.7"
  tasks:
   - name: Deploy multiple versions of app
     include_role:
       name: app
     with_items: "{{ app_versions }}"
     loop_control:
       loop_var: app_version
     vars:
       app_product_family: "{{ app_version.app_product_family }}"
       app_release: "{{ app_version.app_release }}"
     tags:
       - app
       - app_debug

playbook_v2.yml - with_items loop inside role task

PROBLEM: Handler is run with the default value from "Defaults"

#!/usr/bin/env ansible-playbook

- hosts: localhost
  gather_facts: no
  roles:
    - app_v2
  vars:
    app_v2_root: "/tmp/test_ansible_v2"
    app_v2_versions:
      - app_v2_product_family: 1
        app_v2_release: "v1.0.2"
      - app_v2_product_family: 3
        app_v2_release: "v4.0.7"

Tasks roles/app_v2/main.yml

---
# Workaround because import_tasks can't be run with_items
- include_tasks: deploy.yml
  when: app_v2_versions
  with_items: "{{ app_v2_versions }}"
  loop_control:
    loop_var: app_v2
  vars:
    app_v2_product_family: "{{ app_v2.app_v2_product_family }}"
    app_v2_release: "{{ app_v2.app_v2_release }}"
  tags:
    - app_v2
    - app_v2_deploy
...

One idea was about writing a separate role for each product family, but they share nginx and uWSGI, so it will be lots of copy-pasting and sharing tasks (so tags would not work properly).
For now, I solved it with shell script wrapper, but this is not an ideal solution and does not work from Ansible tower.

Sample repo with tasks to reproduce problem (tested with ansible 2.4, 2.5, 2.6)

Any ideas & recommendations are very welcome.

Upvotes: 1

Views: 1683

Answers (2)

Maros Mitucha
Maros Mitucha

Reputation: 85

Ok, It is a bug as @George Shuklin posted.

I will use my shell wrapper, which reads group_vars yaml and then runs the playbook multiple times according to the variable list length.

Sadly I hit multiple annoying bugs in ansible in last few weeks, kinda losing my trust in it ):

And probably everybody is using microservices and kubernetes, so need to speed up our migration (:

Upvotes: 0

George Shuklin
George Shuklin

Reputation: 7877

The order of overrides for variables is broken for includes in Ansible. F.e. even set_fact in the included role will be shadowed by role defaults.

See this bug: https://github.com/ansible/ansible/issues/22025

It's closed but not fixed. My advice: use include and variables really carefully.

In practice I never use role includes with loop. If you need loop, include a tasklist in this loop (and that tasklist, in turn, may import_role).

Upvotes: 1

Related Questions