218 lines
5.5 KiB
Python
Executable file
218 lines
5.5 KiB
Python
Executable file
#!/bin/env python3
|
|
# Copyright: (c) 2025, Luca Bilke <luca@bil.ke>
|
|
# MIT License (see LICENSE)
|
|
# ruff: noqa: T201
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import textwrap
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, cast
|
|
|
|
import yaml
|
|
from ansible_collections.snailed.ez_docker.plugins.module_utils import (
|
|
label,
|
|
service,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from types import ModuleType
|
|
|
|
GALAXY_METADATA_PATH = Path("../galaxy.yml")
|
|
|
|
|
|
def get_modules(parent_module: ModuleType) -> list[tuple[str, ModuleType]]:
|
|
return [
|
|
(name, getattr(parent_module, name)) for name in parent_module.__all__ if name != "common"
|
|
]
|
|
|
|
|
|
def settings_format(text: str) -> str:
|
|
return "\n".join(
|
|
line for line in text.splitlines() if not line.strip().startswith(("default:", "required:"))
|
|
)
|
|
|
|
|
|
def get_module_docs(
|
|
module: ModuleType,
|
|
) -> str:
|
|
ret: str = ""
|
|
if docs := getattr(module, "DOCUMENTATION", None):
|
|
ret = docs
|
|
|
|
if ret:
|
|
return ret
|
|
|
|
msg = f"Module {module.__name__} has no documentation"
|
|
raise ValueError(msg)
|
|
|
|
|
|
def get_metadata() -> str:
|
|
with GALAXY_METADATA_PATH.open("r") as f:
|
|
metadata = yaml.safe_load(f)
|
|
|
|
docs = """
|
|
module: compose
|
|
version_added: 1.0.0
|
|
short_description: Simplify docker-compose deployments.
|
|
description: Easily create docker-compose files using a single module
|
|
seealso:
|
|
- name: Compose file reference
|
|
description: Complete reference of the compose file spec.
|
|
link: https://docs.docker.com/reference/compose-file/
|
|
attributes:
|
|
check_mode:
|
|
support: full
|
|
diff_mode:
|
|
support: full
|
|
"""
|
|
|
|
if author := metadata.get("author"):
|
|
if isinstance(author, str):
|
|
docs += f"author: {author}\n"
|
|
|
|
elif isinstance(author, list):
|
|
docs += "author:\n"
|
|
for a in cast(list[str], author):
|
|
docs += f" - {a}\n"
|
|
|
|
return docs
|
|
|
|
|
|
def get_settings_docs() -> str:
|
|
service_default_definitions = """
|
|
service_default_definitions:
|
|
description:
|
|
- Default definitions for each service
|
|
type: dict
|
|
suboptions:
|
|
"""
|
|
|
|
service_default_args = """
|
|
service_default_args:
|
|
description:
|
|
- Default arguments for each service helper.
|
|
type: dict
|
|
suboptions:
|
|
"""
|
|
|
|
label_default_args = """
|
|
label_default_args:
|
|
description:
|
|
- Default arguments for each label helper.
|
|
type: dict
|
|
suboptions:
|
|
"""
|
|
|
|
for name, module in get_modules(service):
|
|
if module.__name__.endswith("common"):
|
|
continue
|
|
|
|
service_default_definitions += f"""
|
|
{name}:
|
|
description:
|
|
- Default definitions for {name} services.
|
|
type: dict
|
|
"""
|
|
|
|
service_default_args += textwrap.indent(settings_format(get_module_docs(module)), " " * 8)
|
|
|
|
if module.__name__.endswith("custom"):
|
|
for _, label_module in get_modules(label):
|
|
if label_module.__name__.endswith("common"):
|
|
continue
|
|
|
|
service_default_args += textwrap.indent(
|
|
settings_format(get_module_docs(label_module)), " " * 24
|
|
)
|
|
else:
|
|
service_default_args += textwrap.indent(
|
|
settings_format(service.common.BASE_DOCUMENTATION), " " * 16
|
|
)
|
|
|
|
for _, module in get_modules(label):
|
|
if module.__name__.endswith("common"):
|
|
continue
|
|
|
|
label_default_args += textwrap.indent(settings_format(get_module_docs(module)), " " * 8)
|
|
|
|
return f"""
|
|
settings:
|
|
description:
|
|
- Settings/Defaults for the module.
|
|
type: dict
|
|
suboptions:
|
|
external_networks:
|
|
description:
|
|
- Networks to mark as external.
|
|
type: list
|
|
elements: str
|
|
external_volumes:
|
|
description:
|
|
- Volumes to mark as external.
|
|
type: list
|
|
elements: str
|
|
default_definition:
|
|
description:
|
|
- Default definition for all containers.
|
|
- Overwritten by per-service defaults.
|
|
type: dict
|
|
{textwrap.indent(service_default_definitions, " " * 8)}
|
|
{textwrap.indent(service_default_args, " " * 8)}
|
|
{textwrap.indent(label_default_args, " " * 8)}
|
|
"""
|
|
|
|
|
|
def get_service_docs() -> str:
|
|
service_docs = """
|
|
services:
|
|
description:
|
|
- Services to create in the project.
|
|
type: dict
|
|
suboptions:
|
|
"""
|
|
for _, module in get_modules(service):
|
|
service_docs += f"""
|
|
{textwrap.indent(get_module_docs(module), " " * 8)}
|
|
"""
|
|
|
|
if module.__name__.endswith("custom"):
|
|
for _, label_module in get_modules(label):
|
|
if label_module.__name__ == "common":
|
|
continue
|
|
|
|
service_docs += textwrap.indent(
|
|
settings_format(get_module_docs(label_module)), " " * 24
|
|
)
|
|
else:
|
|
service_docs += textwrap.indent(service.common.BASE_DOCUMENTATION, " " * 16)
|
|
|
|
return service_docs
|
|
|
|
|
|
ret = f"""
|
|
---
|
|
{get_metadata()}
|
|
options:
|
|
name:
|
|
description:
|
|
- Name of the compose project to create or modify.
|
|
aliases: [project]
|
|
type: str
|
|
project_dir:
|
|
description:
|
|
- Path to store project directory under.
|
|
type: path
|
|
{textwrap.indent(get_settings_docs(), " " * 4)}
|
|
{textwrap.indent(get_service_docs(), " " * 4)}
|
|
"""
|
|
|
|
ret = "\n".join(line for line in ret.splitlines() if line.strip())
|
|
|
|
print(ret)
|
|
try:
|
|
yaml.safe_load(ret)
|
|
except yaml.YAMLError as e:
|
|
print("YAML is invalid!\n", file=sys.stderr)
|
|
print(e, file=sys.stderr)
|