mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-09-08 03:07:14 +02:00
Compare commits
6 Commits
9469452275
...
d1982af63d
Author | SHA1 | Date | |
---|---|---|---|
d1982af63d | |||
409e659143 | |||
562603a8cd | |||
6d4b7227ce | |||
9a8ef5e047 | |||
ad449c3b6a |
11
Makefile
11
Makefile
@@ -25,10 +25,17 @@ clean:
|
||||
@echo "Removing ignored git files"
|
||||
git clean -fdX
|
||||
|
||||
list:
|
||||
@echo Generating the roles list
|
||||
python3 main.py build roles_list
|
||||
|
||||
tree:
|
||||
@echo Generating Tree
|
||||
python3 main.py build tree -D 2 --no-signal
|
||||
|
||||
mig: list tree
|
||||
@echo Creating meta data for meta infinity graph
|
||||
|
||||
dockerignore:
|
||||
@echo Create dockerignore
|
||||
cat .gitignore > .dockerignore
|
||||
@@ -61,9 +68,9 @@ build: clean dockerignore
|
||||
install: build
|
||||
@echo "⚙️ Install complete."
|
||||
|
||||
partial-test:
|
||||
messy-test:
|
||||
@echo "🧪 Running Python tests…"
|
||||
python -m unittest discover -s tests
|
||||
PYTHONPATH=. python -m unittest discover -s tests
|
||||
@echo "📑 Checking Ansible syntax…"
|
||||
ansible-playbook playbook.yml --syntax-check
|
||||
|
||||
|
@@ -5,7 +5,7 @@ import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure project root on PYTHONPATH so utils is importable
|
||||
# Ensure project root on PYTHONPATH so module_utils is importable
|
||||
repo_root = Path(__file__).resolve().parent.parent.parent.parent
|
||||
sys.path.insert(0, str(repo_root))
|
||||
|
||||
@@ -13,7 +13,7 @@ sys.path.insert(0, str(repo_root))
|
||||
plugin_path = repo_root / "lookup_plugins"
|
||||
sys.path.insert(0, str(plugin_path))
|
||||
|
||||
from utils.dict_renderer import DictRenderer
|
||||
from module_utils.dict_renderer import DictRenderer
|
||||
from application_gid import LookupModule
|
||||
|
||||
def load_yaml_file(path: Path) -> dict:
|
||||
|
113
cli/build/inventory/full.py
Normal file
113
cli/build/inventory/full.py
Normal file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
# cli/build/inventory/full.py
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
|
||||
try:
|
||||
from filter_plugins.get_all_invokable_apps import get_all_invokable_apps
|
||||
except ImportError:
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
|
||||
from filter_plugins.get_all_invokable_apps import get_all_invokable_apps
|
||||
|
||||
import yaml
|
||||
import json
|
||||
|
||||
def build_group_inventory(apps, host):
|
||||
"""
|
||||
Builds a group-based Ansible inventory: each app is a group containing the host.
|
||||
"""
|
||||
groups = {app: {"hosts": [host]} for app in apps}
|
||||
inventory = {
|
||||
"all": {
|
||||
"hosts": [host],
|
||||
"children": {app: {} for app in apps},
|
||||
},
|
||||
**groups
|
||||
}
|
||||
return inventory
|
||||
|
||||
def build_hostvar_inventory(apps, host):
|
||||
"""
|
||||
Alternative: Builds an inventory where all invokables are set as hostvars (as a list).
|
||||
"""
|
||||
return {
|
||||
"all": {
|
||||
"hosts": [host],
|
||||
},
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
host: {
|
||||
"invokable_applications": apps
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Build a dynamic Ansible inventory for a given host with all invokable applications.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--host',
|
||||
required=True,
|
||||
help='Hostname to assign to all invokable application groups'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-f', '--format',
|
||||
choices=['json', 'yaml'],
|
||||
default='yaml',
|
||||
help='Output format (yaml [default], json)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--inventory-style',
|
||||
choices=['group', 'hostvars'],
|
||||
default='group',
|
||||
help='Inventory style: group (default, one group per app) or hostvars (list as hostvar)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-c', '--categories-file',
|
||||
default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles', 'categories.yml')),
|
||||
help='Path to roles/categories.yml (default: roles/categories.yml at project root)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-r', '--roles-dir',
|
||||
default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles')),
|
||||
help='Path to roles/ directory (default: roles/ at project root)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-o', '--output',
|
||||
help='Write output to file instead of stdout'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
apps = get_all_invokable_apps(
|
||||
categories_file=args.categories_file,
|
||||
roles_dir=args.roles_dir
|
||||
)
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"Error: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
# Select inventory style
|
||||
if args.inventory_style == 'group':
|
||||
inventory = build_group_inventory(apps, args.host)
|
||||
else:
|
||||
inventory = build_hostvar_inventory(apps, args.host)
|
||||
|
||||
# Output in chosen format
|
||||
if args.format == 'json':
|
||||
output = json.dumps(inventory, indent=2)
|
||||
else:
|
||||
output = yaml.safe_dump(inventory, default_flow_style=False)
|
||||
|
||||
if args.output:
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(output)
|
||||
else:
|
||||
print(output)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@@ -4,9 +4,9 @@ import sys
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from typing import Dict, Any
|
||||
from utils.manager.inventory import InventoryManager
|
||||
from utils.handler.vault import VaultHandler, VaultScalar
|
||||
from utils.handler.yaml import YamlHandler
|
||||
from module_utils.manager.inventory import InventoryManager
|
||||
from module_utils.handler.vault import VaultHandler, VaultScalar
|
||||
from module_utils.handler.yaml import YamlHandler
|
||||
from yaml.dumper import SafeDumper
|
||||
|
||||
|
||||
|
@@ -88,7 +88,7 @@ def validate_application_ids(inventory, app_ids):
|
||||
"""
|
||||
Abort the script if any application IDs are invalid, with detailed reasons.
|
||||
"""
|
||||
from utils.valid_deploy_id import ValidDeployId
|
||||
from module_utils.valid_deploy_id import ValidDeployId
|
||||
validator = ValidDeployId()
|
||||
invalid = validator.validate(inventory, app_ids)
|
||||
if invalid:
|
||||
|
@@ -4,8 +4,8 @@ import sys
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
from typing import Dict, Any
|
||||
from utils.handler.vault import VaultHandler, VaultScalar
|
||||
from utils.handler.yaml import YamlHandler
|
||||
from module_utils.handler.vault import VaultHandler, VaultScalar
|
||||
from module_utils.handler.yaml import YamlHandler
|
||||
from yaml.dumper import SafeDumper
|
||||
|
||||
def ask_for_confirmation(key: str) -> bool:
|
||||
|
49
cli/meta/applications/invokable.py
Normal file
49
cli/meta/applications/invokable.py
Normal file
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
# cli/meta/applications/invokable.py
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Import filter plugin for get_all_invokable_apps
|
||||
try:
|
||||
from filter_plugins.get_all_invokable_apps import get_all_invokable_apps
|
||||
except ImportError:
|
||||
# Try to adjust sys.path if running outside Ansible
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
|
||||
try:
|
||||
from filter_plugins.get_all_invokable_apps import get_all_invokable_apps
|
||||
except ImportError:
|
||||
sys.stderr.write("Could not import filter_plugins.get_all_invokable_apps. Check your PYTHONPATH.\n")
|
||||
sys.exit(1)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='List all invokable applications (application_ids) based on invokable paths from categories.yml and available roles.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-c', '--categories-file',
|
||||
default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles', 'categories.yml')),
|
||||
help='Path to roles/categories.yml (default: roles/categories.yml at project root)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'-r', '--roles-dir',
|
||||
default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles')),
|
||||
help='Path to roles/ directory (default: roles/ at project root)'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
result = get_all_invokable_apps(
|
||||
categories_file=args.categories_file,
|
||||
roles_dir=args.roles_dir
|
||||
)
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"Error: {e}\n")
|
||||
sys.exit(1)
|
||||
|
||||
for app_id in result:
|
||||
print(app_id)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
27
filter_plugins/README.md
Normal file
27
filter_plugins/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Custom Filter Plugins for CyMaIS
|
||||
|
||||
This directory contains custom **Ansible filter plugins** used within the CyMaIS project.
|
||||
|
||||
## When to Use a Filter Plugin
|
||||
|
||||
- **Transform values:** Use filters to transform, extract, reformat, or compute values from existing variables or facts.
|
||||
- **Inline data manipulation:** Filters are designed for inline use in Jinja2 expressions (in templates, tasks, vars, etc.).
|
||||
- **No external lookups:** Filters only operate on data you explicitly pass to them and cannot access external files, the Ansible inventory, or runtime context.
|
||||
|
||||
### Examples
|
||||
|
||||
```jinja2
|
||||
{{ role_name | get_entity_name }}
|
||||
{{ my_list | unique }}
|
||||
{{ user_email | regex_replace('^(.+)@.*$', '\\1') }}
|
||||
````
|
||||
|
||||
## When *not* to Use a Filter Plugin
|
||||
|
||||
* If you need to **load data from an external source** (e.g., file, environment, API), use a lookup plugin instead.
|
||||
* If your logic requires **access to inventory, facts, or host-level information** that is not passed as a parameter.
|
||||
|
||||
## Further Reading
|
||||
|
||||
* [Ansible Filter Plugins Documentation](https://docs.ansible.com/ansible/latest/plugins/filter.html)
|
||||
* [Developing Ansible Filter Plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#developing-filter-plugins)
|
54
filter_plugins/get_all_invokable_apps.py
Normal file
54
filter_plugins/get_all_invokable_apps.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import os
|
||||
import yaml
|
||||
|
||||
def get_all_invokable_apps(
|
||||
categories_file=None,
|
||||
roles_dir=None
|
||||
):
|
||||
"""
|
||||
Return all application_ids (or role names) for roles whose directory names match invokable paths from categories.yml.
|
||||
:param categories_file: Path to categories.yml (default: roles/categories.yml at project root)
|
||||
:param roles_dir: Path to roles directory (default: roles/ at project root)
|
||||
:return: List of application_ids (or role names)
|
||||
"""
|
||||
# Resolve defaults
|
||||
here = os.path.dirname(os.path.abspath(__file__))
|
||||
project_root = os.path.abspath(os.path.join(here, '..'))
|
||||
if not categories_file:
|
||||
categories_file = os.path.join(project_root, 'roles', 'categories.yml')
|
||||
if not roles_dir:
|
||||
roles_dir = os.path.join(project_root, 'roles')
|
||||
|
||||
# Get invokable paths
|
||||
from filter_plugins.invokable_paths import get_invokable_paths
|
||||
invokable_paths = get_invokable_paths(categories_file)
|
||||
if not invokable_paths:
|
||||
return []
|
||||
|
||||
result = []
|
||||
if not os.path.isdir(roles_dir):
|
||||
return []
|
||||
|
||||
for role in sorted(os.listdir(roles_dir)):
|
||||
role_path = os.path.join(roles_dir, role)
|
||||
if not os.path.isdir(role_path):
|
||||
continue
|
||||
if any(role == p or role.startswith(p + '-') for p in invokable_paths):
|
||||
vars_file = os.path.join(role_path, 'vars', 'main.yml')
|
||||
if os.path.isfile(vars_file):
|
||||
try:
|
||||
with open(vars_file, 'r', encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
app_id = data.get('application_id', role)
|
||||
except Exception:
|
||||
app_id = role
|
||||
else:
|
||||
app_id = role
|
||||
result.append(app_id)
|
||||
return sorted(result)
|
||||
|
||||
class FilterModule(object):
|
||||
def filters(self):
|
||||
return {
|
||||
'get_all_invokable_apps': get_all_invokable_apps
|
||||
}
|
@@ -1,5 +1,3 @@
|
||||
# filter_plugins/get_application_id.py
|
||||
|
||||
import os
|
||||
import re
|
||||
import yaml
|
||||
|
@@ -1,9 +1,15 @@
|
||||
def get_docker_compose(path_docker_compose_instances: str, application_id: str) -> dict:
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from module_utils.entity_name_utils import get_entity_name
|
||||
|
||||
def get_docker_paths(application_id: str, path_docker_compose_instances: str) -> dict:
|
||||
"""
|
||||
Build the docker_compose dict based on
|
||||
path_docker_compose_instances and application_id.
|
||||
Uses get_entity_name to extract the entity name from application_id.
|
||||
"""
|
||||
base = f"{path_docker_compose_instances}{application_id}/"
|
||||
entity = get_entity_name(application_id)
|
||||
base = f"{path_docker_compose_instances}{entity}/"
|
||||
|
||||
return {
|
||||
'directories': {
|
||||
@@ -23,5 +29,5 @@ def get_docker_compose(path_docker_compose_instances: str, application_id: str)
|
||||
class FilterModule(object):
|
||||
def filters(self):
|
||||
return {
|
||||
'get_docker_compose': get_docker_compose,
|
||||
'get_docker_paths': get_docker_paths,
|
||||
}
|
9
filter_plugins/get_entity_name.py
Normal file
9
filter_plugins/get_entity_name.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
from module_utils.entity_name_utils import get_entity_name
|
||||
|
||||
class FilterModule(object):
|
||||
def filters(self):
|
||||
return {
|
||||
'get_entity_name': get_entity_name,
|
||||
}
|
@@ -1,17 +0,0 @@
|
||||
class FilterModule(object):
|
||||
def filters(self):
|
||||
return {
|
||||
'get_public_id': self.get_public_id
|
||||
}
|
||||
|
||||
def get_public_id(self, value):
|
||||
"""
|
||||
Extract the substring after the last hyphen in the input string.
|
||||
Example:
|
||||
'service-user-abc123' => 'abc123'
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
raise ValueError("Expected a string")
|
||||
if '-' not in value:
|
||||
raise ValueError("No hyphen found in input string")
|
||||
return value.rsplit('-', 1)[-1]
|
@@ -1,5 +1,3 @@
|
||||
# filter_plugins/role_path_by_app_id.py
|
||||
|
||||
import os
|
||||
import glob
|
||||
import yaml
|
||||
|
@@ -1,4 +1,3 @@
|
||||
# file: filter_plugins/safe_join.py
|
||||
"""
|
||||
Ansible filter plugin that joins a base string and a tail path safely.
|
||||
If the base is falsy (None, empty, etc.), returns an empty string.
|
||||
|
@@ -1,5 +1,3 @@
|
||||
# filter_plugins/text_filters.py
|
||||
|
||||
from ansible.errors import AnsibleFilterError
|
||||
import re
|
||||
|
||||
|
@@ -27,7 +27,7 @@ ports:
|
||||
web-app-mediawiki: 8004
|
||||
web-app-mybb: 8005
|
||||
yourls: 8006
|
||||
mailu: 8007
|
||||
web-app-mailu: 8007
|
||||
web-app-elk: 8008
|
||||
web-app-mastodon: 8009
|
||||
web-app-pixelfed: 8010
|
||||
@@ -67,7 +67,7 @@ ports:
|
||||
simpleicons: 8044
|
||||
libretranslate: 8055
|
||||
pretix: 8056
|
||||
bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port
|
||||
web-app-bigbluebutton: 48087 # This port is predefined by bbb. @todo Try to change this to a 8XXX port
|
||||
# Ports which are exposed to the World Wide Web
|
||||
|
||||
public:
|
||||
@@ -78,8 +78,8 @@ ports:
|
||||
ldaps:
|
||||
svc-db-openldap: 636
|
||||
stun:
|
||||
bigbluebutton: 3478 # Not sure if it's right placed here or if it should be moved to localhost section
|
||||
web-app-bigbluebutton: 3478 # Not sure if it's right placed here or if it should be moved to localhost section
|
||||
web-app-nextcloud: 3479
|
||||
turn:
|
||||
bigbluebutton: 5349 # Not sure if it's right placed here or if it should be moved to localhost section
|
||||
web-app-bigbluebutton: 5349 # Not sure if it's right placed here or if it should be moved to localhost section
|
||||
web-app-nextcloud: 5350 # Not used yet
|
@@ -42,7 +42,7 @@ defaults_networks:
|
||||
subnet: 192.168.101.240/28
|
||||
web-app-matrix:
|
||||
subnet: 192.168.102.0/28
|
||||
mailu:
|
||||
web-app-mailu:
|
||||
# Use one of the last container ips for dns resolving so that it isn't used
|
||||
dns: 192.168.102.29
|
||||
subnet: 192.168.102.16/28
|
||||
@@ -94,7 +94,7 @@ defaults_networks:
|
||||
subnet: 192.168.103.144/28
|
||||
|
||||
# /24 Networks / 254 Usable Clients
|
||||
bigbluebutton:
|
||||
web-app-bigbluebutton:
|
||||
subnet: 10.7.7.0/24 # This variable does not have an impact. It's just there for documentation reasons, because this network is used in bbb
|
||||
svc-db-postgres:
|
||||
subnet: 192.168.200.0/24
|
||||
|
@@ -19,7 +19,7 @@ defaults_service_provider:
|
||||
bluesky: >-
|
||||
{{ ('@' ~ users.contact.username ~ '.' ~ domains.bluesky.api)
|
||||
if 'bluesky' in group_names else '' }}
|
||||
email: "{{ users.contact.username ~ '@' ~ primary_domain if 'mailu' in group_names else '' }}"
|
||||
email: "{{ users.contact.username ~ '@' ~ primary_domain if 'web-app-mailu' in group_names else '' }}"
|
||||
mastodon: "{{ '@' ~ users.contact.username ~ '@' ~ domains | get_domain('web-app-mastodon') if 'web-app-mastodon' in group_names else '' }}"
|
||||
matrix: "{{ '@' ~ users.contact.username ~ ':' ~ domains['web-app-matrix'].synapse if 'web-app-matrix' in group_names else '' }}"
|
||||
peertube: "{{ '@' ~ users.contact.username ~ '@' ~ domains | get_domain('web-app-peertube') if 'web-app-peertube' in group_names else '' }}"
|
||||
|
40
library/README.md
Normal file
40
library/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Custom Modules (`library/`) for CyMaIS
|
||||
|
||||
This directory contains **custom Ansible modules** developed specifically for the CyMaIS project.
|
||||
|
||||
## When to Use the `library/` Directory
|
||||
|
||||
- **Place custom Ansible modules here:**
|
||||
Use this directory for any Python modules you have written yourself that are not part of the official Ansible distribution.
|
||||
- **Extend automation capabilities:**
|
||||
Custom modules allow you to implement logic, workflows, or integrations that are not available through built-in Ansible modules or existing community collections.
|
||||
- **Project-specific functionality:**
|
||||
Use for project- or infrastructure-specific tasks, such as managing custom APIs, provisioning special infrastructure resources, or integrating with internal systems.
|
||||
|
||||
### Examples
|
||||
|
||||
- Managing a special internal API for your company.
|
||||
- Automating a resource that has no official Ansible module.
|
||||
- Creating a highly customized deployment step for your environment.
|
||||
|
||||
## Usage Example
|
||||
|
||||
In your playbook, call your custom module as you would any other Ansible module:
|
||||
```yaml
|
||||
- name: Use custom CyMaIS module
|
||||
cymais_my_custom_module:
|
||||
option1: value1
|
||||
option2: value2
|
||||
````
|
||||
|
||||
Ansible automatically looks in the `library/` directory for custom modules during execution.
|
||||
|
||||
## When *not* to Use the `library/` Directory
|
||||
|
||||
* Do **not** place shared utility code here—put that in `module_utils/` for use across multiple modules or plugins.
|
||||
* Do **not** put filter or lookup plugins here—those belong in `filter_plugins/` or `lookup_plugins/` respectively.
|
||||
|
||||
## Further Reading
|
||||
|
||||
* [Developing Ansible Modules](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules.html)
|
||||
* [Best Practices: Organizing Custom Modules](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html)
|
28
lookup_plugins/README.md
Normal file
28
lookup_plugins/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Custom Lookup Plugins for CyMaIS
|
||||
|
||||
This directory contains custom **Ansible lookup plugins** used within the CyMaIS project.
|
||||
|
||||
## When to Use a Lookup Plugin
|
||||
|
||||
- **Load external data:** Use lookups to retrieve data from files, APIs, databases, environment variables, or other external sources.
|
||||
- **Context-aware data access:** Lookups can access the full Ansible context, including inventory, facts, and runtime variables.
|
||||
- **Generate dynamic lists:** Lookups are often used to build inventories, secrets, or host lists dynamically.
|
||||
|
||||
### Examples
|
||||
|
||||
```yaml
|
||||
# Load the contents of a file as a variable
|
||||
my_secret: "{{ lookup('file', '/path/to/secret.txt') }}"
|
||||
|
||||
# Retrieve a list of hostnames from an external source
|
||||
host_list: "{{ lookup('cymais_inventory_hosts', 'group_name') }}"
|
||||
````
|
||||
|
||||
## When *not* to Use a Lookup Plugin
|
||||
|
||||
* If you only need to **manipulate or transform data already available** in your playbook, prefer a filter plugin instead.
|
||||
|
||||
## Further Reading
|
||||
|
||||
* [Ansible Lookup Plugins Documentation](https://docs.ansible.com/ansible/latest/plugins/lookup.html)
|
||||
* [Developing Ansible Lookup Plugins](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#developing-lookup-plugins)
|
2
main.py
2
main.py
@@ -35,7 +35,7 @@ if _IN_DOCKER:
|
||||
|
||||
Sound = Quiet
|
||||
else:
|
||||
from utils.sounds import Sound
|
||||
from module_utils.sounds import Sound
|
||||
|
||||
|
||||
def color_text(text, color):
|
||||
|
32
module_utils/README.md
Normal file
32
module_utils/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Shared Utility Code (`module_utils/`) for CyMaIS
|
||||
|
||||
This directory contains shared Python utility code (also known as "library code") for use by custom Ansible modules, plugins, or roles in the CyMaIS project.
|
||||
|
||||
## When to Use `module_utils`
|
||||
|
||||
- **Shared logic:** Use `module_utils` to define functions, classes, or helpers that are shared across multiple custom modules, plugins, or filter/lookups in your project.
|
||||
- **Reduce duplication:** Centralize code such as API clients, input validation, complex calculations, or protocol helpers.
|
||||
- **Maintainability:** If you find yourself repeating code in different custom modules/plugins, refactor it into `module_utils/`.
|
||||
|
||||
### Examples
|
||||
|
||||
- Shared HTTP(S) connection handler for multiple modules.
|
||||
- Common validation or transformation functions for user input.
|
||||
- Utility functions for interacting with Docker, LDAP, etc.
|
||||
|
||||
## Usage Example
|
||||
|
||||
In a custom Ansible module or plugin:
|
||||
```python
|
||||
from ansible.module_utils.cymais_utils import my_shared_function
|
||||
````
|
||||
|
||||
## When *not* to Use `module_utils`
|
||||
|
||||
* Do not place standalone Ansible modules or plugins here—those go into `library/`, `filter_plugins/`, or `lookup_plugins/` respectively.
|
||||
* Only use for code that will be **imported** by other plugins or modules.
|
||||
|
||||
## Further Reading
|
||||
|
||||
* [Ansible Module Utilities Documentation](https://docs.ansible.com/ansible/latest/dev_guide/developing_module_utilities.html)
|
||||
* [Best Practices: Reusing Code with module\_utils](https://docs.ansible.com/ansible/latest/dev_guide/developing_plugins.html#sharing-code-among-plugins)
|
49
module_utils/entity_name_utils.py
Normal file
49
module_utils/entity_name_utils.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import os
|
||||
import yaml
|
||||
|
||||
def load_categories_tree(categories_file):
|
||||
with open(categories_file, 'r', encoding='utf-8') as f:
|
||||
categories = yaml.safe_load(f)['roles']
|
||||
return categories
|
||||
|
||||
def flatten_categories(tree, prefix=''):
|
||||
"""Flattens nested category tree to all possible category paths."""
|
||||
result = []
|
||||
for k, v in tree.items():
|
||||
current = f"{prefix}-{k}" if prefix else k
|
||||
result.append(current)
|
||||
if isinstance(v, dict):
|
||||
for sk, sv in v.items():
|
||||
if isinstance(sv, dict):
|
||||
result.extend(flatten_categories({sk: sv}, current))
|
||||
return result
|
||||
|
||||
def get_entity_name(role_name):
|
||||
"""
|
||||
Get the entity name from a role name by removing the
|
||||
longest matching category path from categories.yml.
|
||||
"""
|
||||
possible_locations = [
|
||||
os.path.join(os.getcwd(), 'roles', 'categories.yml'),
|
||||
os.path.join(os.path.dirname(__file__), '..', 'roles', 'categories.yml'),
|
||||
'roles/categories.yml',
|
||||
]
|
||||
categories_file = None
|
||||
for loc in possible_locations:
|
||||
if os.path.exists(loc):
|
||||
categories_file = loc
|
||||
break
|
||||
if not categories_file:
|
||||
return role_name
|
||||
|
||||
categories_tree = load_categories_tree(categories_file)
|
||||
all_category_paths = flatten_categories(categories_tree)
|
||||
|
||||
role_name_lc = role_name.lower()
|
||||
all_category_paths = [cat.lower() for cat in all_category_paths]
|
||||
for cat in sorted(all_category_paths, key=len, reverse=True):
|
||||
if role_name_lc.startswith(cat + "-"):
|
||||
return role_name[len(cat) + 1:]
|
||||
if role_name_lc == cat:
|
||||
return ""
|
||||
return role_name
|
@@ -1,7 +1,7 @@
|
||||
import yaml
|
||||
from yaml.loader import SafeLoader
|
||||
from typing import Any, Dict
|
||||
from utils.handler.vault import VaultScalar
|
||||
from module_utils.handler.vault import VaultScalar
|
||||
|
||||
class YamlHandler:
|
||||
@staticmethod
|
@@ -3,8 +3,8 @@ import hashlib
|
||||
import bcrypt
|
||||
from pathlib import Path
|
||||
from typing import Dict
|
||||
from utils.handler.yaml import YamlHandler
|
||||
from utils.handler.vault import VaultHandler, VaultScalar
|
||||
from module_utils.handler.yaml import YamlHandler
|
||||
from module_utils.handler.vault import VaultHandler, VaultScalar
|
||||
import string
|
||||
import sys
|
||||
import base64
|
@@ -1,4 +1,4 @@
|
||||
# File: utils/valid_deploy_id.py
|
||||
# File: module_utils/valid_deploy_id.py
|
||||
"""
|
||||
Utility for validating deployment application IDs against defined roles and inventory.
|
||||
"""
|
@@ -13,9 +13,9 @@
|
||||
- database:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: [ "CMD", "sh", "-c", "/usr/bin/mariadb --user=$$MYSQL_USER --password=$$MYSQL_PASSWORD --execute 'SHOW DATABASES;'" ]
|
||||
interval: 3s
|
||||
timeout: 1s
|
||||
retries: 5
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 18
|
||||
networks:
|
||||
- default
|
||||
{% endif %}
|
||||
|
@@ -1,14 +1,17 @@
|
||||
# Helper variables
|
||||
_database_id: "svc-db-{{ database_type }}"
|
||||
_database_central_name: "{{ applications | get_app_conf( _database_id, 'docker.services.' ~ database_type ~ '.name') }}"
|
||||
_database_id: "svc-db-{{ database_type }}"
|
||||
_database_central_name: "{{ applications | get_app_conf( _database_id, 'docker.services.' ~ database_type ~ '.name') }}"
|
||||
_database_consumer_entity_name: "{{ database_application_id | get_entity_name }}"
|
||||
_database_central_enabled: "{{ applications | get_app_conf(database_application_id, 'features.central_database', False) }}"
|
||||
|
||||
# Definition
|
||||
database_name: "{{ applications | get_app_conf( database_application_id, 'database.name', false, database_application_id | get_public_id ) }}" # The overwritte configuration is needed by bigbluebutton
|
||||
database_instance: "{{ _database_central_name if applications | get_app_conf(database_application_id, 'features.central_database', False) else database_name }}" # This could lead to bugs at dedicated database @todo cleanup
|
||||
database_host: "{{ _database_central_name if applications | get_app_conf(database_application_id, 'features.central_database', False) else 'database' }}" # This could lead to bugs at dedicated database @todo cleanup
|
||||
database_username: "{{ applications | get_app_conf(database_application_id, 'database.username', false, database_application_id | get_public_id)}}" # The overwritte configuration is needed by bigbluebutton
|
||||
database_name: "{{ applications | get_app_conf( database_application_id, 'database.name', false, _database_consumer_entity_name ) }}" # The overwritte configuration is needed by bigbluebutton
|
||||
database_instance: "{{ _database_central_name if _database_central_enabled else database_name }}" # This could lead to bugs at dedicated database @todo cleanup
|
||||
database_host: "{{ _database_central_name if _database_central_enabled else 'database' }}" # This could lead to bugs at dedicated database @todo cleanup
|
||||
database_username: "{{ applications | get_app_conf(database_application_id, 'database.username', false, _database_consumer_entity_name)}}" # The overwritte configuration is needed by bigbluebutton
|
||||
database_password: "{{ applications | get_app_conf(database_application_id, 'credentials.database_password', true) }}"
|
||||
database_port: "{{ ports.localhost.database[ _database_id ] }}"
|
||||
database_env: "{{docker_compose.directories.env}}{{database_type}}.env"
|
||||
database_url_jdbc: "jdbc:{{ database_type if database_type == 'mariadb' else 'postgresql' }}://{{ database_host }}:{{ database_port }}/{{ database_name }}"
|
||||
database_url_full: "{{database_type}}://{{database_username}}:{{database_password}}@{{database_host}}:{{database_port}}/{{ database_name }}"
|
||||
database_url_full: "{{database_type}}://{{database_username}}:{{database_password}}@{{database_host}}:{{database_port}}/{{ database_name }}"
|
||||
database_volume: "{{ _database_consumer_entity_name ~ '_' if not _database_central_enabled }}{{ database_host }}"
|
||||
|
@@ -5,8 +5,8 @@
|
||||
- docker-compose
|
||||
state: present
|
||||
|
||||
- name: Adding user {{users.client.username}} to relevant docker usergroup
|
||||
- name: "Adding user {{ users[desktop_username].username }} to relevant docker usergroup"
|
||||
user:
|
||||
name: "{{users.client.username}}"
|
||||
name: "{{ users[desktop_username].username }}"
|
||||
groups: docker
|
||||
append: yes
|
||||
|
@@ -1,2 +1,2 @@
|
||||
auto_start_directory: /home/{{users.client.username}}/.config/autostart/
|
||||
auto_start_directory: /home/{{ users[desktop_username].username }}/.config/autostart/
|
||||
application_id: desk-gnome-caffeine
|
||||
|
@@ -5,8 +5,8 @@
|
||||
- gnome-terminal
|
||||
state: present
|
||||
|
||||
- name: "Set zsh as default shell for {{users.client.username}}"
|
||||
- name: "Set zsh as default shell for {{ users[desktop_username].username }}"
|
||||
user:
|
||||
name: "{{users.client.username}}"
|
||||
name: "{{ users[desktop_username].username }}"
|
||||
shell: /usr/bin/zsh
|
||||
become: true
|
@@ -7,8 +7,8 @@
|
||||
ansible.builtin.file:
|
||||
src: "{{nextcloud_cloud_directory}}{{item}}"
|
||||
dest: "{{nextcloud_user_home_directory}}{{item}}"
|
||||
owner: "{{users.client.username}}"
|
||||
group: "{{users.client.username}}"
|
||||
owner: "{{ users[desktop_username].username }}"
|
||||
group: "{{ users[desktop_username].username }}"
|
||||
state: link
|
||||
force: yes
|
||||
ignore_errors: true # Just temporary @todo remove
|
||||
@@ -29,6 +29,6 @@
|
||||
ansible.builtin.file:
|
||||
src: "{{nextcloud_cloud_directory}}InstantUpload"
|
||||
dest: "{{nextcloud_user_home_directory}}Dump"
|
||||
owner: "{{users.client.username}}"
|
||||
group: "{{users.client.username}}"
|
||||
owner: "{{ users[desktop_username].username }}"
|
||||
group: "{{ users[desktop_username].username }}"
|
||||
state: link
|
@@ -1,4 +1,4 @@
|
||||
application_id: desk-nextcloud
|
||||
nextcloud_user_home_directory: "/home/{{users.client.username}}/"
|
||||
nextcloud_user_home_directory: "/home/{{ users[desktop_username].username }}/"
|
||||
nextcloud_cloud_fqdn: "{{ applications | get_app_conf(application_id, 'cloud_fqdn') }}"
|
||||
nextcloud_cloud_directory: '{{nextcloud_user_home_directory}}Clouds/{{nextcloud_cloud_fqdn}}/{{users.client.username}}/'
|
||||
nextcloud_cloud_directory: '{{nextcloud_user_home_directory}}Clouds/{{nextcloud_cloud_fqdn}}/{{ users[desktop_username].username }}/'
|
||||
|
@@ -19,7 +19,7 @@
|
||||
- docker compose restart
|
||||
|
||||
- name: docker compose up
|
||||
shell: docker-compose -p {{ application_id }} up -d --force-recreate --remove-orphans --build
|
||||
shell: docker-compose -p {{ application_id | get_entity_name }} up -d --force-recreate --remove-orphans --build
|
||||
args:
|
||||
chdir: "{{ docker_compose.directories.instance }}"
|
||||
executable: /bin/bash
|
||||
|
@@ -18,7 +18,7 @@ networks:
|
||||
application_id in networks.local and
|
||||
networks.local[application_id].subnet is defined
|
||||
%}
|
||||
name: {{ application_id }}
|
||||
name: {{ application_id | get_entity_name }}
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
|
@@ -2,5 +2,6 @@
|
||||
{% if not applications | get_app_conf(application_id, 'features.central_database', False)%}
|
||||
volumes:
|
||||
database:
|
||||
name: {{ database_volume }}
|
||||
{% endif %}
|
||||
{{ "\n" }}
|
@@ -1,6 +1,7 @@
|
||||
{# This template needs to be included in docker-compose.yml which contain a database and additional volumes #}
|
||||
volumes:
|
||||
{% if not applications | get_app_conf(application_id, 'features.central_database', False)%}
|
||||
{% if not applications | get_app_conf(application_id, 'features.central_database', False) and applications | get_app_conf(application_id, 'docker.services.database.enabled', False) %}
|
||||
database:
|
||||
name: {{ database_volume }}
|
||||
{% endif %}
|
||||
{{ "\n" }}
|
@@ -1,2 +1,2 @@
|
||||
# @See https://chatgpt.com/share/67a23d18-fb54-800f-983c-d6d00752b0b4
|
||||
docker_compose: "{{ path_docker_compose_instances | get_docker_compose(application_id) }}"
|
||||
docker_compose: "{{ application_id | get_docker_paths(path_docker_compose_instances) }}"
|
2
roles/docker-core/Todo.md
Normal file
2
roles/docker-core/Todo.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Todos
|
||||
- Add cleanup service for docker system prune -f
|
@@ -24,9 +24,9 @@
|
||||
restart_policy: "{{ docker_restart_policy }}"
|
||||
healthcheck:
|
||||
test: "/usr/bin/mariadb --user=root --password={{ mariadb_root_pwd }} --execute \"SHOW DATABASES;\""
|
||||
interval: 3s
|
||||
timeout: 1s
|
||||
retries: 5
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 18
|
||||
when: run_once_docker_mariadb is not defined
|
||||
register: setup_mariadb_container_result
|
||||
|
||||
|
@@ -3,7 +3,7 @@
|
||||
{% set redis_version = applications | get_app_conf('svc-db-redis', 'docker.services.redis.version')%}
|
||||
redis:
|
||||
image: "{{ redis_image }}:{{ redis_version }}"
|
||||
container_name: {{ application_id }}-redis
|
||||
container_name: {{ application_id | get_entity_name }}-redis
|
||||
restart: {{ docker_restart_policy }}
|
||||
logging:
|
||||
driver: journald
|
||||
|
@@ -43,7 +43,7 @@ for filename in os.listdir(config_path):
|
||||
url = f"{{ web_protocol }}://{domain}"
|
||||
|
||||
redirected_domains = [domain['source'] for domain in {{ current_play_domain_mappings_redirect}}]
|
||||
redirected_domains.append("{{domains | get_domain('mailu')}}")
|
||||
redirected_domains.append("{{domains | get_domain('web-app-mailu')}}")
|
||||
|
||||
expected_statuses = get_expected_statuses(domain, parts, redirected_domains)
|
||||
|
||||
|
@@ -43,7 +43,7 @@ if __name__ == "__main__":
|
||||
|
||||
if os.path.isfile(docker_compose_file):
|
||||
print(f"Found docker-compose.yml in {dir_path}.")
|
||||
if dir_name == "mailu":
|
||||
if dir_name == "web-app-mailu":
|
||||
print(f"Directory {dir_name} detected. Performing hard restart...")
|
||||
hard_restart_docker_services(dir_path)
|
||||
else:
|
||||
|
@@ -5,7 +5,7 @@
|
||||
- arduino-docs
|
||||
state: present
|
||||
|
||||
- name: Adding user {{users.client.username}} to relevant arduino usergroups
|
||||
user: name={{users.client.username}}
|
||||
- name: Adding user {{ users[desktop_username].username }} to relevant arduino usergroups
|
||||
user: name={{ users[desktop_username].username }}
|
||||
groups=uucp lock
|
||||
append=yes
|
||||
|
@@ -10,7 +10,7 @@
|
||||
domain: "{{ item }}"
|
||||
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||
loop:
|
||||
- "{{ domains | get_domain('mailu') }}"
|
||||
- "{{ domains | get_domain('web-app-mailu') }}"
|
||||
- "{{ domain }}"
|
||||
|
||||
- name: "For '{{ application_id }}': configure {{domains | get_domain(application_id)}}.conf"
|
||||
|
@@ -59,7 +59,7 @@
|
||||
|
||||
- name: Wait for BigBlueButton
|
||||
wait_for:
|
||||
host: "{{ domains | get_domain('bigbluebutton') }}"
|
||||
host: "{{ domains | get_domain('web-app-bigbluebutton') }}"
|
||||
port: 80
|
||||
delay: 5
|
||||
timeout: 600
|
||||
@@ -68,11 +68,11 @@
|
||||
command:
|
||||
cmd: docker compose exec greenlight bundle exec rake admin:create
|
||||
chdir: "{{ docker_compose.directories.instance }}"
|
||||
when: applications.bigbluebutton.setup | bool
|
||||
when: bigbluebutton_setup
|
||||
ignore_errors: true
|
||||
register: admin_creation_result
|
||||
|
||||
- name: print admin user data
|
||||
debug:
|
||||
msg: "{{ admin_creation_result.stdout }}"
|
||||
when: applications.bigbluebutton.setup | bool
|
||||
when: bigbluebutton_setup
|
@@ -1,15 +1,22 @@
|
||||
application_id: "bigbluebutton"
|
||||
bbb_repository_directory: "{{ docker_compose.directories.services }}"
|
||||
docker_compose_file_origine: "{{ docker_compose.directories.services }}docker-compose.yml"
|
||||
docker_compose_file_final: "{{ docker_compose.directories.instance }}docker-compose.yml"
|
||||
application_id: "web-app-bigbluebutton"
|
||||
bbb_repository_directory: "{{ docker_compose.directories.services }}"
|
||||
docker_compose_file_origine: "{{ docker_compose.directories.services }}docker-compose.yml"
|
||||
docker_compose_file_final: "{{ docker_compose.directories.instance }}docker-compose.yml"
|
||||
|
||||
# Database configuration
|
||||
database_type: "postgres"
|
||||
database_password: "{{ applications.bigbluebutton.credentials.postgresql_secret }}"
|
||||
database_type: "postgres"
|
||||
database_password: "{{ applications | get_app_conf(application_id, 'credentials.postgresql_secret') }}"
|
||||
|
||||
domain: "{{ domains | get_domain(application_id) }}"
|
||||
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||
bbb_env_file_link: "{{ docker_compose.directories.instance }}.env"
|
||||
bbb_env_file_origine: "{{ bbb_repository_directory }}.env"
|
||||
domain: "{{ domains | get_domain(application_id) }}"
|
||||
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||
bbb_env_file_link: "{{ docker_compose.directories.instance }}.env"
|
||||
bbb_env_file_origine: "{{ bbb_repository_directory }}.env"
|
||||
|
||||
docker_compose_skipp_file_creation: true # Skipp creation of docker-compose.yml file
|
||||
docker_compose_skipp_file_creation: true # Skipp creation of docker-compose.yml file
|
||||
|
||||
# Setup
|
||||
bigbluebutton_setup: "{{ applications | get_app_conf(application_id, 'setup') }}"
|
||||
|
||||
# Credentials
|
||||
bigbluebutton_shared_secret: "{{ applications | get_app_conf(application_id, 'credentials.shared_secret') }}"
|
||||
bigbluebutton_api_suffix: "{{ applications | get_app_conf(application_id, 'api_suffix') }}"
|
@@ -33,5 +33,5 @@ docker:
|
||||
image: "espocrm/espocrm"
|
||||
version: "latest"
|
||||
name: "espocrm"
|
||||
volumes:
|
||||
data: espocrm_data
|
||||
volumes:
|
||||
data: espocrm_data
|
32
roles/web-app-espocrm/tasks/database.yml
Normal file
32
roles/web-app-espocrm/tasks/database.yml
Normal file
@@ -0,0 +1,32 @@
|
||||
- name: Check if config.php exists in EspoCRM
|
||||
command: docker exec --user root {{ espocrm_name }} test -f {{ espocrm_config_file }}
|
||||
register: config_file_exists
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Patch EspoCRM config.php with updated DB credentials
|
||||
when: config_file_exists.rc == 0
|
||||
block:
|
||||
- name: Update DB host
|
||||
command: >
|
||||
docker exec --user root {{ espocrm_name }}
|
||||
sed -i "s/'host' => .*/'host' => '{{ database_host }}',/" {{ espocrm_config_file }}
|
||||
notify: docker compose up
|
||||
|
||||
- name: Update DB name
|
||||
command: >
|
||||
docker exec --user root {{ espocrm_name }}
|
||||
sed -i "s/'dbname' => .*/'dbname' => '{{ database_name }}',/" {{ espocrm_config_file }}
|
||||
notify: docker compose up
|
||||
|
||||
- name: Update DB user
|
||||
command: >
|
||||
docker exec --user root {{ espocrm_name }}
|
||||
sed -i "s/'user' => .*/'user' => '{{ database_username }}',/" {{ espocrm_config_file }}
|
||||
notify: docker compose up
|
||||
|
||||
- name: Update DB password
|
||||
command: >
|
||||
docker exec --user root {{ espocrm_name }}
|
||||
sed -i "s/'password' => .*/'password' => '{{ database_password }}',/" {{ espocrm_config_file }}
|
||||
notify: docker compose up
|
@@ -3,6 +3,13 @@
|
||||
include_role:
|
||||
name: cmp-db-docker-proxy
|
||||
|
||||
- name: Update database credentials
|
||||
include_tasks: database.yml
|
||||
|
||||
- name: Flush handlers to make DB available before password reset
|
||||
meta: flush_handlers
|
||||
when: docker_compose_flush_handlers | bool
|
||||
|
||||
- name: Set OIDC scopes in EspoCRM config (inside web container)
|
||||
ansible.builtin.shell: |
|
||||
docker compose exec -T web php -r '
|
||||
|
@@ -8,4 +8,5 @@ docker_compose_flush_handlers: true
|
||||
espocrm_version: "{{ applications | get_app_conf(application_id, 'docker.services.espocrm.version', True) }}"
|
||||
espocrm_image: "{{ applications | get_app_conf(application_id, 'docker.services.espocrm.image', True) }}"
|
||||
espocrm_name: "{{ applications | get_app_conf(application_id, 'docker.services.espocrm.name', True) }}"
|
||||
espocrm_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}"
|
||||
espocrm_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}"
|
||||
espocrm_config_file: "/var/www/html/data/config-internal.php"
|
2
roles/web-app-mailu/Todo.md
Normal file
2
roles/web-app-mailu/Todo.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Todos
|
||||
- Implement hard restart into Backup for mailu
|
@@ -1,4 +1,3 @@
|
||||
version: "2024.06" # Docker Image Version
|
||||
oidc:
|
||||
email_by_username: true # If true, then the mail is set by the username. If wrong then the OIDC user email is used
|
||||
enable_user_creation: true # Users will be created if not existing
|
||||
@@ -30,4 +29,7 @@ docker:
|
||||
redis:
|
||||
enabled: true
|
||||
database:
|
||||
enabled: true
|
||||
enabled: true
|
||||
mailu:
|
||||
version: "2024.06" # Docker Image Version
|
||||
name: mailu
|
@@ -19,7 +19,7 @@
|
||||
mailu_compose_dir: "{{ docker_compose.directories.instance }}"
|
||||
mailu_domain: "{{ primary_domain }}"
|
||||
mailu_api_base_url: "http://127.0.0.1:8080/api/v1"
|
||||
mailu_global_api_token: "{{ applications.mailu.credentials.api_token }}"
|
||||
mailu_global_api_token: "{{ applications | get_app_conf(application_id, 'credentials.api_token') }}"
|
||||
mailu_action: >-
|
||||
{{
|
||||
(
|
||||
|
@@ -2,13 +2,15 @@
|
||||
|
||||
# Core services
|
||||
resolver:
|
||||
image: {{docker_source}}/unbound:{{applications.mailu.version}}
|
||||
image: {{docker_source}}/unbound:{{ mailu_version }}
|
||||
container_name: {{mailu_name}}_resolver
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
ipv4_address: {{networks.local.mailu.dns}}
|
||||
ipv4_address: {{networks.local['web-app-mailu'].dns}}
|
||||
|
||||
front:
|
||||
image: {{docker_source}}/nginx:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_front
|
||||
image: {{docker_source}}/nginx:{{ mailu_version }}
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
ports:
|
||||
- "127.0.0.1:{{ports.localhost.http[application_id]}}:80"
|
||||
@@ -30,10 +32,11 @@
|
||||
webmail:
|
||||
radicale:
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
|
||||
admin:
|
||||
image: {{docker_source}}/admin:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_admin
|
||||
image: {{docker_source}}/admin:{{ mailu_version }}
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
volumes:
|
||||
- "admin_data:/data"
|
||||
@@ -44,11 +47,12 @@
|
||||
front:
|
||||
condition: service_started
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
|
||||
imap:
|
||||
image: {{docker_source}}/dovecot:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_imap
|
||||
image: {{docker_source}}/dovecot:{{ mailu_version }}
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
volumes:
|
||||
- "dovecot_mail:/mail"
|
||||
@@ -57,11 +61,12 @@
|
||||
- front
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
|
||||
smtp:
|
||||
image: {{docker_source}}/postfix:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_smtp
|
||||
image: {{docker_source}}/postfix:{{ mailu_version }}
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
volumes:
|
||||
- "{{docker_compose.directories.volumes}}overrides:/overrides:ro"
|
||||
@@ -70,22 +75,24 @@
|
||||
- front
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
|
||||
oletools:
|
||||
image: {{docker_source}}/oletools:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_oletools
|
||||
image: {{docker_source}}/oletools:{{ mailu_version }}
|
||||
hostname: oletools
|
||||
restart: {{docker_restart_policy}}
|
||||
depends_on:
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
noinet:
|
||||
|
||||
antispam:
|
||||
image: {{docker_source}}/rspamd:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_antispam
|
||||
image: {{docker_source}}/rspamd:{{ mailu_version }}
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
volumes:
|
||||
- "filter:/var/lib/rspamd"
|
||||
@@ -97,13 +104,14 @@
|
||||
- antivirus
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
noinet:
|
||||
|
||||
|
||||
# Optional services
|
||||
antivirus:
|
||||
container_name: {{mailu_name}}_antivirus
|
||||
image: clamav/clamav-debian:latest
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
volumes:
|
||||
@@ -111,23 +119,25 @@
|
||||
depends_on:
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
|
||||
webdav:
|
||||
image: {{docker_source}}/radicale:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_webdav
|
||||
image: {{docker_source}}/radicale:{{ mailu_version }}
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
volumes:
|
||||
- "webdav_data:/data"
|
||||
depends_on:
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
radicale:
|
||||
|
||||
fetchmail:
|
||||
image: {{docker_source}}/fetchmail:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_fetchmail
|
||||
image: {{docker_source}}/fetchmail:{{ mailu_version }}
|
||||
volumes:
|
||||
- "admin_data:/data"
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
@@ -137,11 +147,12 @@
|
||||
- imap
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
|
||||
webmail:
|
||||
image: {{docker_source}}/webmail:{{applications.mailu.version}}
|
||||
container_name: {{mailu_name}}_webmail
|
||||
image: {{docker_source}}/webmail:{{ mailu_version }}
|
||||
{% include 'roles/docker-container/templates/base.yml.j2' %}
|
||||
volumes:
|
||||
- "webmail_data:/data"
|
||||
@@ -151,19 +162,27 @@
|
||||
- front
|
||||
- resolver
|
||||
dns:
|
||||
- {{networks.local.mailu.dns}}
|
||||
- {{networks.local['web-app-mailu'].dns}}
|
||||
{% include 'roles/docker-container/templates/networks.yml.j2' %}
|
||||
webmail:
|
||||
|
||||
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
|
||||
smtp_queue:
|
||||
name: {{ mailu_smtp_queue }}
|
||||
admin_data:
|
||||
name: {{ mailu_admin_data }}
|
||||
webdav_data:
|
||||
name: {{ mailu_webdav_data }}
|
||||
webmail_data:
|
||||
name: {{ mailu_webmail_data }}
|
||||
filter:
|
||||
name: {{ mailu_filter }}
|
||||
dkim:
|
||||
name: {{ mailu_dkim }}
|
||||
dovecot_mail:
|
||||
name: {{ mailu_dovecot_mail }}
|
||||
redis:
|
||||
name: {{ mailu_redis }}
|
||||
|
||||
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
|
||||
radicale:
|
||||
|
@@ -11,13 +11,13 @@
|
||||
LD_PRELOAD=/usr/lib/libhardened_malloc.so
|
||||
|
||||
# Set to a randomly generated 16 bytes string
|
||||
SECRET_KEY={{applications.mailu.credentials.secret_key}}
|
||||
SECRET_KEY={{applications | get_app_conf(application_id,'credentials.secret_key')}}
|
||||
|
||||
# Subnet of the docker network. This should not conflict with any networks to which your system is connected. (Internal and external!)
|
||||
SUBNET={{networks.local.mailu.subnet}}
|
||||
SUBNET={{networks.local['web-app-mailu'].subnet}}
|
||||
|
||||
# Main mail domain
|
||||
DOMAIN={{applications.mailu.domain}}
|
||||
DOMAIN={{ applications | get_app_conf(application_id,'domain') }}
|
||||
|
||||
# Hostnames for this server, separated with comas
|
||||
HOSTNAMES={{domains | get_domain(application_id)}}
|
||||
@@ -151,7 +151,7 @@ SQLALCHEMY_DATABASE_URI=mysql+mysqlconnector://{{database_username}}:{{database_
|
||||
API=true
|
||||
WEB_API=/api
|
||||
# Configures the authentication token. The minimum length is 3 characters. This token must be passed as request header to the API as authentication token. This is a mandatory setting for using the RESTful API.
|
||||
API_TOKEN={{applications.mailu.credentials.api_token}}
|
||||
API_TOKEN={{ applications | get_app_conf(application_id, 'credentials.api_token')}}
|
||||
|
||||
|
||||
# Activated https://mailu.io/master/configuration.html#advanced-settings
|
||||
|
@@ -1,7 +1,7 @@
|
||||
application_id: "mailu"
|
||||
application_id: "web-app-mailu"
|
||||
|
||||
# Database Configuration
|
||||
database_password: "{{applications.mailu.credentials.database_password}}"
|
||||
database_password: "{{ applications | get_app_conf(application_id, ' credentials.database_password') }}"
|
||||
database_type: "mariadb"
|
||||
|
||||
cert_mount_directory: "{{docker_compose.directories.volumes}}certs/"
|
||||
@@ -12,4 +12,14 @@ docker_source: "{{ 'ghcr.io/heviat' if applications | get_app_conf(a
|
||||
|
||||
domain: "{{ domains | get_domain(application_id) }}"
|
||||
http_port: "{{ ports.localhost.http[application_id] }}"
|
||||
proxy_extra_configuration: "client_max_body_size 31M;"
|
||||
proxy_extra_configuration: "client_max_body_size 31M;"
|
||||
mailu_version: "{{ applications | get_app_conf(application_id, 'docker.services.mailu.version', True) }}"
|
||||
mailu_name: "{{ applications | get_app_conf(application_id, 'docker.services.mailu.name', True) }}"
|
||||
mailu_smtp_queue: "mailu_smtp_queue"
|
||||
mailu_admin_data: "mailu_admin_data"
|
||||
mailu_webdav_data: "mailu_webdav_data"
|
||||
mailu_webmail_data: "mailu_webmail_data"
|
||||
mailu_filter: "mailu_filter"
|
||||
mailu_dkim: "mailu_dkim"
|
||||
mailu_dovecot_mail: "mailu_dovecot_mail"
|
||||
mailu_redis: "mailu_redis"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
single_user_mode: false # Set true for initial setup
|
||||
setup: false # Set true in inventory file to execute the setup and initializing procedures, don't know if this is still necessary @todo test it
|
||||
setup: true # Set true in inventory file to execute the setup and initializing procedures, don't know if this is still necessary @todo test it
|
||||
features:
|
||||
matomo: true
|
||||
css: true
|
||||
|
10
roles/web-app-mastodon/tasks/01_setup.yml
Normal file
10
roles/web-app-mastodon/tasks/01_setup.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Execute migration for '{{ application_id }}'"
|
||||
command:
|
||||
cmd: "docker-compose run --rm web bundle exec rails db:migrate"
|
||||
chdir: "{{docker_compose.directories.instance}}"
|
||||
|
||||
- name: "Include administrator routines for '{{ application_id }}'"
|
||||
include_tasks: 02_administrator.yml
|
@@ -16,15 +16,6 @@
|
||||
client_max_body_size: "80m"
|
||||
vhost_flavour: "ws_generic"
|
||||
|
||||
- name: flush docker service
|
||||
meta: flush_handlers
|
||||
when: mastodon_setup |bool
|
||||
|
||||
- name: setup routine for mastodon
|
||||
command:
|
||||
cmd: "docker-compose run --rm web bundle exec rails db:migrate"
|
||||
chdir: "{{docker_compose.directories.instance}}"
|
||||
when: mastodon_setup |bool
|
||||
|
||||
- name: "include create-administrator.yml for mastodon"
|
||||
include_tasks: create-administrator.yml
|
||||
- name: "start setup procedures for mastodon"
|
||||
include_tasks: 01_setup.yml
|
||||
when: mastodon_setup |bool
|
@@ -2,7 +2,7 @@
|
||||
include_role:
|
||||
name: cmp-db-docker-proxy
|
||||
|
||||
- name: "Update database credentials"
|
||||
- name: "Patch Matomo config.ini.php with updated DB credentials"
|
||||
include_tasks: database.yml
|
||||
|
||||
- name: flush docker service
|
||||
|
@@ -1,25 +1,16 @@
|
||||
- name: Backup config.ini.php before patching
|
||||
- name: Update DB host
|
||||
command: >
|
||||
docker cp {{ matomo_name }}:{{ matomo_config }} {{ matomo_backup_file }}
|
||||
|
||||
- name: Patch Matomo config.ini.php with updated DB credentials
|
||||
block:
|
||||
- name: Update DB host
|
||||
command: >
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^host *=.*/host = {{ database_host }}/" {{ matomo_config }}
|
||||
|
||||
- name: Update DB name
|
||||
command: >
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^dbname *=.*/dbname = {{ database_name }}/" {{ matomo_config }}
|
||||
|
||||
- name: Update DB user
|
||||
command: >
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^username *=.*/username = {{ database_username }}/" {{ matomo_config }}
|
||||
|
||||
- name: Update DB password
|
||||
command: >
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^password *=.*/password = {{ database_password }}/" {{ matomo_config }}
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^host *=.*/host = {{ database_host }}/" {{ matomo_config }}
|
||||
- name: Update DB name
|
||||
command: >
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^dbname *=.*/dbname = {{ database_name }}/" {{ matomo_config }}
|
||||
- name: Update DB user
|
||||
command: >
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^username *=.*/username = {{ database_username }}/" {{ matomo_config }}
|
||||
- name: Update DB password
|
||||
command: >
|
||||
docker exec --user root {{ matomo_name }}
|
||||
sed -i "s/^password *=.*/password = {{ database_password }}/" {{ matomo_config }}
|
||||
|
@@ -8,7 +8,6 @@ matomo_version: "{{ applications | get_app_conf(application_id, 'docker.se
|
||||
matomo_image: "{{ applications | get_app_conf(application_id, 'docker.services.matomo.image', True) }}"
|
||||
matomo_name: "{{ applications | get_app_conf(application_id, 'docker.services.matomo.name', True) }}"
|
||||
matomo_data: "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}"
|
||||
matomo_backup_file: "{{ docker_compose.directories.instance }}/config.ini.php.bak"
|
||||
matomo_config: "/var/www/html/config/config.ini.php"
|
||||
|
||||
# I don't know if this is still necessary
|
||||
|
@@ -13,10 +13,6 @@
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: Copy config.php from container to host
|
||||
command: >
|
||||
docker cp {{ moodle_container }}:{{ moodle_config }} {{ moodle_backup_file }}
|
||||
|
||||
- name: Check if config.php exists
|
||||
command: docker exec --user root {{ moodle_container }} test -f {{ moodle_config }}
|
||||
register: config_file_exists
|
||||
|
@@ -10,7 +10,6 @@ bitnami_user_group: "{{ bitnami_user }}:{{ bitnami_user }}"
|
||||
|
||||
docker_compose_flush_handlers: false # Wait for env update
|
||||
|
||||
moodle_backup_file: "{{ docker_compose.directories.instance }}/config.ini.php.bak"
|
||||
moodle_config: "/bitnami/moodle/config.php"
|
||||
moodle_version: "{{ applications | get_app_conf(application_id, 'docker.services.moodle.version', True) }}"
|
||||
moodle_image: "{{ applications | get_app_conf(application_id, 'docker.services.moodle.image', True) }}"
|
||||
|
@@ -10,9 +10,12 @@ csp:
|
||||
- "data:"
|
||||
domains:
|
||||
canonical:
|
||||
nextcloud: "cloud.{{ primary_domain }}"
|
||||
- "cloud.{{ primary_domain }}"
|
||||
# nextcloud: "cloud.{{ primary_domain }}"
|
||||
# talk: "talk.{{ primary_domain }}" @todo needs to be activated
|
||||
docker:
|
||||
volumes:
|
||||
data: nextcloud_data
|
||||
services:
|
||||
redis:
|
||||
enabled: true
|
||||
@@ -21,7 +24,7 @@ docker:
|
||||
nextcloud:
|
||||
name: "nextcloud"
|
||||
image: "nextcloud"
|
||||
version: "latest-fpm-alpine"
|
||||
version: "production-fpm-alpine"
|
||||
backup:
|
||||
no_stop_required: true
|
||||
proxy:
|
||||
@@ -73,7 +76,7 @@ plugins:
|
||||
enabled: true
|
||||
bbb:
|
||||
# Nextcloud BigBlueButton integration: enables video conferencing using BigBlueButton (https://apps.nextcloud.com/apps/bbb)
|
||||
enabled: "{{ 'bigbluebutton' in group_names | lower }}"
|
||||
enabled: "{{ 'web-app-bigbluebutton' in group_names | lower }}"
|
||||
#- bookmarks
|
||||
# # Nextcloud Bookmarks: manage and share your bookmarks easily (https://apps.nextcloud.com/apps/bookmarks)
|
||||
# enabled: false
|
||||
|
@@ -3,16 +3,27 @@
|
||||
- name: Add dynamic config merging from Jinja template
|
||||
template:
|
||||
src: include.php.j2
|
||||
dest: "{{nextcloud_host_include_instructions_file}}"
|
||||
dest: "{{ nextcloud_host_include_instructions_file }}"
|
||||
notify: docker compose restart
|
||||
|
||||
- name: Flush handlers so Nextcloud container is restarted and ready
|
||||
meta: flush_handlers
|
||||
|
||||
- name: "Wait until Nextcloud is reachable on port {{ports.localhost.http[application_id]}}"
|
||||
wait_for:
|
||||
host: 127.0.0.1
|
||||
port: "{{ports.localhost.http[application_id]}}"
|
||||
timeout: 120
|
||||
delay: 2
|
||||
state: started
|
||||
|
||||
- name: Copy include instructions to the container
|
||||
command: >
|
||||
docker cp {{ nextcloud_host_include_instructions_file }} {{ nextcloud_name }}:{{nextcloud_docker_include_instructions_file}}
|
||||
docker cp {{ nextcloud_host_include_instructions_file }} {{ nextcloud_container }}:{{ nextcloud_docker_include_instructions_file }}
|
||||
|
||||
- name: Append generated config to config.php only if not present
|
||||
command: >
|
||||
docker exec -u {{nextcloud_docker_user}} {{ nextcloud_name }} sh -c "
|
||||
docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_container }} sh -c "
|
||||
grep -q '{{ nextcloud_docker_config_additives_directory }}' {{ nextcloud_docker_config_file }} ||
|
||||
cat {{nextcloud_docker_include_instructions_file}} >> {{ nextcloud_docker_config_file }}"
|
||||
notify: docker compose restart
|
||||
cat {{ nextcloud_docker_include_instructions_file }} >> {{ nextcloud_docker_config_file }}"
|
||||
notify: docker compose restart
|
||||
|
@@ -65,7 +65,7 @@
|
||||
|
||||
- name: Ensure Nextcloud administrator is in the 'admin' group
|
||||
command: >
|
||||
docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_name }}
|
||||
docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_container }}
|
||||
php occ group:adduser admin {{ nextcloud_administrator_username }}
|
||||
register: add_admin_to_group
|
||||
changed_when: "'Added user' in add_admin_to_group.stdout"
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
application:
|
||||
image: "{{ nextcloud_image }}:{{ nextcloud_version }}"
|
||||
container_name: {{ nextcloud_name }}
|
||||
container_name: {{ nextcloud_container }}
|
||||
volumes:
|
||||
- data:{{nextcloud_docker_work_directory}}
|
||||
- {{nextcloud_host_config_additives_directory}}:{{nextcloud_docker_config_additives_directory}}:ro
|
||||
@@ -70,6 +70,7 @@
|
||||
|
||||
{% include 'roles/docker-compose/templates/volumes.yml.j2' %}
|
||||
data:
|
||||
name: {{ nextcloud_volume }}
|
||||
redis:
|
||||
|
||||
{% include 'roles/docker-compose/templates/networks.yml.j2' %}
|
||||
|
@@ -20,16 +20,18 @@ nextcloud_control_node_plugin_tasks_directory: "{{role_path}}/tasks/plugins/"
|
||||
# Host
|
||||
|
||||
## Host Paths
|
||||
nextcloud_host_config_additives_directory: "{{docker_compose.directories.volumes}}cymais/" # This folder is the path to which the additive configurations will be copied
|
||||
nextcloud_host_include_instructions_file: "{{docker_compose.directories.volumes}}includes.php" # Path to the instruction file on the host. Responsible for loading the additional configurations
|
||||
nextcloud_host_config_additives_directory: "{{ docker_compose.directories.volumes }}cymais/" # This folder is the path to which the additive configurations will be copied
|
||||
nextcloud_host_include_instructions_file: "{{ docker_compose.directories.volumes }}includes.php" # Path to the instruction file on the host. Responsible for loading the additional configurations
|
||||
|
||||
nextcloud_domains: "{{ domains[application_id].nextcloud }}"
|
||||
nextcloud_domains: "{{ domains | get_domain(application_id) }}" # This is wrong and should be optimized @todo implement support for multiple domains
|
||||
|
||||
# Docker
|
||||
|
||||
nextcloud_volume: "{{ applications | get_app_conf(application_id, 'docker.volumes.data', True) }}"
|
||||
|
||||
nextcloud_version: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.version', True) }}"
|
||||
nextcloud_image: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.image', True) }}"
|
||||
nextcloud_name: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.name', True) }}"
|
||||
nextcloud_container: "{{ applications | get_app_conf(application_id, 'docker.services.nextcloud.name', True) }}"
|
||||
|
||||
nextcloud_proxy_name: "{{ applications | get_app_conf(application_id, 'docker.services.proxy.name', True) }}"
|
||||
nextcloud_proxy_image: "{{ applications | get_app_conf(application_id, 'docker.services.proxy.image', True) }}"
|
||||
@@ -58,5 +60,5 @@ nextcloud_docker_config_additives_directory: "{{nextcloud_docker_config_direc
|
||||
nextcloud_docker_include_instructions_file: "/tmp/includes.php" # Path to the temporary file which will be included to the config.php to load the additional configurations
|
||||
|
||||
## Execution
|
||||
nextcloud_docker_exec: "docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_name }}" # General execute composition
|
||||
nextcloud_docker_exec: "docker exec -u {{ nextcloud_docker_user }} {{ nextcloud_container }}" # General execute composition
|
||||
nextcloud_docker_exec_occ: "{{nextcloud_docker_exec}} {{ nextcloud_docker_work_directory }}occ" # Execute docker occ command
|
@@ -1,7 +1,7 @@
|
||||
plugin_configuration:
|
||||
- appid: "bbb"
|
||||
configkey: "api.secret"
|
||||
configvalue: "{{ applications.bigbluebutton.credentials.shared_secret }}"
|
||||
configvalue: "{{ bigbluebutton_shared_secret }}"
|
||||
- appid: "bbb"
|
||||
configkey: "api.url"
|
||||
configvalue: "{{ domains | get_url('bigbluebutton', web_protocol) }}{{applications.bigbluebutton.api_suffix}}"
|
||||
configvalue: "{{ domains | get_url('web-app-bigbluebutton', web_protocol) }}{{ bigbluebutton_api_suffix }}"
|
@@ -1,6 +1,6 @@
|
||||
- name: "Transfering oauth2-proxy-keycloak.cfg.j2 to {{(path_docker_compose_instances | get_docker_compose(application_id)).directories.volumes}}"
|
||||
- name: "Transfering oauth2-proxy-keycloak.cfg.j2 to {{( path_docker_compose_instances | get_docker_paths(application_id)).directories.volumes }}"
|
||||
template:
|
||||
src: "{{ playbook_dir }}/roles/web-app-oauth2-proxy/templates/oauth2-proxy-keycloak.cfg.j2"
|
||||
dest: "{{(path_docker_compose_instances | get_docker_compose(application_id)).directories.volumes}}{{applications | get_app_conf('oauth2-proxy','configuration_file')}}"
|
||||
dest: "{{( path_docker_compose_instances | get_docker_paths(application_id)).directories.volumes }}{{applications | get_app_conf('oauth2-proxy','configuration_file')}}"
|
||||
notify:
|
||||
- docker compose up
|
@@ -3,7 +3,7 @@ followus:
|
||||
description: Follow us to stay up to recieve the newest CyMaIS updates
|
||||
icon:
|
||||
class: fas fa-newspaper
|
||||
{% if ["mastodon", "bluesky"] | any_in(group_names) %}
|
||||
{% if ["web-app-mastodon", "web-app-bluesky"] | any_in(group_names) %}
|
||||
children:
|
||||
{% if service_provider.contact.mastodon is defined and service_provider.contact.mastodon != "" %}
|
||||
- name: Mastodon
|
||||
|
@@ -6,7 +6,7 @@
|
||||
- name: "Include role srv-proxy-6-6-domain for {{ application_id }}"
|
||||
include_role:
|
||||
name: srv-proxy-6-6-domain
|
||||
loop: "{{ applications | get_app_conf(application_id, 'domain', True)s.canonical }}"
|
||||
loop: "{{ applications | get_app_conf(application_id, 'domains.canonical', True) }}"
|
||||
loop_control:
|
||||
loop_var: domain
|
||||
vars:
|
||||
|
@@ -1,6 +1,16 @@
|
||||
application_id: {{ application_id }} # ID of the application, should be the name of the role folder
|
||||
database_type: 0 # Database type [postgres, mariadb]
|
||||
docker_compose_flush_handlers: true # When this is set to true an auto-flush after the docker-compose.yml, and env deploy is triggered, otherwise you have todo it manual.
|
||||
# This file is just used for internal configurations by the developer.
|
||||
# All configuration possibilities are available in the config/main.yml file.
|
||||
|
||||
# General
|
||||
application_id: {{ application_id }} # ID of the application, should be the name of the role folder
|
||||
|
||||
# Database
|
||||
database_type: 0 # Database type [postgres, mariadb]
|
||||
|
||||
# Docker
|
||||
docker_compose_flush_handlers: true # When this is set to true an auto-flush after the docker-compose.yml, and env deploy is triggered, otherwise you have todo it manual.
|
||||
docker_compose_skipp_file_creation: false # Skipp creation of docker-compose.yml file
|
||||
|
||||
|
||||
# The following variable mapping is optional, but imt makes it easier to read the code.
|
||||
# I recommend, to use this mappings, but you can skipp it and access the config entries direct via get_app_conf
|
||||
|
@@ -0,0 +1,36 @@
|
||||
import os
|
||||
import unittest
|
||||
import yaml
|
||||
import warnings
|
||||
|
||||
# Dynamically determine the path to the roles directory
|
||||
ROLES_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'roles'))
|
||||
|
||||
class TestApplicationIdDeprecation(unittest.TestCase):
|
||||
def test_application_id_matches_role_name(self):
|
||||
"""
|
||||
Deprecation: application_id in vars/main.yml must match the role name.
|
||||
"""
|
||||
for role in os.listdir(ROLES_DIR):
|
||||
role_path = os.path.join(ROLES_DIR, role)
|
||||
vars_main_yml = os.path.join(role_path, 'vars', 'main.yml')
|
||||
if not os.path.isfile(vars_main_yml):
|
||||
continue
|
||||
with open(vars_main_yml, 'r', encoding='utf-8') as f:
|
||||
try:
|
||||
data = yaml.safe_load(f)
|
||||
except Exception as e:
|
||||
self.fail(f"Could not parse {vars_main_yml}: {e}")
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
app_id = data.get('application_id')
|
||||
if app_id is not None and app_id != role:
|
||||
warnings.warn(
|
||||
f"[DEPRECATION WARNING] application_id '{app_id}' in {vars_main_yml} "
|
||||
f"does not match its role directory '{role}'.\n"
|
||||
f"Please update 'application_id' to match the role name for future compatibility.",
|
||||
DeprecationWarning
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -11,7 +11,7 @@ sys.path.insert(0, dir_path)
|
||||
|
||||
# Import functions and classes to test
|
||||
from cli.create.credentials import ask_for_confirmation, main
|
||||
from utils.handler.vault import VaultHandler, VaultScalar
|
||||
from module_utils.handler.vault import VaultHandler, VaultScalar
|
||||
import subprocess
|
||||
import tempfile
|
||||
import yaml
|
||||
|
@@ -14,9 +14,9 @@ sys.path.insert(
|
||||
),
|
||||
)
|
||||
|
||||
from utils.handler.yaml import YamlHandler
|
||||
from utils.handler.vault import VaultHandler, VaultScalar
|
||||
from utils.manager.inventory import InventoryManager
|
||||
from module_utils.handler.yaml import YamlHandler
|
||||
from module_utils.handler.vault import VaultHandler, VaultScalar
|
||||
from module_utils.manager.inventory import InventoryManager
|
||||
|
||||
|
||||
class TestInventoryManager(unittest.TestCase):
|
||||
|
118
tests/unit/filter_plugins/test_get_all_invokable_apps.py
Normal file
118
tests/unit/filter_plugins/test_get_all_invokable_apps.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import yaml
|
||||
import unittest
|
||||
|
||||
from filter_plugins.get_all_invokable_apps import get_all_invokable_apps
|
||||
|
||||
class TestGetAllInvokableApps(unittest.TestCase):
|
||||
def setUp(self):
|
||||
"""Create a temporary roles/ directory with categories.yml and some example roles."""
|
||||
self.test_dir = tempfile.mkdtemp(prefix="invokable_apps_test_")
|
||||
self.roles_dir = os.path.join(self.test_dir, "roles")
|
||||
os.makedirs(self.roles_dir, exist_ok=True)
|
||||
self.categories_file = os.path.join(self.roles_dir, "categories.yml")
|
||||
|
||||
# Write a categories.yml with nested invokable/non-invokable paths
|
||||
categories = {
|
||||
"roles": {
|
||||
"web": {
|
||||
"title": "Web",
|
||||
"invokable": False,
|
||||
"app": {
|
||||
"title": "Applications",
|
||||
"invokable": True
|
||||
},
|
||||
"svc": {
|
||||
"title": "Services",
|
||||
"invokable": False
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"title": "Update",
|
||||
"invokable": True
|
||||
},
|
||||
"util": {
|
||||
"title": "module_utils",
|
||||
"invokable": False,
|
||||
"desk": {
|
||||
"title": "Desktop module_utils",
|
||||
"invokable": True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
with open(self.categories_file, 'w') as f:
|
||||
yaml.safe_dump(categories, f)
|
||||
|
||||
# Create roles: some should match invokable paths, some shouldn't
|
||||
roles = [
|
||||
('web-app-nextcloud', 'web-app-nextcloud'),
|
||||
('web-app-matomo', 'matomo-app'), # application_id differs
|
||||
('web-svc-nginx', None), # should NOT match any invokable path
|
||||
('update', None), # exact match to invokable path
|
||||
('util-desk-custom', None) # matches util-desk
|
||||
]
|
||||
for rolename, appid in roles:
|
||||
role_dir = os.path.join(self.roles_dir, rolename)
|
||||
os.makedirs(os.path.join(role_dir, 'vars'), exist_ok=True)
|
||||
vars_path = os.path.join(role_dir, 'vars', 'main.yml')
|
||||
data = {}
|
||||
if appid:
|
||||
data['application_id'] = appid
|
||||
with open(vars_path, 'w') as f:
|
||||
yaml.safe_dump(data, f)
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up the temporary test directory after each test."""
|
||||
shutil.rmtree(self.test_dir)
|
||||
|
||||
def test_get_all_invokable_apps(self):
|
||||
"""Should return only applications whose role paths match invokable paths."""
|
||||
result = get_all_invokable_apps(
|
||||
categories_file=self.categories_file,
|
||||
roles_dir=self.roles_dir
|
||||
)
|
||||
expected = sorted([
|
||||
'web-app-nextcloud', # application_id from role
|
||||
'matomo-app', # application_id from role
|
||||
'update', # role directory name
|
||||
'util-desk-custom' # role directory name
|
||||
])
|
||||
self.assertEqual(sorted(result), expected)
|
||||
|
||||
def test_empty_when_no_invokable(self):
|
||||
"""Should return an empty list if there are no invokable paths in categories.yml."""
|
||||
with open(self.categories_file, 'w') as f:
|
||||
yaml.safe_dump({"roles": {"foo": {"invokable": False}}}, f)
|
||||
result = get_all_invokable_apps(
|
||||
categories_file=self.categories_file,
|
||||
roles_dir=self.roles_dir
|
||||
)
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_empty_when_no_roles(self):
|
||||
"""Should return an empty list if there are no roles, but categories.yml exists."""
|
||||
shutil.rmtree(self.roles_dir)
|
||||
os.makedirs(self.roles_dir, exist_ok=True)
|
||||
# Recreate categories.yml after removing roles_dir
|
||||
with open(self.categories_file, 'w') as f:
|
||||
yaml.safe_dump({"roles": {"web": {"app": {"invokable": True}}}}, f)
|
||||
result = get_all_invokable_apps(
|
||||
categories_file=self.categories_file,
|
||||
roles_dir=self.roles_dir
|
||||
)
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_error_when_no_categories_file(self):
|
||||
"""Should raise FileNotFoundError if categories.yml is missing."""
|
||||
os.remove(self.categories_file)
|
||||
with self.assertRaises(FileNotFoundError):
|
||||
get_all_invokable_apps(
|
||||
categories_file=self.categories_file,
|
||||
roles_dir=self.roles_dir
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
93
tests/unit/filter_plugins/test_get_entity_name.py
Normal file
93
tests/unit/filter_plugins/test_get_entity_name.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import unittest
|
||||
import tempfile
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
class TestGetEntityNameFilter(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Create a temporary directory for roles and categories.yml
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.roles_dir = os.path.join(self.temp_dir, 'roles')
|
||||
os.makedirs(self.roles_dir)
|
||||
self.categories_file = os.path.join(self.roles_dir, 'categories.yml')
|
||||
|
||||
# Minimal categories.yml for tests
|
||||
categories = {
|
||||
'roles': {
|
||||
'web': {
|
||||
'app': {
|
||||
'title': "Applications",
|
||||
'invokable': True
|
||||
},
|
||||
'svc': {
|
||||
'title': "Services",
|
||||
'invokable': True
|
||||
}
|
||||
},
|
||||
'util': {
|
||||
'desk': {
|
||||
'dev': {
|
||||
'title': "Dev Utilities",
|
||||
'invokable': True
|
||||
}
|
||||
}
|
||||
},
|
||||
'sys': {
|
||||
'bkp': {
|
||||
'title': "Backup",
|
||||
'invokable': True
|
||||
},
|
||||
'hlth': {
|
||||
'title': "Health",
|
||||
'invokable': True
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
with open(self.categories_file, 'w', encoding='utf-8') as f:
|
||||
yaml.safe_dump(categories, f, default_flow_style=False)
|
||||
|
||||
# Patch working directory so plugin finds the test categories.yml
|
||||
self._cwd = os.getcwd()
|
||||
os.chdir(self.temp_dir)
|
||||
|
||||
# Make sure filter_plugins directory is on sys.path
|
||||
plugin_path = os.path.join(self._cwd, "filter_plugins")
|
||||
if plugin_path not in sys.path and os.path.isdir(plugin_path):
|
||||
sys.path.insert(0, plugin_path)
|
||||
# Import plugin fresh each time
|
||||
global get_entity_name
|
||||
from filter_plugins.get_entity_name import get_entity_name
|
||||
|
||||
self.get_entity_name = get_entity_name
|
||||
|
||||
def tearDown(self):
|
||||
os.chdir(self._cwd)
|
||||
shutil.rmtree(self.temp_dir)
|
||||
|
||||
def test_entity_name_web_app(self):
|
||||
self.assertEqual(self.get_entity_name("web-app-snipe-it"), "snipe-it")
|
||||
self.assertEqual(self.get_entity_name("web-app-nextcloud"), "nextcloud")
|
||||
self.assertEqual(self.get_entity_name("web-svc-file"), "file")
|
||||
|
||||
def test_entity_name_util_desk_dev(self):
|
||||
self.assertEqual(self.get_entity_name("util-desk-dev-arduino"), "arduino")
|
||||
self.assertEqual(self.get_entity_name("util-desk-dev-shell"), "shell")
|
||||
|
||||
def test_entity_name_sys_bkp(self):
|
||||
self.assertEqual(self.get_entity_name("sys-bkp-directory-validator"), "directory-validator")
|
||||
|
||||
def test_entity_name_sys_hlth(self):
|
||||
self.assertEqual(self.get_entity_name("sys-hlth-btrfs"), "btrfs")
|
||||
|
||||
def test_no_category_match(self):
|
||||
# Unknown category, should return input
|
||||
self.assertEqual(self.get_entity_name("foobar-role"), "foobar-role")
|
||||
|
||||
def test_exact_category_match(self):
|
||||
self.assertEqual(self.get_entity_name("web-app"), "")
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
@@ -1,26 +0,0 @@
|
||||
import unittest
|
||||
from filter_plugins.get_public_id import FilterModule
|
||||
|
||||
class TestGetPublicId(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.filter = FilterModule().filters()['get_public_id']
|
||||
|
||||
def test_extract_public_id(self):
|
||||
self.assertEqual(self.filter("svc-user-abc123"), "abc123")
|
||||
self.assertEqual(self.filter("something-simple-xyz"), "xyz")
|
||||
self.assertEqual(self.filter("a-b-c-d-e"), "e")
|
||||
|
||||
def test_no_hyphen(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.filter("nohyphenhere")
|
||||
|
||||
def test_non_string_input(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.filter(12345)
|
||||
|
||||
def test_empty_string(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.filter("")
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
@@ -1,5 +1,5 @@
|
||||
import unittest
|
||||
from utils.dict_renderer import DictRenderer
|
||||
from module_utils.dict_renderer import DictRenderer
|
||||
|
||||
class TestDictRenderer(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
@@ -1,9 +1,9 @@
|
||||
# File: tests/unit/utils/test_valid_deploy_id.py
|
||||
# File: tests/unit/module_utils/test_valid_deploy_id.py
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
import yaml
|
||||
from utils.valid_deploy_id import ValidDeployId
|
||||
from module_utils.valid_deploy_id import ValidDeployId
|
||||
|
||||
class TestValidDeployId(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
Reference in New Issue
Block a user