Skip to content

Instantly share code, notes, and snippets.

@3coma3
Last active March 26, 2020 21:48
Show Gist options
  • Save 3coma3/398993bd523a4f0edec8e6d63cbd82e0 to your computer and use it in GitHub Desktop.
Save 3coma3/398993bd523a4f0edec8e6d63cbd82e0 to your computer and use it in GitHub Desktop.
Generic subtask dispatcher for multiplaform support in roles, using filename conventions
---
# Enforce order for task inclusion using filename tokens:
# 1) numeric index
# 2) component list
# 3) increasing platform specificity
- include_tasks:
file: '{{ taskfile }}'
loop: |
{%- set taskfiles = [] -%}
{%- set delimiter = '_' -%}
{%- set tfs_dir = role_path + '/tasks/' + stage + '/' -%}
{%- set tfs_paths = lookup('fileglob', tfs_dir + '*', wantlist=True) -%}
{%- macro validate(tokens) -%}
{%- set path = tfs_dir ~ (tokens | join(delimiter) | lower) ~ '.yml' -%}
{%- if path in tfs_paths and path not in taskfiles -%}
{%- do taskfiles.append(path) -%}
{%- endif -%}
{%- endmacro -%}
{%- for i in tfs_paths | map('regex_replace', '^.*/([0-9]+)' + delimiter + '[^/]*$', '\\1')
| map('int')
| sort -%}
{%- for c in components -%}
{%- do validate([i, c]) -%}
{%- do validate([i, c, ansible_os_family]) -%}
{%- do validate([i, c, ansible_distribution]) -%}
{%- do validate([i, c, ansible_distribution, ansible_distribution_release]) -%}
{%- do validate([i, c, ansible_distribution, ansible_distribution_major_version]) -%}
{%- endfor -%}
{%- endfor -%}
{{ taskfiles }}
loop_control:
loop_var: taskfile
@3coma3
Copy link
Author

3coma3 commented Mar 21, 2020

This is a front main.yml I'm using on a role that builds templates for LXC and ISO images for Qemu (called generically images). Images may have multiple components enabled, across many distributions. Components can be something like networking, management user / SSH keys / sudo setup, mesh (and other) agents setup, and so on. Necessary things for an initial boot.

The tasks subdirectory for this role only contains this file and then stage subdirectories, where tasks exist for three main stages: "pre-chroot", "chroot", and "post-chroot". The chroot is used for tasks like installing packages and managing users with standard modules. Pre and post stages are used to prepare and clean the chroot environment, uploading the image to the virtualization environment, and other stuff.

The naming convention for taskfiles is based on tokens:

  1. a numeric index used to organize the inclusion:
  2. the component name (what otherwise would be the taskfile name)
  3. then optional tokens by increasing platform specificity, separated by a configurable delimiter, I use the underscore

example
role/tasks/<stage>/<index>_<task name (component)>_<optional platform specific tokens>

Optional platform specific tokens and their priority:

{{ ansible_os_family }}
{{ ansible_distribution }}
{{ ansible_distribution}}_{{ ansible_distribution_release }}
{{ ansible_distribution }}_{{ ansible_distribution_major_version }}

what this accomplishes
There's no need anymore to intersperse when clauses over the role tasks, it becomes implicit in the tasks directory and file layout.

"Callers", like playbooks or other roles that include this role, only need to pass a list of desired components. Adding those components is done in a predictable manner across all stages, while retaining ability to override the component sequence (using the index) and to isolate all the sections specific to each supported platform.

The ansible_* distribution variables could be even passed from callers or be extended directly into a generic tag system to manage task includes.

downsides
Increasing complexity. This "frontend dispatcher" attempts to capture the most complex logic, but execution order can get harder to follow. It's worth having into account though that all this is mostly useful for roles that already are complex enough and also need extensive multi platform support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment