mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-06-25 03:38:59 +02:00
In between commit domain restruturing
This commit is contained in:
parent
cc3f5d75ea
commit
37dcc5f74e
2
Makefile
2
Makefile
@ -1,5 +1,5 @@
|
|||||||
ROLES_DIR := ./roles
|
ROLES_DIR := ./roles
|
||||||
APPLICATIONS_OUT := ./group_vars/all/11_applications.yml
|
APPLICATIONS_OUT := ./group_vars/all/03_applications.yml
|
||||||
APPLICATIONS_SCRIPT := ./cli/generate-applications-defaults.py
|
APPLICATIONS_SCRIPT := ./cli/generate-applications-defaults.py
|
||||||
INCLUDES_OUT := ./tasks/include-docker-roles.yml
|
INCLUDES_OUT := ./tasks/include-docker-roles.yml
|
||||||
INCLUDES_SCRIPT := ./cli/generate-role-includes.py
|
INCLUDES_SCRIPT := ./cli/generate-role-includes.py
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
[defaults]
|
[defaults]
|
||||||
lookup_plugins = ./lookup_plugins
|
lookup_plugins = ./lookup_plugins
|
||||||
|
filter_plugins = ./filter_plugins
|
@ -15,7 +15,7 @@ def load_yaml_file(path):
|
|||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="Generate defaults_applications YAML from docker roles.")
|
parser = argparse.ArgumentParser(description="Generate defaults_applications YAML from docker roles.")
|
||||||
parser.add_argument("--roles-dir", default="roles", help="Path to the roles directory (default: roles)")
|
parser.add_argument("--roles-dir", default="roles", help="Path to the roles directory (default: roles)")
|
||||||
parser.add_argument("--output-file", default="group_vars/all/11_applications.yml", help="Path to output YAML file")
|
parser.add_argument("--output-file", default="group_vars/all/03_applications.yml", help="Path to output YAML file")
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
cwd = Path.cwd()
|
cwd = Path.cwd()
|
||||||
|
111
docs/guides/developer/Ansible_Directory_Guide.md
Normal file
111
docs/guides/developer/Ansible_Directory_Guide.md
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
## 📖 CyMaIS.Cloud Ansible & Python Directory Guide
|
||||||
|
|
||||||
|
This document provides a **decision matrix** for when to use each default Ansible plugin and module directory in the context of **CyMaIS.Cloud development** with Ansible and Python. It links to official docs, explains use-cases, and points back to our conversation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🔗 Links & References
|
||||||
|
|
||||||
|
* Official Ansible Plugin Guide: [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_plugins.html](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html)
|
||||||
|
* Official Ansible Module Guide: [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_modules.html](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules.html)
|
||||||
|
* This conversation: [Link to this conversation](https://chat.openai.com/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ Repo Layout & Default Directories
|
||||||
|
|
||||||
|
```plaintext
|
||||||
|
ansible-repo/
|
||||||
|
├── library/ # 📦 Custom Ansible modules
|
||||||
|
├── filter_plugins/ # 🔍 Custom Jinja2 filters
|
||||||
|
├── lookup_plugins/ # 👉 Custom lookup plugins
|
||||||
|
├── module_utils/ # 🛠️ Shared Python helpers for modules
|
||||||
|
├── action_plugins/ # ⚙️ Task-level orchestration logic
|
||||||
|
├── callback_plugins/ # 📣 Event callbacks (logging, notifications)
|
||||||
|
├── inventory_plugins/ # 🌐 Dynamic inventory sources
|
||||||
|
├── strategy_plugins/ # 🧠 Task execution strategies
|
||||||
|
└── ... # Other plugin dirs (connection, cache, etc.)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🎯 Decision Matrix: Which Folder for What?
|
||||||
|
|
||||||
|
| Folder | Type | Use-Case | Example (CyMaIS.Cloud) | Emoji |
|
||||||
|
| -------------------- | -------------------- | ---------------------------------------- | ----------------------------------------------------- | ----- |
|
||||||
|
| `library/` | **Module** | Write idempotent actions | `cloud_network.py`: manage VPCs, subnets | 📦 |
|
||||||
|
| `filter_plugins/` | **Filter plugin** | Jinja2 data transforms in templates/vars | `to_camel_case.py`: convert keys for API calls | 🔍 |
|
||||||
|
| `lookup_plugins/` | **Lookup plugin** | Fetch external/secure data at runtime | `vault_lookup.py`: pull secrets from CyMaIS Vault | 👉 |
|
||||||
|
| `module_utils/` | **Utility library** | Shared Python code for modules | `cymais_client.py`: common API client base class | 🛠️ |
|
||||||
|
| `action_plugins/` | **Action plugin** | Complex task orchestration wrappers | `deploy_stack.py`: sequence Terraform + Ansible steps | ⚙️ |
|
||||||
|
| `callback_plugins/` | **Callback plugin** | Customize log/report behavior | `notify_slack.py`: send playbook status to Slack | 📣 |
|
||||||
|
| `inventory_plugins/` | **Inventory plugin** | Dynamic host/group sources | `azure_inventory.py`: list hosts from Azure tags | 🌐 |
|
||||||
|
| `strategy_plugins/` | **Strategy plugin** | Control task execution order/parallelism | `rolling_batch.py`: phased rollout of VMs | 🧠 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📝 Detailed Guidance
|
||||||
|
|
||||||
|
1. **library/** 📦
|
||||||
|
|
||||||
|
* **When?** Implement **one-off, idempotent actions** (create/delete cloud resources).
|
||||||
|
* **Why?** Modules under `library/` are first in search path for `ansible` modules.
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_modules.html](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules.html)
|
||||||
|
|
||||||
|
2. **filter\_plugins/** 🔍
|
||||||
|
|
||||||
|
* **When?** You need **data manipulation** (lists, strings, dicts) inside Jinja2.
|
||||||
|
* **Why?** Extends `|` filters in templates and variable declarations.
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_plugins.html#filter-plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#filter-plugins)
|
||||||
|
|
||||||
|
3. **lookup\_plugins/** 👉
|
||||||
|
|
||||||
|
* **When?** You must **retrieve secret/external data** during playbook compile/runtime.
|
||||||
|
* **Why?** Lookup plugins run before tasks, enabling dynamic variable resolution.
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_plugins.html#lookup-plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#lookup-plugins)
|
||||||
|
|
||||||
|
4. **module\_utils/** 🛠️
|
||||||
|
|
||||||
|
* **When?** Multiple modules share **common Python code** (HTTP clients, validation).
|
||||||
|
* **Why?** Avoid code duplication; modules import these utilities.
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_modules.html#module-utils](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules.html#module-utils)
|
||||||
|
|
||||||
|
5. **action\_plugins/** ⚙️
|
||||||
|
|
||||||
|
* **When?** You need to **wrap or extend** module behavior at task invocation time.
|
||||||
|
* **Why?** Provides hooks before/after module execution.
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_plugins.html#action-plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#action-plugins)
|
||||||
|
|
||||||
|
6. **callback\_plugins/** 📣
|
||||||
|
|
||||||
|
* **When?** You want **custom event handlers** (logging, progress, notifications).
|
||||||
|
* **Why?** Receive play/task events for custom output.
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_plugins.html#callback-plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#callback-plugins)
|
||||||
|
|
||||||
|
7. **inventory\_plugins/** 🌐
|
||||||
|
|
||||||
|
* **When?** Hosts/groups come from **dynamic sources** (cloud APIs, databases).
|
||||||
|
* **Why?** Replace static `inventory.ini` with code-driven inventories.
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_plugins.html#inventory-plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#inventory-plugins)
|
||||||
|
|
||||||
|
8. **strategy\_plugins/** 🧠
|
||||||
|
|
||||||
|
* **When?** You need to **customize execution strategy** (parallelism, ordering).
|
||||||
|
* **Why?** Override default `linear` strategy (e.g., `free`, custom batches).
|
||||||
|
* **Docs:** [https://docs.ansible.com/ansible/latest/dev\_guide/developing\_plugins.html#strategy-plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#strategy-plugins)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🚀 CyMaIS.Cloud Best Practices
|
||||||
|
|
||||||
|
* **Organize modules** by service under `library/cloud/` (e.g., `vm`, `network`, `storage`).
|
||||||
|
* **Shared client code** in `module_utils/cymais/` for authentication, request handling.
|
||||||
|
* **Secrets lookup** via `lookup_plugins/vault_lookup.py` pointing to CyMaIS Vault.
|
||||||
|
* **Filters** to normalize data formats from cloud APIs (e.g., `snake_to_camel`).
|
||||||
|
* **Callbacks** to stream playbook results into CyMaIS Monitoring.
|
||||||
|
|
||||||
|
Use this matrix as your **single source of truth** when extending Ansible for CyMaIS.Cloud! 👍
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
This matrix was created with the help of ChatGPT 🤖—see our conversation [here](https://chatgpt.com/canvas/shared/682b1a62d6dc819184ecdc696c51290a).
|
@ -1,6 +1,6 @@
|
|||||||
def is_feature_enabled(applications, feature: str, application_id: str) -> bool:
|
def is_feature_enabled(applications: dict, feature: str, application_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if a generic feature is enabled for the given application.
|
Return True if applications[application_id].features[feature] is truthy.
|
||||||
"""
|
"""
|
||||||
app = applications.get(application_id, {})
|
app = applications.get(application_id, {})
|
||||||
return bool(app.get('features', {}).get(feature, False))
|
return bool(app.get('features', {}).get(feature, False))
|
||||||
|
@ -12,9 +12,10 @@ class FilterModule(object):
|
|||||||
'build_csp_header': self.build_csp_header,
|
'build_csp_header': self.build_csp_header,
|
||||||
}
|
}
|
||||||
|
|
||||||
def is_feature_enabled(self, applications, feature: str, application_id: str) -> bool:
|
@staticmethod
|
||||||
|
def is_feature_enabled(applications: dict, feature: str, application_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if a generic feature is enabled for the given application.
|
Return True if applications[application_id].features[feature] is truthy.
|
||||||
"""
|
"""
|
||||||
app = applications.get(application_id, {})
|
app = applications.get(application_id, {})
|
||||||
return bool(app.get('features', {}).get(feature, False))
|
return bool(app.get('features', {}).get(feature, False))
|
||||||
|
@ -3,17 +3,102 @@ from ansible.errors import AnsibleFilterError
|
|||||||
|
|
||||||
class FilterModule(object):
|
class FilterModule(object):
|
||||||
"""
|
"""
|
||||||
Custom Ansible filter plugin:
|
Ansible Filter Plugin for Domain Processing
|
||||||
- generate_all_domains: Flatten, dedupe, sort domains with optional www prefixes
|
|
||||||
- generate_base_sld_domains: Extract unique sld.tld domains from values and redirect sources
|
This plugin provides filters to manage and transform domain configurations for applications:
|
||||||
|
|
||||||
|
- generate_all_domains(domains_dict, include_www=True):
|
||||||
|
Flattens nested domain values (string, list, or dict), optionally adds 'www.' prefixes,
|
||||||
|
removes duplicates, and returns a sorted list of unique domains.
|
||||||
|
|
||||||
|
- generate_base_sld_domains(domains_dict, redirect_mappings):
|
||||||
|
Flattens domains and redirect mappings, extracts second-level + top-level domains (SLDs),
|
||||||
|
deduplicates, and returns a sorted list of base domains.
|
||||||
|
|
||||||
|
- canonical_domains_map(apps, primary_domain):
|
||||||
|
Builds a mapping of application IDs to their canonical domains using
|
||||||
|
DomainUtils.canonical_list, enforcing uniqueness and detecting conflicts.
|
||||||
|
|
||||||
|
- alias_domains_map(apps, primary_domain):
|
||||||
|
Generates alias domains for each application via DomainUtils.alias_list,
|
||||||
|
based on their canonical domains and provided configurations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def filters(self):
|
def filters(self):
|
||||||
return {
|
return {
|
||||||
'generate_all_domains': self.generate_all_domains,
|
'generate_all_domains': self.generate_all_domains,
|
||||||
'generate_base_sld_domains': self.generate_base_sld_domains,
|
'generate_base_sld_domains': self.generate_base_sld_domains,
|
||||||
|
'canonical_domains_map': self.canonical_domains_map,
|
||||||
|
'alias_domains_map': self.alias_domains_map,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_entry(domains_cfg, key, app_id):
|
||||||
|
"""
|
||||||
|
Extract list of strings from domains_cfg[key], which may be dict or list.
|
||||||
|
Returns None if key not in domains_cfg.
|
||||||
|
Raises AnsibleFilterError on invalid type or empty/invalid values.
|
||||||
|
"""
|
||||||
|
if key not in domains_cfg:
|
||||||
|
return None
|
||||||
|
entry = domains_cfg[key]
|
||||||
|
if isinstance(entry, dict):
|
||||||
|
values = list(entry.values())
|
||||||
|
elif isinstance(entry, list):
|
||||||
|
values = entry
|
||||||
|
else:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"Unexpected type for 'domains.{key}' in application '{app_id}': {type(entry).__name__}"
|
||||||
|
)
|
||||||
|
for d in values:
|
||||||
|
if not isinstance(d, str) or not d.strip():
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"Invalid domain entry in '{key}' for application '{app_id}': {d!r}"
|
||||||
|
)
|
||||||
|
return values
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_domain(app_id, primary_domain):
|
||||||
|
"""
|
||||||
|
Returns the default domain string for an application.
|
||||||
|
"""
|
||||||
|
return f"{app_id}.{primary_domain}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def canonical_list(cls, domains_cfg, app_id, primary_domain):
|
||||||
|
"""
|
||||||
|
Returns the list of canonical domains: parsed entry or default.
|
||||||
|
"""
|
||||||
|
domains = cls.parse_entry(domains_cfg, 'canonical', app_id)
|
||||||
|
if domains is None:
|
||||||
|
return [cls.default_domain(app_id, primary_domain)]
|
||||||
|
return domains
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def alias_list(cls, domains_cfg, app_id, primary_domain, canonical_domains=None):
|
||||||
|
"""
|
||||||
|
Returns the list of alias domains based on:
|
||||||
|
- explicit aliases entry
|
||||||
|
- presence of canonical entry and default not in canonical
|
||||||
|
Always ensures default domain in aliases when appropriate.
|
||||||
|
"""
|
||||||
|
default = cls.default_domain(app_id, primary_domain)
|
||||||
|
aliases = cls.parse_entry(domains_cfg, 'aliases', app_id) or []
|
||||||
|
has_aliases = 'aliases' in domains_cfg
|
||||||
|
has_canonical = 'canonical' in domains_cfg
|
||||||
|
|
||||||
|
if has_aliases:
|
||||||
|
if default not in aliases:
|
||||||
|
aliases.append(default)
|
||||||
|
elif has_canonical:
|
||||||
|
# use provided canonical_domains if given otherwise parse
|
||||||
|
canon = canonical_domains if canonical_domains is not None else cls.parse_entry(domains_cfg, 'canonical', app_id)
|
||||||
|
if default not in (canon or []):
|
||||||
|
aliases.append(default)
|
||||||
|
# else: neither defined -> empty list
|
||||||
|
return aliases
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_all_domains(domains_dict, include_www=True):
|
def generate_all_domains(domains_dict, include_www=True):
|
||||||
"""
|
"""
|
||||||
@ -67,3 +152,33 @@ class FilterModule(object):
|
|||||||
elif isinstance(val, dict):
|
elif isinstance(val, dict):
|
||||||
flat.extend(val.values())
|
flat.extend(val.values())
|
||||||
return flat
|
return flat
|
||||||
|
|
||||||
|
def canonical_domains_map(self, apps, primary_domain):
|
||||||
|
result = {}
|
||||||
|
seen = {}
|
||||||
|
for app_id, app_cfg in apps.items():
|
||||||
|
domains_cfg = app_cfg.get('domains', {}) or {}
|
||||||
|
domains = self.canonical_list(domains_cfg, app_id, primary_domain)
|
||||||
|
for d in domains:
|
||||||
|
if d in seen:
|
||||||
|
raise AnsibleFilterError(
|
||||||
|
f"Domain '{d}' is configured for both '{seen[d]}' and '{app_id}'"
|
||||||
|
)
|
||||||
|
seen[d] = app_id
|
||||||
|
result[app_id] = domains
|
||||||
|
return result
|
||||||
|
|
||||||
|
def alias_domains_map(self, apps, primary_domain):
|
||||||
|
result = {}
|
||||||
|
# wir können die canonical_map vorab holen…
|
||||||
|
canonical_map = self.canonical_domains_map(apps, primary_domain)
|
||||||
|
for app_id, app_cfg in apps.items():
|
||||||
|
domains_cfg = app_cfg.get('domains', {}) or {}
|
||||||
|
aliases = self.alias_list(
|
||||||
|
domains_cfg,
|
||||||
|
app_id,
|
||||||
|
primary_domain,
|
||||||
|
canonical_domains=canonical_map.get(app_id),
|
||||||
|
)
|
||||||
|
result[app_id] = aliases
|
||||||
|
return result
|
||||||
|
@ -1,16 +1,9 @@
|
|||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
import sys
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
from ansible.errors import AnsibleFilterError
|
|
||||||
|
|
||||||
class FilterModule(object):
|
class FilterModule(object):
|
||||||
"""
|
|
||||||
Custom filters for conditional domain assignments, handling both direct group matches
|
|
||||||
and recursive role dependency resolution.
|
|
||||||
|
|
||||||
Determines if a given application_id (domain_key) should have its domain added by checking:
|
|
||||||
- If domain_key is explicitly listed in group_names, or
|
|
||||||
- If domain_key matches any application_id of roles reachable from active groups via dependencies.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def filters(self):
|
def filters(self):
|
||||||
return {
|
return {
|
||||||
@ -39,9 +32,27 @@ class FilterModule(object):
|
|||||||
result[domain_key] = domain_value
|
result[domain_key] = domain_value
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Setup roles directory path
|
# Determine plugin directory based on filter plugin module if available
|
||||||
|
plugin_dir = None
|
||||||
|
for module in sys.modules.values():
|
||||||
|
fm = getattr(module, 'FilterModule', None)
|
||||||
|
if fm is not None:
|
||||||
|
try:
|
||||||
|
# Access staticmethod, compare underlying function
|
||||||
|
if getattr(fm, 'add_domain_if_group') is DomainFilterUtil.add_domain_if_group:
|
||||||
|
plugin_dir = os.path.dirname(module.__file__)
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if plugin_dir:
|
||||||
|
# The plugin_dir is the filter_plugins directory; project_root is one level up
|
||||||
|
project_root = os.path.abspath(os.path.join(plugin_dir, '..'))
|
||||||
|
else:
|
||||||
|
# Fallback: locate project root relative to this utility file
|
||||||
plugin_dir = os.path.dirname(__file__)
|
plugin_dir = os.path.dirname(__file__)
|
||||||
project_root = os.path.abspath(os.path.join(plugin_dir, '..'))
|
project_root = os.path.abspath(os.path.join(plugin_dir, '..'))
|
||||||
|
|
||||||
roles_dir = os.path.join(project_root, 'roles')
|
roles_dir = os.path.join(project_root, 'roles')
|
||||||
|
|
||||||
# Collect all roles reachable from the active groups
|
# Collect all roles reachable from the active groups
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
defaults_domains: >-
|
|
||||||
{{ {}
|
|
||||||
| add_domain_if_group('akaunting', 'accounting.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('attendize', 'tickets.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('baserow', 'baserow.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('bigbluebutton', 'meet.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('bluesky', {'web': 'bskyweb.' ~ primary_domain,'api':'bluesky.' ~ primary_domain}, group_names)
|
|
||||||
| add_domain_if_group('discourse', 'forum.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('elk', 'elk.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('espocrm', 'espocrm.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('file_server', 'files.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('friendica', 'friendica.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('funkwhale', 'music.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('gitea', 'git.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('gitlab', 'gitlab.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('html_server', 'html.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('keycloak', 'auth.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('lam', 'lam.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('ldap', 'ldap.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('listmonk', 'newsletter.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('mailu', 'mail.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('mastodon', ['microblog.' ~ primary_domain], group_names)
|
|
||||||
| add_domain_if_group('matomo', 'matomo.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('matrix', {'synapse': 'matrix.' ~ primary_domain, 'element':'element.' ~ primary_domain}, group_names)
|
|
||||||
| add_domain_if_group('moodle', 'academy.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('mediawiki', 'wiki.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('nextcloud', 'cloud.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('openproject', 'project.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('peertube', ['video.' ~ primary_domain], group_names)
|
|
||||||
| add_domain_if_group('pgadmin', 'pgadmin.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('phpmyadmin', 'phpmyadmin.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('phpmyldapadmin', 'phpmyldap.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('pixelfed', 'picture.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('portfolio', primary_domain, group_names)
|
|
||||||
| add_domain_if_group('presentation', 'slides.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('roulette-wheel', 'roulette.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('snipe_it', 'inventory.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('sphinx', 'docs.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('syncope', 'syncope.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('taiga', 'kanban.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('yourls', 's.' ~ primary_domain, group_names)
|
|
||||||
| add_domain_if_group('wordpress', ['blog.' ~ primary_domain], group_names)
|
|
||||||
}}
|
|
||||||
|
|
||||||
defaults_redirect_domain_mappings: >-
|
|
||||||
{{ []
|
|
||||||
| add_redirect_if_group('akaunting', "akaunting." ~ primary_domain, domains.akaunting, group_names)
|
|
||||||
| add_redirect_if_group('bigbluebutton', "bbb." ~ primary_domain, domains.bigbluebutton, group_names)
|
|
||||||
| add_redirect_if_group('discourse', "discourse." ~ primary_domain, domains.discourse, group_names)
|
|
||||||
| add_redirect_if_group('espocrm', "crm." ~ primary_domain, domains.espocrm, group_names)
|
|
||||||
| add_redirect_if_group('funkwhale', "funkwhale." ~ primary_domain, domains.funkwhale, group_names)
|
|
||||||
| add_redirect_if_group('gitea', "gitea." ~ primary_domain, domains.gitea, group_names)
|
|
||||||
| add_redirect_if_group('keycloak', "keycloak." ~ primary_domain, domains.keycloak, group_names)
|
|
||||||
| add_redirect_if_group('lam', domains.ldap, domains.lam, group_names)
|
|
||||||
| add_redirect_if_group('phpmyldapadmin', domains.ldap, domains.phpmyldapadmin,group_names)
|
|
||||||
| add_redirect_if_group('listmonk', "listmonk." ~ primary_domain, domains.listmonk, group_names)
|
|
||||||
| add_redirect_if_group('mailu', "mailu." ~ primary_domain, domains.mailu, group_names)
|
|
||||||
| add_redirect_if_group('mastodon', "mastodon." ~ primary_domain, domains.mastodon[0], group_names)
|
|
||||||
| add_redirect_if_group('moodle', "moodle." ~ primary_domain, domains.moodle, group_names)
|
|
||||||
| add_redirect_if_group('nextcloud', "nextcloud." ~ primary_domain, domains.nextcloud, group_names)
|
|
||||||
| add_redirect_if_group('openproject', "openproject." ~ primary_domain, domains.openproject, group_names)
|
|
||||||
| add_redirect_if_group('peertube', "peertube." ~ primary_domain, domains.peertube[0], group_names)
|
|
||||||
| add_redirect_if_group('pixelfed', "pictures." ~ primary_domain, domains.pixelfed, group_names)
|
|
||||||
| add_redirect_if_group('pixelfed', "pixelfed." ~ primary_domain, domains.pixelfed, group_names)
|
|
||||||
| add_redirect_if_group('yourls', "short." ~ primary_domain, domains.yourls, group_names)
|
|
||||||
| add_redirect_if_group('snipe-it', "snipe-it." ~ primary_domain, domains.snipe_it, group_names)
|
|
||||||
| add_redirect_if_group('taiga', "taiga." ~ primary_domain, domains.taiga, group_names)
|
|
||||||
| add_redirect_if_group('peertube', "videos." ~ primary_domain, domains.peertube[0], group_names)
|
|
||||||
| add_redirect_if_group('wordpress', "wordpress." ~ primary_domain, domains.wordpress[0], group_names)
|
|
||||||
}}
|
|
||||||
|
|
||||||
# Domains which are deprecated and should be cleaned up
|
|
||||||
deprecated_domains: []
|
|
10
group_vars/all/11_domains.yml
Normal file
10
group_vars/all/11_domains.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
defaults_domains: "{{ defaults_applications | canonical_domains_map(primary_domain) }}"
|
||||||
|
|
||||||
|
defaults_redirect_domain_mappings: >-
|
||||||
|
{{ []
|
||||||
|
| add_redirect_if_group('lam', domains.ldap, domains.lam, group_names)
|
||||||
|
| add_redirect_if_group('phpmyldapadmin', domains.ldap, domains.phpmyldapadmin,group_names)
|
||||||
|
}}
|
||||||
|
|
||||||
|
# Domains which are deprecated and should be cleaned up
|
||||||
|
deprecated_domains: []
|
@ -1,3 +1,2 @@
|
|||||||
colorscheme-generator @ https://github.com/kevinveenbirkenbach/colorscheme-generator/archive/refs/tags/v0.3.0.zip
|
colorscheme-generator @ https://github.com/kevinveenbirkenbach/colorscheme-generator/archive/refs/tags/v0.3.0.zip
|
||||||
simpleaudio
|
|
||||||
numpy
|
numpy
|
@ -5,3 +5,5 @@ pacman:
|
|||||||
- ansible
|
- ansible
|
||||||
- python-passlib
|
- python-passlib
|
||||||
- python-pytest
|
- python-pytest
|
||||||
|
yay:
|
||||||
|
- python-simpleaudio
|
@ -10,3 +10,7 @@ features:
|
|||||||
credentials:
|
credentials:
|
||||||
# database_password: Needs to be defined in inventory file
|
# database_password: Needs to be defined in inventory file
|
||||||
# setup_admin_password: Needs to be defined in inventory file
|
# setup_admin_password: Needs to be defined in inventory file
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "accounting.{{ primary_domain }}"
|
||||||
|
|
||||||
|
@ -7,3 +7,7 @@ features:
|
|||||||
css: true
|
css: true
|
||||||
portfolio_iframe: false
|
portfolio_iframe: false
|
||||||
central_database: true
|
central_database: true
|
||||||
|
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "tickets.{{ primary_domain }}"
|
@ -1,17 +1,10 @@
|
|||||||
enable_greenlight: "true"
|
enable_greenlight: "true"
|
||||||
setup: false # Set to true in inventory file for initial setup
|
setup: false
|
||||||
credentials:
|
credentials:
|
||||||
# shared_secret: # Needs to be defined in inventory file
|
|
||||||
# etherpad_api_key: # Needs to be defined in inventory file
|
|
||||||
# rails_secret: # Needs to be defined in inventory file
|
|
||||||
# postgresql_secret: # Needs to be defined in inventory file
|
|
||||||
# fsesl_password: # Needs to be defined in inventory file
|
|
||||||
# turn_secret: # Needs to be defined in inventory file
|
|
||||||
database:
|
database:
|
||||||
name: "multiple_databases"
|
name: "multiple_databases"
|
||||||
username: "postgres2"
|
username: "postgres2"
|
||||||
urls:
|
api_suffix: "/bigbluebutton/"
|
||||||
api: "{{ web_protocol }}://{{domains | get_domain('bigbluebutton')}}/bigbluebutton/" # API Address used by Nextcloud Integration
|
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
@ -19,3 +12,6 @@ features:
|
|||||||
ldap: false
|
ldap: false
|
||||||
oidc: true
|
oidc: true
|
||||||
central_database: false
|
central_database: false
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "meet.{{ primary_domain }}"
|
@ -4,11 +4,12 @@ users:
|
|||||||
pds:
|
pds:
|
||||||
version: "latest"
|
version: "latest"
|
||||||
credentials:
|
credentials:
|
||||||
#jwt_secret: # Needs to be defined in inventory file - Use: openssl rand -base64 64 | tr -d '\n'
|
|
||||||
#plc_rotation_key_k256_private_key_hex: # Needs to be defined in inventory file - Use: openssl rand -hex 32
|
|
||||||
#admin_password: # Needs to be defined in inventory file - Use: openssl rand -base64 16
|
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
portfolio_iframe: true
|
portfolio_iframe: true
|
||||||
central_database: true
|
central_database: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
web: "bskyweb.{{ primary_domain }}"
|
||||||
|
api: "bluesky.{{ primary_domain }}"
|
@ -17,3 +17,6 @@ csp:
|
|||||||
whitelist:
|
whitelist:
|
||||||
font-src:
|
font-src:
|
||||||
- "http://*.{{primary_domain}}"
|
- "http://*.{{primary_domain}}"
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "forum.{{ primary_domain }}"
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
# Jinja2 configuration template
|
|
||||||
# Define your variables here
|
|
||||||
|
|
1
roles/docker-elk/vars/configuration.yml
Normal file
1
roles/docker-elk/vars/configuration.yml
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -17,3 +17,6 @@ csp:
|
|||||||
script-src:
|
script-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
unsafe-eval: true
|
unsafe-eval: true
|
||||||
|
domains:
|
||||||
|
aliases:
|
||||||
|
- "crm.{{ primary_domain }}"
|
@ -5,3 +5,6 @@ features:
|
|||||||
portfolio_iframe: true
|
portfolio_iframe: true
|
||||||
oidc: true
|
oidc: true
|
||||||
central_database: true
|
central_database: true
|
||||||
|
domains:
|
||||||
|
aliases:
|
||||||
|
- "social.{{ primary_domain }}"
|
@ -6,5 +6,9 @@ features:
|
|||||||
ldap: true
|
ldap: true
|
||||||
central_database: true
|
central_database: true
|
||||||
credentials:
|
credentials:
|
||||||
# database_password: # Needs to be defined in inventory file
|
domains:
|
||||||
# django_secret: # Needs to be defined in inventory file
|
canonical:
|
||||||
|
- "audio.{{ primary_domain }}"
|
||||||
|
aliases:
|
||||||
|
- "music.{{ primary_domain }}"
|
||||||
|
- "sound.{{ primary_domain }}"
|
@ -23,3 +23,6 @@ csp:
|
|||||||
- "blob:"
|
- "blob:"
|
||||||
manifest-src:
|
manifest-src:
|
||||||
- "data:"
|
- "data:"
|
||||||
|
domains:
|
||||||
|
aliases:
|
||||||
|
- "git.{{ primary_domain }}"
|
@ -1,3 +0,0 @@
|
|||||||
# Jinja2 configuration template
|
|
||||||
# Define your variables here
|
|
||||||
|
|
@ -3,3 +3,6 @@ features:
|
|||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
portfolio_iframe: true
|
portfolio_iframe: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "cms.{{ primary_domain }}"
|
@ -17,3 +17,6 @@ csp:
|
|||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "auth.{{ primary_domain }}"
|
@ -1,10 +1,8 @@
|
|||||||
version: "latest"
|
version: "latest"
|
||||||
oauth2_proxy:
|
oauth2_proxy:
|
||||||
application: application # Needs to be the same as webinterface
|
application: application
|
||||||
port: 80 # application port
|
port: 80
|
||||||
credentials:
|
credentials:
|
||||||
# oauth2_proxy_cookie_secret: None # Set via openssl rand -hex 16
|
|
||||||
# administrator_password: "None" # CHANGE for security reasons
|
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
@ -19,3 +17,7 @@ csp:
|
|||||||
script-src:
|
script-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
unsafe-eval: true
|
unsafe-eval: true
|
||||||
|
domains:
|
||||||
|
aliases:
|
||||||
|
- "ldap.{{primary_domain}}"
|
||||||
|
|
||||||
|
@ -9,3 +9,6 @@ features:
|
|||||||
portfolio_iframe: true
|
portfolio_iframe: true
|
||||||
central_database: true
|
central_database: true
|
||||||
oidc: true
|
oidc: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "newsletter.{{ primary_domain }}"
|
@ -7,14 +7,12 @@ oidc:
|
|||||||
enable_user_creation: true # Users will be created if not existing
|
enable_user_creation: true # Users will be created if not existing
|
||||||
domain: "{{primary_domain}}" # The main domain from which mails will be send \ email suffix behind @
|
domain: "{{primary_domain}}" # The main domain from which mails will be send \ email suffix behind @
|
||||||
credentials:
|
credentials:
|
||||||
# secret_key: # Set to a randomly generated 16 bytes string
|
|
||||||
# database_password: # Needs to be set in inventory file
|
|
||||||
# api_token: # Configures the authentication token. The minimum length is 3 characters. This is a mandatory setting for using the RESTful API.
|
|
||||||
# initial_administrator_password: # Initial administrator password for setup
|
|
||||||
# dkim_public_key: # Must be set in inventory file
|
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
portfolio_iframe: false # Deactivated mailu iframe loading until keycloak supports it
|
portfolio_iframe: false # Deactivated mailu iframe loading until keycloak supports it
|
||||||
oidc: true
|
oidc: true
|
||||||
central_database: false # Deactivate central database for mailu, I don't know why the database deactivation is necessary
|
central_database: false # Deactivate central database for mailu, I don't know why the database deactivation is necessary
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "mail.{{ primary_domain }}"
|
@ -2,18 +2,12 @@ version: "latest"
|
|||||||
single_user_mode: false # Set true for initial setup
|
single_user_mode: false # Set true for initial setup
|
||||||
setup: false # Set true in inventory file to execute the setup and initializing procedures
|
setup: false # Set true in inventory file to execute the setup and initializing procedures
|
||||||
credentials:
|
credentials:
|
||||||
# Check out the README.md of the docker-mastodon role to get detailled instructions about how to setup the credentials
|
|
||||||
# database_password:
|
|
||||||
# secret_key_base:
|
|
||||||
# otp_secret:
|
|
||||||
# vapid_private_key:
|
|
||||||
# vapid_public_key:
|
|
||||||
# active_record_encryption_deterministic_key:
|
|
||||||
# active_record_encryption_key_derivation_salt:
|
|
||||||
# active_record_encryption_primary_key:
|
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
portfolio_iframe: false
|
portfolio_iframe: false
|
||||||
oidc: true
|
oidc: true
|
||||||
central_database: true
|
central_database: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "microblog.{{ primary_domain }}"
|
@ -17,3 +17,6 @@ csp:
|
|||||||
unsafe-eval: true
|
unsafe-eval: true
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
aliases:
|
||||||
|
- "analytics.{{ primary_domain }}"
|
@ -25,9 +25,9 @@ csp:
|
|||||||
whitelist:
|
whitelist:
|
||||||
connect-src:
|
connect-src:
|
||||||
- "{{ primary_domain }}"
|
- "{{ primary_domain }}"
|
||||||
- "{{ domains.matrix.synapse | safe_var }}"
|
- "matrix.{{ primary_domain }}"
|
||||||
script-src:
|
script-src:
|
||||||
- "{{ domains.matrix.synapse | safe_var }}"
|
- "element.{{ primary_domain }}"
|
||||||
- "https://cdn.jsdelivr.net"
|
- "https://cdn.jsdelivr.net"
|
||||||
plugins:
|
plugins:
|
||||||
# You need to enable them in the inventory file
|
# You need to enable them in the inventory file
|
||||||
@ -39,3 +39,8 @@ plugins:
|
|||||||
slack: false
|
slack: false
|
||||||
telegram: false
|
telegram: false
|
||||||
whatsapp: false
|
whatsapp: false
|
||||||
|
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
synapse: "matrix.{{ primary_domain }}"
|
||||||
|
element: "element.{{ primary_domain }}"
|
3
roles/docker-mediawiki/vars/configuration.yml
Normal file
3
roles/docker-mediawiki/vars/configuration.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "wiki.{{ primary_domain }}"
|
@ -23,3 +23,6 @@ csp:
|
|||||||
- "blob:"
|
- "blob:"
|
||||||
script-src:
|
script-src:
|
||||||
- "https://cdn.jsdelivr.net"
|
- "https://cdn.jsdelivr.net"
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "academy.{{ primary_domain }}"
|
@ -1,6 +1,4 @@
|
|||||||
version: "production" # @see https://nextcloud.com/blog/nextcloud-release-channels-and-how-to-track-them/
|
version: "production" # @see https://nextcloud.com/blog/nextcloud-release-channels-and-how-to-track-them/
|
||||||
ldap:
|
|
||||||
enabled: True # Enables LDAP by default
|
|
||||||
csp:
|
csp:
|
||||||
flags:
|
flags:
|
||||||
style-src:
|
style-src:
|
||||||
@ -10,6 +8,10 @@ csp:
|
|||||||
whitelist:
|
whitelist:
|
||||||
font-src:
|
font-src:
|
||||||
- "data:"
|
- "data:"
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "cloud.{{ primary_domain }}"
|
||||||
|
|
||||||
oidc:
|
oidc:
|
||||||
enabled: "{{ applications.nextcloud.features.oidc | default(true) }}" # Activate OIDC for Nextcloud
|
enabled: "{{ applications.nextcloud.features.oidc | default(true) }}" # Activate OIDC for Nextcloud
|
||||||
# floavor decides which OICD plugin should be used.
|
# floavor decides which OICD plugin should be used.
|
||||||
|
@ -4,4 +4,4 @@ plugin_configuration:
|
|||||||
configvalue: "{{ applications.bigbluebutton.credentials.shared_secret }}"
|
configvalue: "{{ applications.bigbluebutton.credentials.shared_secret }}"
|
||||||
- appid: "bbb"
|
- appid: "bbb"
|
||||||
configkey: "api.url"
|
configkey: "api.url"
|
||||||
configvalue: "{{ applications.bigbluebutton.urls.api }}"
|
configvalue: "{{ web_protocol }}://{{domains | get_domain(''bigbluebutton'')}}{{applications.bigbluebutton.api_suffix}}"
|
@ -1,6 +1,5 @@
|
|||||||
configuration_file: "oauth2-proxy-keycloak.cfg" # Needs to be set true in the roles which use it
|
configuration_file: "oauth2-proxy-keycloak.cfg" # Needs to be set true in the roles which use it
|
||||||
version: "latest" # Docker Image version
|
version: "latest" # Docker Image version
|
||||||
redirect_url: "{{ web_protocol }}://{{domains | get_domain('keycloak')}}/auth/realms/{{primary_domain}}/protocol/openid-connect/auth" # The redirect URL for the OAuth2 flow. It should match the redirect URL configured in Keycloak.
|
|
||||||
allowed_roles: admin # Restrict it default to admin role. Use the vars/main.yml to open the specific role for other groups
|
allowed_roles: admin # Restrict it default to admin role. Use the vars/main.yml to open the specific role for other groups
|
||||||
features:
|
features:
|
||||||
matomo: true
|
matomo: true
|
||||||
|
@ -17,3 +17,6 @@ csp:
|
|||||||
flags:
|
flags:
|
||||||
script-src:
|
script-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "project.{{ primary_domain }}"
|
@ -10,3 +10,8 @@ csp:
|
|||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "video.{{ primary_domain }}"
|
||||||
|
aliases:
|
||||||
|
- "videos.{{ primary_domain }}"
|
@ -16,3 +16,7 @@ csp:
|
|||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
script-src:
|
script-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
aliases:
|
||||||
|
- "mysql.{{ primary_domain }}"
|
||||||
|
- "mariadb.{{ primary_domain }}"
|
||||||
|
@ -12,3 +12,8 @@ csp:
|
|||||||
unsafe-eval: true
|
unsafe-eval: true
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "picture.{{ primary_domain }}"
|
||||||
|
aliases:
|
||||||
|
- "pictures.{{ primary_domain }}"
|
@ -19,3 +19,7 @@ csp:
|
|||||||
flags:
|
flags:
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "{{ primary_domain }}"
|
||||||
|
|
||||||
|
@ -19,3 +19,6 @@ csp:
|
|||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
script-src:
|
script-src:
|
||||||
unsafe-eval: true
|
unsafe-eval: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "slides.{{ primary_domain }}"
|
3
roles/docker-roulette-wheel/vars/configuration.yml
Normal file
3
roles/docker-roulette-wheel/vars/configuration.yml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "wheel.{{ primary_domain }}"
|
@ -4,3 +4,6 @@ features:
|
|||||||
css: true
|
css: true
|
||||||
portfolio_iframe: false
|
portfolio_iframe: false
|
||||||
central_database: true
|
central_database: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "inventory.{{ primary_domain }}"
|
||||||
|
@ -9,3 +9,6 @@ csp:
|
|||||||
unsafe-eval: true
|
unsafe-eval: true
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "docs.{{ primary_domain }}"
|
||||||
|
@ -20,3 +20,6 @@ csp:
|
|||||||
unsafe-eval: true
|
unsafe-eval: true
|
||||||
style-src:
|
style-src:
|
||||||
unsafe-inline: true
|
unsafe-inline: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "kanban.{{ primary_domain }}"
|
@ -31,6 +31,9 @@ csp:
|
|||||||
- "https://fonts.bunny.net"
|
- "https://fonts.bunny.net"
|
||||||
script-src:
|
script-src:
|
||||||
- "https://cdn.gtranslate.net"
|
- "https://cdn.gtranslate.net"
|
||||||
- "{{ domains | get_domain('wordpress') }}"
|
- "blog.{{ primary_domain }}"
|
||||||
style-src:
|
style-src:
|
||||||
- "https://fonts.bunny.net"
|
- "https://fonts.bunny.net"
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "blog.{{ primary_domain }}"
|
||||||
|
0
roles/docker-xmpp/vars/configuration.yml
Normal file
0
roles/docker-xmpp/vars/configuration.yml
Normal file
@ -12,3 +12,8 @@ features:
|
|||||||
portfolio_iframe: false
|
portfolio_iframe: false
|
||||||
central_database: true
|
central_database: true
|
||||||
oauth2: true
|
oauth2: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "s.{{ primary_domain }}"
|
||||||
|
aliases:
|
||||||
|
- "short.{{ primary_domain }}"
|
@ -2,3 +2,6 @@ features:
|
|||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
portfolio_iframe: true
|
portfolio_iframe: true
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "files.{{ primary_domain }}"
|
||||||
|
@ -2,3 +2,6 @@ features:
|
|||||||
matomo: true
|
matomo: true
|
||||||
css: true
|
css: true
|
||||||
portfolio_iframe: false
|
portfolio_iframe: false
|
||||||
|
domains:
|
||||||
|
canonical:
|
||||||
|
- "html.{{ primary_domain }}"
|
||||||
|
@ -8,6 +8,11 @@
|
|||||||
- name: Merge system_email definitions
|
- name: Merge system_email definitions
|
||||||
set_fact:
|
set_fact:
|
||||||
system_email: "{{ default_system_email | combine(system_email | default({}, true), recursive=True) }}"
|
system_email: "{{ default_system_email | combine(system_email | default({}, true), recursive=True) }}"
|
||||||
|
|
||||||
|
- name: Merge application definitions
|
||||||
|
set_fact:
|
||||||
|
applications: "{{ defaults_applications | combine(applications | default({}, true), recursive=True) }}"
|
||||||
|
|
||||||
- name: Merge domain definitions
|
- name: Merge domain definitions
|
||||||
set_fact:
|
set_fact:
|
||||||
domains: "{{ defaults_domains | combine(domains | default({}, true), recursive=True) }}"
|
domains: "{{ defaults_domains | combine(domains | default({}, true), recursive=True) }}"
|
||||||
@ -28,10 +33,6 @@
|
|||||||
redirect_domain_mappings: "{{ redirect_domain_mappings | default([]) + [ {'source': item.key, 'target': item.value} ] }}"
|
redirect_domain_mappings: "{{ redirect_domain_mappings | default([]) + [ {'source': item.key, 'target': item.value} ] }}"
|
||||||
loop: "{{ combined_mapping | dict2items }}"
|
loop: "{{ combined_mapping | dict2items }}"
|
||||||
|
|
||||||
- name: Merge application definitions
|
|
||||||
set_fact:
|
|
||||||
applications: "{{ defaults_applications | combine(applications | default({}, true), recursive=True) }}"
|
|
||||||
|
|
||||||
# @todo implement
|
# @todo implement
|
||||||
# - name: Ensure features.integrated is set based on group membership
|
# - name: Ensure features.integrated is set based on group membership
|
||||||
# set_fact:
|
# set_fact:
|
||||||
|
55
tests/integration/test_domain_uniqueness.py
Normal file
55
tests/integration/test_domain_uniqueness.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import unittest
|
||||||
|
import yaml
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
class TestDomainUniqueness(unittest.TestCase):
|
||||||
|
def test_no_duplicate_domains(self):
|
||||||
|
"""
|
||||||
|
Load the applications YAML (generating it via `make build` if missing),
|
||||||
|
collect all entries under domains.canonical and domains.aliases across all applications,
|
||||||
|
and assert that no domain appears more than once.
|
||||||
|
"""
|
||||||
|
repo_root = Path(__file__).resolve().parents[2]
|
||||||
|
yaml_file = repo_root / 'group_vars' / 'all' / '03_applications.yml'
|
||||||
|
|
||||||
|
# Generate the file if it doesn't exist
|
||||||
|
if not yaml_file.exists():
|
||||||
|
subprocess.run(['make', 'build'], cwd=repo_root, check=True)
|
||||||
|
|
||||||
|
# Load the applications configuration
|
||||||
|
cfg = yaml.safe_load(yaml_file.read_text(encoding='utf-8')) or {}
|
||||||
|
apps = cfg.get('defaults_applications', {})
|
||||||
|
|
||||||
|
all_domains = []
|
||||||
|
for app_name, app_cfg in apps.items():
|
||||||
|
domains_cfg = app_cfg.get('domains', {})
|
||||||
|
|
||||||
|
# canonical entries may be a list or a mapping
|
||||||
|
canonical = domains_cfg.get('canonical', [])
|
||||||
|
if isinstance(canonical, dict):
|
||||||
|
values = list(canonical.values())
|
||||||
|
else:
|
||||||
|
values = canonical or []
|
||||||
|
all_domains.extend(values)
|
||||||
|
|
||||||
|
# aliases entries may be a list or a mapping
|
||||||
|
aliases = domains_cfg.get('aliases', [])
|
||||||
|
if isinstance(aliases, dict):
|
||||||
|
values = list(aliases.values())
|
||||||
|
else:
|
||||||
|
values = aliases or []
|
||||||
|
all_domains.extend(values)
|
||||||
|
|
||||||
|
# Filter out any empty or non-string entries
|
||||||
|
domain_list = [d for d in all_domains if isinstance(d, str) and d.strip()]
|
||||||
|
counts = Counter(domain_list)
|
||||||
|
|
||||||
|
# Find duplicates
|
||||||
|
duplicates = [domain for domain, count in counts.items() if count > 1]
|
||||||
|
if duplicates:
|
||||||
|
self.fail(f"Duplicate domain entries found: {duplicates}\n (May 'make build' solves this issue.)")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
89
tests/integration/test_domains_structure.py
Normal file
89
tests/integration/test_domains_structure.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
ROLES_DIR = Path(__file__).resolve().parent.parent.parent / "roles"
|
||||||
|
|
||||||
|
class TestDomainsStructure(unittest.TestCase):
|
||||||
|
def test_domains_keys_types_and_uniqueness(self):
|
||||||
|
"""Ensure that under 'domains' only 'canonical' and 'aliases' keys exist,
|
||||||
|
'aliases' is a list of strings, 'canonical' is either a list of strings
|
||||||
|
or a dict with string values, and no domain is defined more than once
|
||||||
|
across all roles."""
|
||||||
|
failed_roles = []
|
||||||
|
all_domains = []
|
||||||
|
|
||||||
|
for role_path in ROLES_DIR.iterdir():
|
||||||
|
if not role_path.is_dir():
|
||||||
|
continue
|
||||||
|
vars_dir = role_path / "vars"
|
||||||
|
if not vars_dir.exists():
|
||||||
|
continue
|
||||||
|
|
||||||
|
for vars_file in vars_dir.glob("*.yml"):
|
||||||
|
try:
|
||||||
|
with open(vars_file, 'r') as f:
|
||||||
|
data = yaml.safe_load(f) or {}
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
failed_roles.append((role_path.name, vars_file.name, f"YAML error: {e}"))
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'domains' not in data:
|
||||||
|
continue
|
||||||
|
|
||||||
|
domains = data['domains']
|
||||||
|
if not isinstance(domains, dict):
|
||||||
|
failed_roles.append((role_path.name, vars_file.name, "'domains' should be a dict"))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check allowed keys
|
||||||
|
allowed_keys = {'canonical', 'aliases'}
|
||||||
|
extra_keys = set(domains.keys()) - allowed_keys
|
||||||
|
if extra_keys:
|
||||||
|
failed_roles.append((role_path.name, vars_file.name,
|
||||||
|
f"Unexpected keys in 'domains': {extra_keys}"))
|
||||||
|
|
||||||
|
# Validate and collect 'aliases'
|
||||||
|
if 'aliases' in domains:
|
||||||
|
aliases = domains['aliases']
|
||||||
|
if not isinstance(aliases, list) or not all(isinstance(item, str) for item in aliases):
|
||||||
|
failed_roles.append((role_path.name, vars_file.name,
|
||||||
|
"'aliases' must be a list of strings"))
|
||||||
|
else:
|
||||||
|
all_domains.extend(aliases)
|
||||||
|
|
||||||
|
# Validate and collect 'canonical'
|
||||||
|
if 'canonical' in domains:
|
||||||
|
canonical = domains['canonical']
|
||||||
|
if isinstance(canonical, list):
|
||||||
|
if not all(isinstance(item, str) for item in canonical):
|
||||||
|
failed_roles.append((role_path.name, vars_file.name,
|
||||||
|
"'canonical' list items must be strings"))
|
||||||
|
else:
|
||||||
|
all_domains.extend(canonical)
|
||||||
|
elif isinstance(canonical, dict):
|
||||||
|
if not all(isinstance(k, str) and isinstance(v, str) for k, v in canonical.items()):
|
||||||
|
failed_roles.append((role_path.name, vars_file.name,
|
||||||
|
"All keys and values in 'canonical' dict must be strings"))
|
||||||
|
else:
|
||||||
|
all_domains.extend(canonical.values())
|
||||||
|
else:
|
||||||
|
failed_roles.append((role_path.name, vars_file.name,
|
||||||
|
"'canonical' must be a list or a dict"))
|
||||||
|
|
||||||
|
# Check for duplicate domains across all roles
|
||||||
|
duplicates = [domain for domain, count in Counter(all_domains).items() if count > 1]
|
||||||
|
if duplicates:
|
||||||
|
failed_roles.append(("GLOBAL", "", f"Duplicate domain entries found: {duplicates}"))
|
||||||
|
|
||||||
|
if failed_roles:
|
||||||
|
messages = []
|
||||||
|
for role, file, reason in failed_roles:
|
||||||
|
entry = f"{role}/{file}: {reason}" if file else f"{role}: {reason}"
|
||||||
|
messages.append(entry)
|
||||||
|
self.fail("Domain structure errors found:\n" + "\n".join(messages))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
123
tests/unit/test_domain_filters.py
Normal file
123
tests/unit/test_domain_filters.py
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# Add the filter_plugins directory to the import path
|
||||||
|
dir_path = os.path.abspath(
|
||||||
|
os.path.join(os.path.dirname(__file__), '../../filter_plugins')
|
||||||
|
)
|
||||||
|
sys.path.insert(0, dir_path)
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleFilterError
|
||||||
|
from domain_filters import FilterModule
|
||||||
|
|
||||||
|
class TestDomainFilters(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.filter_module = FilterModule()
|
||||||
|
# Sample primary domain
|
||||||
|
self.primary = 'example.com'
|
||||||
|
|
||||||
|
def test_canonical_empty_apps(self):
|
||||||
|
apps = {}
|
||||||
|
expected = {}
|
||||||
|
result = self.filter_module.canonical_domains_map(apps, self.primary)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_canonical_without_domains(self):
|
||||||
|
apps = {'app1': {}}
|
||||||
|
expected = {'app1': ['app1.example.com']}
|
||||||
|
result = self.filter_module.canonical_domains_map(apps, self.primary)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_canonical_with_list(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {
|
||||||
|
'domains': {'canonical': ['foo.com', 'bar.com']}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.filter_module.canonical_domains_map(apps, self.primary)
|
||||||
|
self.assertCountEqual(result['app1'], ['foo.com', 'bar.com'])
|
||||||
|
|
||||||
|
def test_canonical_with_dict(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {
|
||||||
|
'domains': {'canonical': {'one': 'one.com', 'two': 'two.com'}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = self.filter_module.canonical_domains_map(apps, self.primary)
|
||||||
|
self.assertCountEqual(result['app1'], ['one.com', 'two.com'])
|
||||||
|
|
||||||
|
def test_canonical_duplicate_raises(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {'domains': {'canonical': ['dup.com']}},
|
||||||
|
'app2': {'domains': {'canonical': ['dup.com']}},
|
||||||
|
}
|
||||||
|
with self.assertRaises(AnsibleFilterError) as cm:
|
||||||
|
self.filter_module.canonical_domains_map(apps, self.primary)
|
||||||
|
self.assertIn("configured for both", str(cm.exception))
|
||||||
|
|
||||||
|
def test_alias_empty_apps(self):
|
||||||
|
apps = {}
|
||||||
|
expected = {}
|
||||||
|
result = self.filter_module.alias_domains_map(apps, self.primary)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_alias_without_aliases_and_no_canonical(self):
|
||||||
|
apps = {'app1': {}}
|
||||||
|
# canonical defaults to ['app1.example.com'], so alias should be []
|
||||||
|
expected = {'app1': []}
|
||||||
|
result = self.filter_module.alias_domains_map(apps, self.primary)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_alias_with_explicit_aliases(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {
|
||||||
|
'domains': {'aliases': ['alias.com']}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# canonical defaults to ['app1.example.com'], so alias should include alias.com and default
|
||||||
|
expected = {'app1': ['alias.com', 'app1.example.com']}
|
||||||
|
result = self.filter_module.alias_domains_map(apps, self.primary)
|
||||||
|
self.assertCountEqual(result['app1'], expected['app1'])
|
||||||
|
|
||||||
|
def test_alias_with_canonical_not_default(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {
|
||||||
|
'domains': {'canonical': ['foo.com']}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# foo.com is canonical, default not in canonical so added as alias
|
||||||
|
expected = {'app1': ['app1.example.com']}
|
||||||
|
result = self.filter_module.alias_domains_map(apps, self.primary)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_alias_with_existing_default(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {
|
||||||
|
'domains': {
|
||||||
|
'canonical': ['foo.com'],
|
||||||
|
'aliases': ['app1.example.com']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# default present in aliases, should not be duplicated
|
||||||
|
expected = {'app1': ['app1.example.com']}
|
||||||
|
result = self.filter_module.alias_domains_map(apps, self.primary)
|
||||||
|
self.assertEqual(result, expected)
|
||||||
|
|
||||||
|
def test_invalid_canonical_type(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {'domains': {'canonical': 123}}
|
||||||
|
}
|
||||||
|
with self.assertRaises(AnsibleFilterError):
|
||||||
|
self.filter_module.canonical_domains_map(apps, self.primary)
|
||||||
|
|
||||||
|
def test_invalid_aliases_type(self):
|
||||||
|
apps = {
|
||||||
|
'app1': {'domains': {'aliases': 123}}
|
||||||
|
}
|
||||||
|
with self.assertRaises(AnsibleFilterError):
|
||||||
|
self.filter_module.alias_domains_map(apps, self.primary)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
@ -23,7 +23,7 @@ class TestGenerateDefaultApplications(unittest.TestCase):
|
|||||||
(self.sample_role / "vars" / "configuration.yml").write_text("foo: bar\nbaz: 123\n")
|
(self.sample_role / "vars" / "configuration.yml").write_text("foo: bar\nbaz: 123\n")
|
||||||
|
|
||||||
# Output file path
|
# Output file path
|
||||||
self.output_file = self.temp_dir / "group_vars" / "all" / "11_applications.yml"
|
self.output_file = self.temp_dir / "group_vars" / "all" / "03_applications.yml"
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
shutil.rmtree(self.temp_dir)
|
shutil.rmtree(self.temp_dir)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user