mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-07-17 22:14:25 +02:00
Solved run_after dependency bug
This commit is contained in:
parent
39d2e6c0fa
commit
a69b2c9cb2
6
Makefile
6
Makefile
@ -7,8 +7,8 @@ INCLUDES_OUT := ./tasks/utils/web-app-roles.yml
|
|||||||
INCLUDES_SCRIPT := ./cli/generate_playbook.py
|
INCLUDES_SCRIPT := ./cli/generate_playbook.py
|
||||||
|
|
||||||
EXTRA_USERS := $(shell \
|
EXTRA_USERS := $(shell \
|
||||||
find $(ROLES_DIR) -maxdepth 1 -type d -name 'docker*' -printf '%f\n' \
|
find $(ROLES_DIR) -maxdepth 1 -type d -name 'web-app*' -printf '%f\n' \
|
||||||
| sed -E 's/^docker[_-]?//' \
|
| sed -E 's/^web-app[_-]?//' \
|
||||||
| grep -E -x '[a-z0-9]+' \
|
| grep -E -x '[a-z0-9]+' \
|
||||||
| paste -sd, - \
|
| paste -sd, - \
|
||||||
)
|
)
|
||||||
@ -24,7 +24,7 @@ build:
|
|||||||
@echo "🔧 Generating users defaults → $(USERS_OUT) from roles in $(ROLES_DIR)…"
|
@echo "🔧 Generating users defaults → $(USERS_OUT) from roles in $(ROLES_DIR)…"
|
||||||
@echo "🔧 Generating Docker role includes → $(INCLUDES_OUT)…"
|
@echo "🔧 Generating Docker role includes → $(INCLUDES_OUT)…"
|
||||||
@mkdir -p $(dir $(INCLUDES_OUT))
|
@mkdir -p $(dir $(INCLUDES_OUT))
|
||||||
python3 $(INCLUDES_SCRIPT) $(ROLES_DIR) -o $(INCLUDES_OUT) -p web-app-
|
python3 $(INCLUDES_SCRIPT) $(ROLES_DIR) -o $(INCLUDES_OUT) -p web-app- -p svc-openldap -p svc-rdbms-postgres -p svc-rdbms-mariadb
|
||||||
@echo "✅ Docker role includes written to $(INCLUDES_OUT)"
|
@echo "✅ Docker role includes written to $(INCLUDES_OUT)"
|
||||||
|
|
||||||
install: build
|
install: build
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import yaml
|
import yaml
|
||||||
import argparse
|
import argparse
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
|
|
||||||
def find_roles(roles_dir, prefix=None):
|
def find_roles(roles_dir, prefixes=None):
|
||||||
"""Find all roles in the given directory."""
|
"""
|
||||||
|
Find all roles in the given directory whose names start with
|
||||||
|
any of the provided prefixes. If prefixes is empty or None,
|
||||||
|
include all roles.
|
||||||
|
"""
|
||||||
for entry in os.listdir(roles_dir):
|
for entry in os.listdir(roles_dir):
|
||||||
if prefix and not entry.startswith(prefix):
|
if prefixes:
|
||||||
continue
|
if not any(entry.startswith(pref) for pref in prefixes):
|
||||||
|
continue
|
||||||
path = os.path.join(roles_dir, entry)
|
path = os.path.join(roles_dir, entry)
|
||||||
meta_file = os.path.join(path, 'meta', 'main.yml')
|
meta_file = os.path.join(path, 'meta', 'main.yml')
|
||||||
if os.path.isdir(path) and os.path.isfile(meta_file):
|
if os.path.isdir(path) and os.path.isfile(meta_file):
|
||||||
@ -28,16 +36,21 @@ def load_application_id(role_path):
|
|||||||
return data.get('application_id')
|
return data.get('application_id')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def build_dependency_graph(roles_dir, prefix=None):
|
def build_dependency_graph(roles_dir, prefixes=None):
|
||||||
"""Build a dependency graph where each role points to the roles it depends on."""
|
"""
|
||||||
|
Build a dependency graph where each key is a role name and
|
||||||
|
its value is a list of roles that depend on it.
|
||||||
|
Also return in_degree counts and the roles metadata map.
|
||||||
|
"""
|
||||||
graph = defaultdict(list)
|
graph = defaultdict(list)
|
||||||
in_degree = defaultdict(int)
|
in_degree = defaultdict(int)
|
||||||
roles = {}
|
roles = {}
|
||||||
|
|
||||||
for role_path, meta_file in find_roles(roles_dir, prefix):
|
for role_path, meta_file in find_roles(roles_dir, prefixes):
|
||||||
run_after = load_run_after(meta_file)
|
run_after = load_run_after(meta_file)
|
||||||
application_id = load_application_id(role_path)
|
application_id = load_application_id(role_path)
|
||||||
role_name = os.path.basename(role_path)
|
role_name = os.path.basename(role_path)
|
||||||
|
|
||||||
roles[role_name] = {
|
roles[role_name] = {
|
||||||
'role_name': role_name,
|
'role_name': role_name,
|
||||||
'run_after': run_after,
|
'run_after': run_after,
|
||||||
@ -45,37 +58,87 @@ def build_dependency_graph(roles_dir, prefix=None):
|
|||||||
'path': role_path
|
'path': role_path
|
||||||
}
|
}
|
||||||
|
|
||||||
# If the role has dependencies, build the graph
|
|
||||||
for dependency in run_after:
|
for dependency in run_after:
|
||||||
graph[dependency].append(role_name)
|
graph[dependency].append(role_name)
|
||||||
in_degree[role_name] += 1
|
in_degree[role_name] += 1
|
||||||
|
|
||||||
# Ensure roles with no dependencies have an in-degree of 0
|
|
||||||
if role_name not in in_degree:
|
if role_name not in in_degree:
|
||||||
in_degree[role_name] = 0
|
in_degree[role_name] = 0
|
||||||
|
|
||||||
return graph, in_degree, roles
|
return graph, in_degree, roles
|
||||||
|
|
||||||
def topological_sort(graph, in_degree):
|
def find_cycle(roles):
|
||||||
"""Perform topological sort on the dependency graph."""
|
"""
|
||||||
# Queue for roles with no incoming dependencies (in_degree == 0)
|
Detect a cycle in the run_after relations:
|
||||||
queue = deque([role for role, degree in in_degree.items() if degree == 0])
|
roles: dict mapping role_name -> { 'run_after': [...], ... }
|
||||||
|
Returns a list of role_names forming the cycle (with the start repeated at end), or None.
|
||||||
|
"""
|
||||||
|
visited = set()
|
||||||
|
stack = set()
|
||||||
|
|
||||||
|
def dfs(node, path):
|
||||||
|
visited.add(node)
|
||||||
|
stack.add(node)
|
||||||
|
path.append(node)
|
||||||
|
for dep in roles.get(node, {}).get('run_after', []):
|
||||||
|
if dep not in visited:
|
||||||
|
res = dfs(dep, path)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
elif dep in stack:
|
||||||
|
idx = path.index(dep)
|
||||||
|
return path[idx:] + [dep]
|
||||||
|
stack.remove(node)
|
||||||
|
path.pop()
|
||||||
|
return None
|
||||||
|
|
||||||
|
for role in roles:
|
||||||
|
if role not in visited:
|
||||||
|
cycle = dfs(role, [])
|
||||||
|
if cycle:
|
||||||
|
return cycle
|
||||||
|
return None
|
||||||
|
|
||||||
|
def topological_sort(graph, in_degree, roles=None):
|
||||||
|
"""
|
||||||
|
Perform topological sort on the dependency graph.
|
||||||
|
If `roles` is provided, on error it will include detailed debug info.
|
||||||
|
"""
|
||||||
|
queue = deque([r for r, d in in_degree.items() if d == 0])
|
||||||
sorted_roles = []
|
sorted_roles = []
|
||||||
|
local_in = dict(in_degree)
|
||||||
|
|
||||||
while queue:
|
while queue:
|
||||||
role = queue.popleft()
|
role = queue.popleft()
|
||||||
sorted_roles.append(role)
|
sorted_roles.append(role)
|
||||||
|
for nbr in graph.get(role, []):
|
||||||
# Reduce in-degree for roles dependent on the current role
|
local_in[nbr] -= 1
|
||||||
for neighbor in graph[role]:
|
if local_in[nbr] == 0:
|
||||||
in_degree[neighbor] -= 1
|
queue.append(nbr)
|
||||||
if in_degree[neighbor] == 0:
|
|
||||||
queue.append(neighbor)
|
|
||||||
|
|
||||||
if len(sorted_roles) != len(in_degree):
|
if len(sorted_roles) != len(in_degree):
|
||||||
# If the number of sorted roles doesn't match the number of roles,
|
cycle = find_cycle(roles or {})
|
||||||
# there was a cycle in the graph (not all roles could be sorted)
|
if roles is not None:
|
||||||
raise Exception("Circular dependency detected among the roles!")
|
if cycle:
|
||||||
|
header = f"Circular dependency detected: {' -> '.join(cycle)}"
|
||||||
|
else:
|
||||||
|
header = "Circular dependency detected among the roles!"
|
||||||
|
|
||||||
|
unsorted = [r for r in in_degree if r not in sorted_roles]
|
||||||
|
detail_lines = ["Unsorted roles and their dependencies:"]
|
||||||
|
for r in unsorted:
|
||||||
|
deps = roles.get(r, {}).get('run_after', [])
|
||||||
|
detail_lines.append(f" - {r} depends on {deps!r}")
|
||||||
|
|
||||||
|
detail_lines.append("Full dependency graph:")
|
||||||
|
detail_lines.append(f" {dict(graph)!r}")
|
||||||
|
|
||||||
|
raise Exception("\n".join([header] + detail_lines))
|
||||||
|
else:
|
||||||
|
if cycle:
|
||||||
|
raise Exception(f"Circular dependency detected: {' -> '.join(cycle)}")
|
||||||
|
else:
|
||||||
|
raise Exception("Circular dependency detected among the roles!")
|
||||||
|
|
||||||
return sorted_roles
|
return sorted_roles
|
||||||
|
|
||||||
@ -83,48 +146,38 @@ def print_dependency_tree(graph):
|
|||||||
"""Print the dependency tree visually on the console."""
|
"""Print the dependency tree visually on the console."""
|
||||||
def print_node(role, indent=0):
|
def print_node(role, indent=0):
|
||||||
print(" " * indent + role)
|
print(" " * indent + role)
|
||||||
for dependency in graph[role]:
|
for dep in graph.get(role, []):
|
||||||
print_node(dependency, indent + 1)
|
print_node(dep, indent + 1)
|
||||||
|
|
||||||
# Print the tree starting from roles with no dependencies
|
|
||||||
all_roles = set(graph.keys())
|
all_roles = set(graph.keys())
|
||||||
dependent_roles = {role for dependencies in graph.values() for role in dependencies}
|
dependent = {r for deps in graph.values() for r in deps}
|
||||||
root_roles = all_roles - dependent_roles
|
roots = all_roles - dependent
|
||||||
|
|
||||||
printed_roles = []
|
for root in roots:
|
||||||
|
print_node(root)
|
||||||
|
|
||||||
def collect_roles(role, indent=0):
|
def generate_playbook_entries(roles_dir, prefixes=None):
|
||||||
printed_roles.append(role)
|
"""
|
||||||
for dependency in graph[role]:
|
Generate playbook entries based on the sorted order.
|
||||||
collect_roles(dependency, indent + 1)
|
Raises a ValueError if application_id is missing.
|
||||||
|
"""
|
||||||
for root in root_roles:
|
graph, in_degree, roles = build_dependency_graph(roles_dir, prefixes)
|
||||||
collect_roles(root)
|
sorted_names = topological_sort(graph, in_degree, roles)
|
||||||
|
|
||||||
return printed_roles
|
|
||||||
|
|
||||||
def generate_playbook_entries(roles_dir, prefix=None):
|
|
||||||
"""Generate playbook entries based on the sorted order."""
|
|
||||||
graph, in_degree, roles = build_dependency_graph(roles_dir, prefix)
|
|
||||||
|
|
||||||
# Detect cycles and get correct topological order
|
|
||||||
sorted_role_names = topological_sort(graph, in_degree)
|
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
for role_name in sorted_role_names:
|
for role_name in sorted_names:
|
||||||
role = roles[role_name]
|
role = roles[role_name]
|
||||||
|
|
||||||
# --- new validation block ---
|
|
||||||
if role.get('application_id') is None:
|
if role.get('application_id') is None:
|
||||||
raise ValueError(f"Role '{role_name}' is missing an application_id")
|
vars_file = os.path.join(role['path'], 'vars', 'main.yml')
|
||||||
# ----------------------------
|
raise ValueError(f"'application_id' missing in {vars_file}")
|
||||||
|
|
||||||
app_id = role['application_id']
|
app_id = role['application_id']
|
||||||
entries.append(
|
entries.append(
|
||||||
f"- name: setup {app_id}\n"
|
f"- name: setup {app_id}\n"
|
||||||
f" when: ('{app_id}' | application_allowed(group_names, allowed_applications))\n"
|
f" when: ('{app_id}' | application_allowed(group_names, allowed_applications))\n"
|
||||||
f" include_role:\n"
|
f" include_role:\n"
|
||||||
f" name: {role['role_name']}\n"
|
f" name: {role_name}\n"
|
||||||
)
|
)
|
||||||
entries.append(
|
entries.append(
|
||||||
f"- name: flush handlers after {app_id}\n"
|
f"- name: flush handlers after {app_id}\n"
|
||||||
@ -137,32 +190,30 @@ def main():
|
|||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description='Generate an Ansible playbook include file from Docker roles, sorted by run_after order.'
|
description='Generate an Ansible playbook include file from Docker roles, sorted by run_after order.'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument('roles_dir', help='Path to directory containing role folders')
|
||||||
'roles_dir',
|
|
||||||
help='Path to directory containing role folders'
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--prefix',
|
'-p', '--prefix',
|
||||||
help='Only include roles whose names start with this prefix (e.g. web-app-, desk-)',
|
action='append',
|
||||||
default=None
|
help='Only include roles whose names start with any of these prefixes; can be specified multiple times'
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument('-o', '--output', default=None,
|
||||||
'-o', '--output',
|
help='Output file path (default: stdout)')
|
||||||
help='Output file path (default: stdout)',
|
parser.add_argument('-t', '--tree', action='store_true',
|
||||||
default=None
|
help='Display the dependency tree of roles and exit')
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'-t', '--tree',
|
|
||||||
action='store_true',
|
|
||||||
help='Display the dependency tree of roles visually'
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Generate and output the playbook entries
|
args = parser.parse_args()
|
||||||
entries = generate_playbook_entries(args.roles_dir, args.prefix)
|
prefixes = args.prefix or []
|
||||||
|
|
||||||
|
if args.tree:
|
||||||
|
graph, _, _ = build_dependency_graph(args.roles_dir, prefixes)
|
||||||
|
print_dependency_tree(graph)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
entries = generate_playbook_entries(args.roles_dir, prefixes)
|
||||||
output = ''.join(entries)
|
output = ''.join(entries)
|
||||||
|
|
||||||
if args.output:
|
if args.output:
|
||||||
|
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
||||||
with open(args.output, 'w') as f:
|
with open(args.output, 'w') as f:
|
||||||
f.write(output)
|
f.write(output)
|
||||||
print(f"Playbook entries written to {args.output}")
|
print(f"Playbook entries written to {args.output}")
|
||||||
|
@ -1,32 +1,8 @@
|
|||||||
- name: setup presentation
|
- name: setup roulette-wheel
|
||||||
when: ('presentation' | application_allowed(group_names, allowed_applications))
|
when: ('roulette-wheel' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-presentation
|
name: web-app-roulette-wheel
|
||||||
- name: flush handlers after presentation
|
- name: flush handlers after roulette-wheel
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup matrix-deprecated
|
|
||||||
when: ('matrix-deprecated' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-matrix-deprecated
|
|
||||||
- name: flush handlers after matrix-deprecated
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup postgres
|
|
||||||
when: ('postgres' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: svc-rdbms-postgres
|
|
||||||
- name: flush handlers after postgres
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup syncope
|
|
||||||
when: ('syncope' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-syncope
|
|
||||||
- name: flush handlers after syncope
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup elk
|
|
||||||
when: ('elk' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-elk
|
|
||||||
- name: flush handlers after elk
|
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup ldap
|
- name: setup ldap
|
||||||
when: ('ldap' | application_allowed(group_names, allowed_applications))
|
when: ('ldap' | application_allowed(group_names, allowed_applications))
|
||||||
@ -34,17 +10,11 @@
|
|||||||
name: svc-openldap
|
name: svc-openldap
|
||||||
- name: flush handlers after ldap
|
- name: flush handlers after ldap
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup None
|
- name: setup simpleicons
|
||||||
when: ('None' | application_allowed(group_names, allowed_applications))
|
when: ('simpleicons' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: docker-compose
|
name: web-app-simpleicons
|
||||||
- name: flush handlers after None
|
- name: flush handlers after simpleicons
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup collabora
|
|
||||||
when: ('collabora' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-collabora
|
|
||||||
- name: flush handlers after collabora
|
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup sphinx
|
- name: setup sphinx
|
||||||
when: ('sphinx' | application_allowed(group_names, allowed_applications))
|
when: ('sphinx' | application_allowed(group_names, allowed_applications))
|
||||||
@ -52,29 +22,59 @@
|
|||||||
name: web-app-sphinx
|
name: web-app-sphinx
|
||||||
- name: flush handlers after sphinx
|
- name: flush handlers after sphinx
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
|
- name: setup presentation
|
||||||
|
when: ('presentation' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-presentation
|
||||||
|
- name: flush handlers after presentation
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup libretranslate
|
||||||
|
when: ('libretranslate' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-libretranslate
|
||||||
|
- name: flush handlers after libretranslate
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup postgres
|
||||||
|
when: ('postgres' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: svc-rdbms-postgres
|
||||||
|
- name: flush handlers after postgres
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup matrix-deprecated
|
||||||
|
when: ('matrix-deprecated' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-matrix-deprecated
|
||||||
|
- name: flush handlers after matrix-deprecated
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup syncope
|
||||||
|
when: ('syncope' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-syncope
|
||||||
|
- name: flush handlers after syncope
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup pretix
|
||||||
|
when: ('pretix' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-pretix
|
||||||
|
- name: flush handlers after pretix
|
||||||
|
meta: flush_handlers
|
||||||
- name: setup mariadb
|
- name: setup mariadb
|
||||||
when: ('mariadb' | application_allowed(group_names, allowed_applications))
|
when: ('mariadb' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: svc-rdbms-mariadb
|
name: svc-rdbms-mariadb
|
||||||
- name: flush handlers after mariadb
|
- name: flush handlers after mariadb
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup simpleicons
|
- name: setup elk
|
||||||
when: ('simpleicons' | application_allowed(group_names, allowed_applications))
|
when: ('elk' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-simpleicons
|
name: web-app-elk
|
||||||
- name: flush handlers after simpleicons
|
- name: flush handlers after elk
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup None
|
- name: setup collabora
|
||||||
when: ('None' | application_allowed(group_names, allowed_applications))
|
when: ('collabora' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: svc-rdbms-central
|
name: web-app-collabora
|
||||||
- name: flush handlers after None
|
- name: flush handlers after collabora
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup roulette-wheel
|
|
||||||
when: ('roulette-wheel' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-roulette-wheel
|
|
||||||
- name: flush handlers after roulette-wheel
|
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup jenkins
|
- name: setup jenkins
|
||||||
when: ('jenkins' | application_allowed(group_names, allowed_applications))
|
when: ('jenkins' | application_allowed(group_names, allowed_applications))
|
||||||
@ -82,29 +82,35 @@
|
|||||||
name: web-app-jenkins
|
name: web-app-jenkins
|
||||||
- name: flush handlers after jenkins
|
- name: flush handlers after jenkins
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup matomo
|
|
||||||
when: ('matomo' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-matomo
|
|
||||||
- name: flush handlers after matomo
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup portfolio
|
- name: setup portfolio
|
||||||
when: ('portfolio' | application_allowed(group_names, allowed_applications))
|
when: ('portfolio' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-portfolio
|
name: web-app-portfolio
|
||||||
- name: flush handlers after portfolio
|
- name: flush handlers after portfolio
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
|
- name: setup matomo
|
||||||
|
when: ('matomo' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-matomo
|
||||||
|
- name: flush handlers after matomo
|
||||||
|
meta: flush_handlers
|
||||||
- name: setup keycloak
|
- name: setup keycloak
|
||||||
when: ('keycloak' | application_allowed(group_names, allowed_applications))
|
when: ('keycloak' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-keycloak
|
name: web-app-keycloak
|
||||||
- name: flush handlers after keycloak
|
- name: flush handlers after keycloak
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup yourls
|
- name: setup mailu
|
||||||
when: ('yourls' | application_allowed(group_names, allowed_applications))
|
when: ('mailu' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-yourls
|
name: web-app-mailu
|
||||||
- name: flush handlers after yourls
|
- name: flush handlers after mailu
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup taiga
|
||||||
|
when: ('taiga' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-taiga
|
||||||
|
- name: flush handlers after taiga
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup wordpress
|
- name: setup wordpress
|
||||||
when: ('wordpress' | application_allowed(group_names, allowed_applications))
|
when: ('wordpress' | application_allowed(group_names, allowed_applications))
|
||||||
@ -112,11 +118,17 @@
|
|||||||
name: web-app-wordpress
|
name: web-app-wordpress
|
||||||
- name: flush handlers after wordpress
|
- name: flush handlers after wordpress
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup pixelfed
|
- name: setup pgadmin
|
||||||
when: ('pixelfed' | application_allowed(group_names, allowed_applications))
|
when: ('pgadmin' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-pixelfed
|
name: web-app-pgadmin
|
||||||
- name: flush handlers after pixelfed
|
- name: flush handlers after pgadmin
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup lam
|
||||||
|
when: ('lam' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-lam
|
||||||
|
- name: flush handlers after lam
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup peertube
|
- name: setup peertube
|
||||||
when: ('peertube' | application_allowed(group_names, allowed_applications))
|
when: ('peertube' | application_allowed(group_names, allowed_applications))
|
||||||
@ -124,6 +136,48 @@
|
|||||||
name: web-app-peertube
|
name: web-app-peertube
|
||||||
- name: flush handlers after peertube
|
- name: flush handlers after peertube
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
|
- name: setup yourls
|
||||||
|
when: ('yourls' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-yourls
|
||||||
|
- name: flush handlers after yourls
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup phpldapadmin
|
||||||
|
when: ('phpldapadmin' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-phpldapadmin
|
||||||
|
- name: flush handlers after phpldapadmin
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup mastodon
|
||||||
|
when: ('mastodon' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-mastodon
|
||||||
|
- name: flush handlers after mastodon
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup friendica
|
||||||
|
when: ('friendica' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-friendica
|
||||||
|
- name: flush handlers after friendica
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup pixelfed
|
||||||
|
when: ('pixelfed' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-pixelfed
|
||||||
|
- name: flush handlers after pixelfed
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup bigbluebutton
|
||||||
|
when: ('bigbluebutton' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-bigbluebutton
|
||||||
|
- name: flush handlers after bigbluebutton
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup moodle
|
||||||
|
when: ('moodle' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-moodle
|
||||||
|
- name: flush handlers after moodle
|
||||||
|
meta: flush_handlers
|
||||||
- name: setup phpmyadmin
|
- name: setup phpmyadmin
|
||||||
when: ('phpmyadmin' | application_allowed(group_names, allowed_applications))
|
when: ('phpmyadmin' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
@ -136,71 +190,83 @@
|
|||||||
name: web-app-openproject
|
name: web-app-openproject
|
||||||
- name: flush handlers after openproject
|
- name: flush handlers after openproject
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup phpldapadmin
|
- name: setup mobilizon
|
||||||
when: ('phpldapadmin' | application_allowed(group_names, allowed_applications))
|
when: ('mobilizon' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-phpldapadmin
|
name: web-app-mobilizon
|
||||||
- name: flush handlers after phpldapadmin
|
- name: flush handlers after mobilizon
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup friendica
|
- name: setup funkwhale
|
||||||
when: ('friendica' | application_allowed(group_names, allowed_applications))
|
when: ('funkwhale' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-friendica
|
name: web-app-funkwhale
|
||||||
- name: flush handlers after friendica
|
- name: flush handlers after funkwhale
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup taiga
|
- name: setup matrix
|
||||||
when: ('taiga' | application_allowed(group_names, allowed_applications))
|
when: ('matrix' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-taiga
|
name: web-app-matrix
|
||||||
- name: flush handlers after taiga
|
- name: flush handlers after matrix
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup bigbluebutton
|
- name: setup baserow
|
||||||
when: ('bigbluebutton' | application_allowed(group_names, allowed_applications))
|
when: ('baserow' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-bigbluebutton
|
name: web-app-baserow
|
||||||
- name: flush handlers after bigbluebutton
|
- name: flush handlers after baserow
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup lam
|
- name: setup gitea
|
||||||
when: ('lam' | application_allowed(group_names, allowed_applications))
|
when: ('gitea' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-lam
|
name: web-app-gitea
|
||||||
- name: flush handlers after lam
|
- name: flush handlers after gitea
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup mastodon
|
- name: setup bluesky
|
||||||
when: ('mastodon' | application_allowed(group_names, allowed_applications))
|
when: ('bluesky' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-mastodon
|
name: web-app-bluesky
|
||||||
- name: flush handlers after mastodon
|
- name: flush handlers after bluesky
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup pgadmin
|
- name: setup mediawiki
|
||||||
when: ('pgadmin' | application_allowed(group_names, allowed_applications))
|
when: ('mediawiki' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-pgadmin
|
name: web-app-mediawiki
|
||||||
- name: flush handlers after pgadmin
|
- name: flush handlers after mediawiki
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup mailu
|
- name: setup attendize
|
||||||
when: ('mailu' | application_allowed(group_names, allowed_applications))
|
when: ('attendize' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-mailu
|
name: web-app-attendize
|
||||||
- name: flush handlers after mailu
|
- name: flush handlers after attendize
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup moodle
|
- name: setup snipe-it
|
||||||
when: ('moodle' | application_allowed(group_names, allowed_applications))
|
when: ('snipe-it' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-moodle
|
name: web-app-snipe-it
|
||||||
- name: flush handlers after moodle
|
- name: flush handlers after snipe-it
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup discourse
|
- name: setup fusiondirectory
|
||||||
when: ('discourse' | application_allowed(group_names, allowed_applications))
|
when: ('fusiondirectory' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-discourse
|
name: web-app-fusiondirectory
|
||||||
- name: flush handlers after discourse
|
- name: flush handlers after fusiondirectory
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup nextcloud
|
- name: setup akaunting
|
||||||
when: ('nextcloud' | application_allowed(group_names, allowed_applications))
|
when: ('akaunting' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-nextcloud
|
name: web-app-akaunting
|
||||||
- name: flush handlers after nextcloud
|
- name: flush handlers after akaunting
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup mybb
|
||||||
|
when: ('mybb' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-mybb
|
||||||
|
- name: flush handlers after mybb
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup gitlab
|
||||||
|
when: ('gitlab' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-gitlab
|
||||||
|
- name: flush handlers after gitlab
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup espocrm
|
- name: setup espocrm
|
||||||
when: ('espocrm' | application_allowed(group_names, allowed_applications))
|
when: ('espocrm' | application_allowed(group_names, allowed_applications))
|
||||||
@ -214,87 +280,21 @@
|
|||||||
name: web-app-joomla
|
name: web-app-joomla
|
||||||
- name: flush handlers after joomla
|
- name: flush handlers after joomla
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup matrix
|
|
||||||
when: ('matrix' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-matrix
|
|
||||||
- name: flush handlers after matrix
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup mobilizon
|
|
||||||
when: ('mobilizon' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-mobilizon
|
|
||||||
- name: flush handlers after mobilizon
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup snipe-it
|
|
||||||
when: ('snipe-it' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-snipe-it
|
|
||||||
- name: flush handlers after snipe-it
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup mybb
|
|
||||||
when: ('mybb' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-mybb
|
|
||||||
- name: flush handlers after mybb
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup attendize
|
|
||||||
when: ('attendize' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-attendize
|
|
||||||
- name: flush handlers after attendize
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup gitlab
|
|
||||||
when: ('gitlab' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-gitlab
|
|
||||||
- name: flush handlers after gitlab
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup mediawiki
|
|
||||||
when: ('mediawiki' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-mediawiki
|
|
||||||
- name: flush handlers after mediawiki
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup funkwhale
|
|
||||||
when: ('funkwhale' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-funkwhale
|
|
||||||
- name: flush handlers after funkwhale
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup gitea
|
|
||||||
when: ('gitea' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-gitea
|
|
||||||
- name: flush handlers after gitea
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup baserow
|
|
||||||
when: ('baserow' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-baserow
|
|
||||||
- name: flush handlers after baserow
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup akaunting
|
|
||||||
when: ('akaunting' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-akaunting
|
|
||||||
- name: flush handlers after akaunting
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup bluesky
|
|
||||||
when: ('bluesky' | application_allowed(group_names, allowed_applications))
|
|
||||||
include_role:
|
|
||||||
name: web-app-bluesky
|
|
||||||
- name: flush handlers after bluesky
|
|
||||||
meta: flush_handlers
|
|
||||||
- name: setup listmonk
|
- name: setup listmonk
|
||||||
when: ('listmonk' | application_allowed(group_names, allowed_applications))
|
when: ('listmonk' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-listmonk
|
name: web-app-listmonk
|
||||||
- name: flush handlers after listmonk
|
- name: flush handlers after listmonk
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
- name: setup fusiondirectory
|
- name: setup discourse
|
||||||
when: ('fusiondirectory' | application_allowed(group_names, allowed_applications))
|
when: ('discourse' | application_allowed(group_names, allowed_applications))
|
||||||
include_role:
|
include_role:
|
||||||
name: web-app-fusiondirectory
|
name: web-app-discourse
|
||||||
- name: flush handlers after fusiondirectory
|
- name: flush handlers after discourse
|
||||||
|
meta: flush_handlers
|
||||||
|
- name: setup nextcloud
|
||||||
|
when: ('nextcloud' | application_allowed(group_names, allowed_applications))
|
||||||
|
include_role:
|
||||||
|
name: web-app-nextcloud
|
||||||
|
- name: flush handlers after nextcloud
|
||||||
meta: flush_handlers
|
meta: flush_handlers
|
||||||
|
36
tests/integration/test_circular_dependencies.py
Normal file
36
tests/integration/test_circular_dependencies.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# import the functions from your CLI script
|
||||||
|
from cli.generate_playbook import build_dependency_graph, find_cycle
|
||||||
|
|
||||||
|
class TestCircularDependencies(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Integration test: ensure there are no circular 'run_after' dependencies
|
||||||
|
among the roles in the roles/ directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
# Determine the path to the repo root and the roles directory
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
repo_root = os.path.abspath(os.path.join(here, '..', '..'))
|
||||||
|
cls.roles_dir = os.path.join(repo_root, 'roles')
|
||||||
|
|
||||||
|
def test_no_circular_dependencies(self):
|
||||||
|
# Build the dependency graph using the real roles/
|
||||||
|
graph, in_degree, roles = build_dependency_graph(self.roles_dir)
|
||||||
|
|
||||||
|
# Attempt to find a cycle in the run_after mapping
|
||||||
|
cycle = find_cycle(roles)
|
||||||
|
|
||||||
|
if cycle:
|
||||||
|
# Format cycle as "A -> B -> C -> A"
|
||||||
|
cycle_str = " -> ".join(cycle)
|
||||||
|
self.fail(f"Circular dependency detected among roles: {cycle_str}")
|
||||||
|
|
||||||
|
# If no cycle, this assertion will pass
|
||||||
|
self.assertIsNone(cycle, "Expected no circular dependencies")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
49
tests/integration/test_run_after_references.py
Normal file
49
tests/integration/test_run_after_references.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
class TestRunAfterReferences(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Integration test: ensure that every name listed under
|
||||||
|
galaxy_info.run_after in each role's meta/main.yml
|
||||||
|
corresponds to an existing role directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
repo_root = os.path.abspath(os.path.join(here, '..', '..'))
|
||||||
|
cls.roles_dir = os.path.join(repo_root, 'roles')
|
||||||
|
# collect all role names (folder names) in roles/
|
||||||
|
cls.existing_roles = {
|
||||||
|
name for name in os.listdir(cls.roles_dir)
|
||||||
|
if os.path.isdir(os.path.join(cls.roles_dir, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_run_after_points_to_existing_roles(self):
|
||||||
|
errors = []
|
||||||
|
for role in sorted(self.existing_roles):
|
||||||
|
meta_path = os.path.join(self.roles_dir, role, 'meta', 'main.yml')
|
||||||
|
if not os.path.isfile(meta_path):
|
||||||
|
# skip roles without a meta/main.yml
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(meta_path, 'r') as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
run_after = data.get('galaxy_info', {}).get('run_after', [])
|
||||||
|
for dep in run_after:
|
||||||
|
if dep not in self.existing_roles:
|
||||||
|
errors.append(
|
||||||
|
f"Role '{role}' declares run_after: '{dep}', "
|
||||||
|
f"but '{dep}' is not a directory under roles/"
|
||||||
|
)
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
self.fail(
|
||||||
|
"Some run_after references are invalid:\n " +
|
||||||
|
"\n ".join(errors)
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
35
tests/integration/test_self_dependency.py
Normal file
35
tests/integration/test_self_dependency.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
class TestSelfDependency(unittest.TestCase):
|
||||||
|
"""
|
||||||
|
Integration test: ensure no role lists itself in its own 'run_after'
|
||||||
|
in meta/main.yml.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
repo_root = os.path.abspath(os.path.join(here, '..', '..'))
|
||||||
|
cls.roles_dir = os.path.join(repo_root, 'roles')
|
||||||
|
|
||||||
|
def test_no_self_in_run_after(self):
|
||||||
|
for entry in os.listdir(self.roles_dir):
|
||||||
|
role_path = os.path.join(self.roles_dir, entry)
|
||||||
|
meta_file = os.path.join(role_path, 'meta', 'main.yml')
|
||||||
|
if not os.path.isdir(role_path) or not os.path.isfile(meta_file):
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(meta_file, 'r') as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
run_after = data.get('galaxy_info', {}).get('run_after', [])
|
||||||
|
if entry in run_after:
|
||||||
|
self.fail(
|
||||||
|
f"Role '{entry}' has a self-dependency in its run_after list "
|
||||||
|
f"in {meta_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user