Solved collection dependency bug

This commit is contained in:
Kevin Veen-Birkenbach 2025-07-12 13:42:45 +02:00
parent b2e32aacf3
commit e174523fc6
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
52 changed files with 148 additions and 69 deletions

View File

@ -9,12 +9,12 @@ defaults_service_provider:
city: "Cybertown"
postal_code: "00001"
country: "Nexusland"
logo: "{{ applications['asset'].url ~ '/img/logo.png' }}"
logo: "{{ applications['web-svc-asset'].url ~ '/img/logo.png' }}"
platform:
titel: "CyMaIS"
subtitel: "One login. Infinite applications."
logo: "{{ applications['asset'].url ~ '/img/logo.png' }}"
favicon: "{{ applications['asset'].url ~ '/img/favicon.ico' }}"
logo: "{{ applications['web-svc-asset'].url ~ '/img/logo.png' }}"
favicon: "{{ applications['web-svc-asset'].url ~ '/img/favicon.ico' }}"
contact:
bluesky: >-
{{ ('@' ~ users.contact.username ~ '.' ~ domains.bluesky.api)
@ -30,4 +30,4 @@ defaults_service_provider:
legal:
editorial_responsible: "Johannes Gutenberg"
source_code: "https://github.com/kevinveenbirkenbach/cymais"
imprint: "{{web_protocol}}://{{ domains | get_domain('html') }}/imprint.html"
imprint: "{{web_protocol}}://{{ domains | get_domain('web-svc-html') }}/imprint.html"

View File

@ -1 +1 @@
application_id: bluray-player
application_id: desk-bluray-player

View File

@ -1,5 +1,5 @@
---
application_id: "chromium"
application_id: "desk-chromium"
chromium_package: "{{ 'chromium-browser' if ansible_os_family == 'Debian' else 'chromium' }}"

View File

@ -1,7 +1,7 @@
---
# Default variables for the pc-firefox role
application_id: "firefox"
application_id: "desk-firefox"
# Package name for Firefox on Arch Linux
firefox_package: firefox

View File

@ -1,2 +1,2 @@
auto_start_directory: /home/{{users.client.username}}/.config/autostart/
application_id: gnome-caffeine
application_id: desk-gnome-caffeine

View File

@ -1 +1 @@
application_id: gnome-extensions
application_id: desk-gnome-extensions

View File

@ -1 +1 @@
application_id: gnome-terminal
application_id: desk-gnome-terminal

View File

@ -1 +1 @@
application_id: gnome
application_id: desk-gnome

View File

@ -1 +1 @@
application_id: gnucash
application_id: desk-gnucash

View File

@ -1 +1 @@
application_id: jrnl
application_id: desk-jrnl

View File

@ -1 +1 @@
application_id: keepassxc
application_id: desk-keepassxc

View File

@ -2,11 +2,11 @@
community.general.pacman:
name:
- ttf-liberation
- "libreoffice-{{ applications.libreoffice.flavor }}"
- "libreoffice-{{ applications['desk-libreoffice'].flavor }}"
state: present
- name: install libreoffice language packages
community.general.pacman:
name: "libreoffice-{{ applications.libreoffice.flavor }}-{{ item }}"
name: "libreoffice-{{ applications['desk-libreoffice'].flavor }}-{{ item }}"
state: present
loop: "{{libreoffice_languages}}"

View File

@ -1 +1 @@
application_id: "libreoffice"
application_id: "desk-libreoffice"

View File

@ -1,3 +1,3 @@
user_home_directory: /home/{{users.client.username}}/
cloud_directory: '{{user_home_directory}}Clouds/{{cloud_fqdn}}/{{users.client.username}}/'
application_id: nextcloud-client
application_id: desk-nextcloud-client

View File

@ -1 +1 @@
application_id: obs
application_id: desk-obs

View File

@ -1 +1 @@
application_id: qbittorrent
application_id: desk-qbittorrent

View File

@ -2,4 +2,4 @@ retroarch_packages:
- retroarch
- retroarch-assets-xmb
- retroarch-assets-ozone
application_id: retroarch
application_id: desk-retroarch

View File

@ -1 +1 @@
application_id: spotify
application_id: desk-spotify

View File

@ -1 +1 @@
application_id: ssh
application_id: desk-ssh

View File

@ -1 +1 @@
application_id: torbrowser
application_id: desk-torbrowser

View File

@ -1 +1 @@
application_id: virtual-box
application_id: desk-virtual-box

View File

@ -1 +1 @@
application_id: zoom
application_id: desk-zoom

View File

@ -1 +1 @@
application_id: java
application_id: dev-java

View File

@ -1 +1 @@
application_id: make
application_id: dev-make

View File

@ -1 +1 @@
application_id: hunspell
application_id: gen-hunspell

View File

@ -1 +1 @@
application_id: wireguard-core
application_id: net-wireguard-core

View File

@ -1 +1 @@
application_id: wireguard-firewalled
application_id: net-wireguard-firewalled

View File

@ -1 +1 @@
application_id: wireguard-plain
application_id: net-wireguard-plain

View File

@ -1 +1 @@
application_id: browser
application_id: util-desk-browser

View File

@ -1 +1 @@
application_id: design
application_id: util-desk-design

View File

@ -1 +1 @@
application_id: dev-arduino
application_id: util-desk-dev-arduino

View File

@ -1 +1 @@
application_id: dev-core
application_id: util-desk-dev-core

View File

@ -1 +1 @@
application_id: dev-java
application_id: util-desk-dev-java

View File

@ -1 +1 @@
application_id: dev-php
application_id: util-desk-dev-php

View File

@ -1 +1 @@
application_id: dev-python
application_id: util-desk-dev-python

View File

@ -1 +1 @@
application_id: dev-shell
application_id: util-desk-dev-shell

View File

@ -1 +1 @@
application_id: game-compose
application_id: util-desk-game-compose

View File

@ -5,4 +5,4 @@ gamer_default_games:
- gnuchess
- sauerbraten
- mari0
application_id: game-os
application_id: util-desk-game-os

View File

@ -1 +1 @@
application_id: game-windows
application_id: util-desk-game-windows

View File

@ -1 +1 @@
application_id: office-tools
application_id: util-desk-office-tools

View File

@ -1 +1 @@
application_id: dev-admin-network
application_id: util-dev-admin-network

View File

@ -1 +1 @@
application_id: dev-admin
application_id: util-dev-admin

View File

@ -1 +1 @@
application_id: corporate-identity
application_id: util-srv-corporate-identity

View File

@ -1,4 +1,4 @@
application_id: "portfolio"
application_id: "web-app-portfolio"
docker_repository_address: "https://github.com/kevinveenbirkenbach/portfolio"
config_inventory_path: "{{ inventory_dir }}/files/{{ inventory_hostname }}/docker/portfolio/config.yaml.j2"
docker_repository: true

View File

@ -1,3 +1,3 @@
application_id: "asset" # Application identifier
application_id: "web-svc-asset" # Application identifier
source_directory: "{{ applications[application_id].source_directory }}/" # Source directory from which the files are coming from
target_directory: "{{ nginx.directories.data.files }}assets" # Directory to which the files will be copied

View File

@ -4,4 +4,6 @@ features:
portfolio_iframe: true
domains:
canonical:
- "file.{{ primary_domain }}"
alias:
- "files.{{ primary_domain }}"

View File

@ -1,2 +1,2 @@
application_id: "file"
application_id: "web-svc-file"
domain: "{{ domains | get_domain(application_id) }}"

View File

@ -1,4 +1,2 @@
application_id: "html"
domain: "{{domains | get_domain(application_id)}}"
features:
portfolio_iframe: true # Necessary for imprint loading
application_id: "web-svc-html"
domain: "{{domains | get_domain(application_id)}}"

View File

@ -1 +1 @@
application_id: "legal"
application_id: "web-svc-legal"

View File

@ -48,7 +48,7 @@
# The following mapping is necessary to define the exceptions for domains which are created, but which aren't used
redirect_domain_mappings: "{{
[] |
add_redirect_if_group('asset', domains | get_domain('asset'), domains | get_domain('file'), group_names) |
add_redirect_if_group('web-svc-asset', domains | get_domain('web-svc-asset'), domains | get_domain('web-svc-file'), group_names) |
merge_mapping(redirect_domain_mappings| default([]), 'source')
}}"

View File

@ -0,0 +1,79 @@
import os
import unittest
import yaml
import glob
class TestDependencyApplicationId(unittest.TestCase):
def test_application_id_matches_folder_for_dependent_roles(self):
"""
For any role A that depends on role B (listed under A/meta/main.yml dependencies),
if both A and B have vars/main.yml with application_id defined,
then application_id must equal the role's folder name for both.
"""
base_dir = os.path.dirname(__file__)
roles_dir = os.path.abspath(os.path.join(base_dir, '..', '..', 'roles'))
violations = []
# Helper to load application_id if present
def load_app_id(role_path):
vars_file = os.path.join(role_path, 'vars', 'main.yml')
if not os.path.isfile(vars_file):
return None
data = yaml.safe_load(open(vars_file, encoding='utf-8')) or {}
return data.get('application_id')
# Iterate all roles
for role_path in glob.glob(os.path.join(roles_dir, '*')):
if not os.path.isdir(role_path):
continue
role_name = os.path.basename(role_path)
meta_file = os.path.join(role_path, 'meta', 'main.yml')
if not os.path.isfile(meta_file):
continue
meta = yaml.safe_load(open(meta_file, encoding='utf-8')) or {}
deps = meta.get('dependencies', [])
if not isinstance(deps, list):
continue
# collect just the role names this role depends on
dep_names = []
for item in deps:
if isinstance(item, dict) and 'role' in item:
dep_names.append(item['role'])
elif isinstance(item, str):
dep_names.append(item)
# load application_id for this role once
app_id_role = load_app_id(role_path)
for dep_name in dep_names:
dep_path = os.path.join(roles_dir, dep_name)
if not os.path.isdir(dep_path):
continue
app_id_dep = load_app_id(dep_path)
# only check if both app_ids are defined
if app_id_role is None or app_id_dep is None:
continue
# check role A
if app_id_role != role_name:
violations.append(
f"{role_name}: application_id='{app_id_role}' ≠ folder name '{role_name}'"
)
# check role B
if app_id_dep != dep_name:
violations.append(
f"{dep_name}: application_id='{app_id_dep}' ≠ folder name '{dep_name}'"
)
if violations:
self.fail(
"application_id mismatches between role folder names and vars/main.yml:\n"
+ "\n".join(violations)
)
if __name__ == '__main__':
unittest.main()

View File

@ -18,25 +18,25 @@ class TestApplicationsIfGroupAndDeps(unittest.TestCase):
def setUp(self):
self.filter = FilterModule().applications_if_group_and_deps
self.sample_apps = {
'html': {},
'legal': {},
'file': {},
'asset': {},
'portfolio': {},
'corporate-identity': {},
'web-svc-html': {},
'web-svc-legal': {},
'web-svc-file': {},
'web-svc-asset': {},
'web-app-portfolio': {},
'util-srv-corporate-identity': {},
}
def test_direct_group(self):
result = self.filter(self.sample_apps, ['html'])
self.assertIn('html', result)
self.assertNotIn('legal', result)
result = self.filter(self.sample_apps, ['web-svc-html'])
self.assertIn('web-svc-html', result)
self.assertNotIn('web-svc-legal', result)
def test_recursive_deps(self):
# html -> depends on none, but corporate-identity pulls in web-svc-legal -> web-svc-html -> legal
# html -> depends on none, but util-srv-corporate-identity pulls in web-svc-legal -> web-svc-html -> legal
result = self.filter(self.sample_apps, ['util-srv-corporate-identity'])
self.assertIn('corporate-identity', result)
self.assertIn('legal', result) # via web-svc-legal
self.assertIn('html', result) # via web-svc-legal -> html
self.assertIn('util-srv-corporate-identity', result)
self.assertIn('web-svc-legal', result) # via web-svc-legal
self.assertIn('web-svc-html', result) # via web-svc-legal -> html
def test_real_vars_files(self):
# load real vars to get application_id