working copy
This commit is contained in:
parent
be661e09de
commit
009fb3304b
20 changed files with 322 additions and 175 deletions
|
@ -11,15 +11,15 @@ module: slurp
|
|||
version_added: 1.0.0
|
||||
short_description: Lookup version of the slurp module
|
||||
description:
|
||||
- Instead of running the slurp module against local host, I wrote this module
|
||||
- Instead of running the slurp module against local host, I wrote this module
|
||||
author:
|
||||
- "Luca Bilke (@ssnailed)"
|
||||
- "Luca Bilke (@ssnailed)"
|
||||
options:
|
||||
_terms:
|
||||
_terms:
|
||||
description: path(s) of files to read
|
||||
required: True
|
||||
seealso:
|
||||
- ref: playbook_task_paths
|
||||
- ref: playbook_task_paths
|
||||
description: Search paths used for relative files.
|
||||
"""
|
||||
|
||||
|
@ -55,7 +55,10 @@ class LookupModule(LookupBase):
|
|||
|
||||
try:
|
||||
lookupfile = self.find_file_in_search_path(
|
||||
variables, "files", term, ignore_missing=True,
|
||||
variables,
|
||||
"files",
|
||||
term,
|
||||
ignore_missing=True,
|
||||
)
|
||||
display.vvvv(f"File lookup using {lookupfile} as file")
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
import copy
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
||||
|
||||
import yaml
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.models import Result, State
|
||||
|
@ -15,13 +15,6 @@ if TYPE_CHECKING:
|
|||
from ansible.module_utils.basic import AnsibleModule # pyright: ignore[reportMissingTypeStubs]
|
||||
|
||||
|
||||
def clean_none(obj: dict[str, Any]) -> dict[str, Any]:
|
||||
obj = copy.deepcopy(obj)
|
||||
for k in copy.deepcopy(obj):
|
||||
if obj.get(k, "sentinel") is None:
|
||||
del obj[k]
|
||||
return obj
|
||||
|
||||
def recursive_update(
|
||||
default: dict[str, Any],
|
||||
update: dict[str, Any],
|
||||
|
@ -31,11 +24,11 @@ def recursive_update(
|
|||
for k, v in update.items():
|
||||
if isinstance(v, dict):
|
||||
v = cast(dict[str, Any], v)
|
||||
default[k] = recursive_update(default.get(k) or {}, v)
|
||||
default[k] = recursive_update(default.get(k, {}), v)
|
||||
|
||||
elif isinstance(v, list):
|
||||
v = cast(list[Any], v)
|
||||
new = cast(list[Any], (default.get(k) or []))
|
||||
new = cast(list[Any], (default.get(k, [])))
|
||||
new.extend(v)
|
||||
default[k] = new
|
||||
|
||||
|
@ -47,7 +40,23 @@ def recursive_update(
|
|||
|
||||
def get_state(module: AnsibleModule) -> State:
|
||||
"""Create a new state object, loading the compose file into "before" if it exists."""
|
||||
compose_filepath = f"{module.params['project_dir']}/{module.params['name']}/docker-compose.yml"
|
||||
T = TypeVar("T")
|
||||
|
||||
# def clean_params[T](obj: T) -> T:
|
||||
def clean_params(obj: T) -> T:
|
||||
# NOTE: ansible sets None as default for arguments, which is a pain in
|
||||
# the ass for the purposes of this module
|
||||
obj = copy.deepcopy(obj)
|
||||
|
||||
if isinstance(obj, dict):
|
||||
return {key: clean_params(value) for key, value in obj.items() if value is not None} # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType, reportReturnType]
|
||||
|
||||
if isinstance(obj, list):
|
||||
return [clean_params(item) for item in obj if item is not None] # pyright: ignore[reportUnknownArgumentType, reportUnknownVariableType, reportReturnType]
|
||||
|
||||
return obj
|
||||
|
||||
compose_filepath = f"{module.params['project_dir']}/docker-compose.yml"
|
||||
|
||||
try:
|
||||
with Path(compose_filepath).open("r") as fp:
|
||||
|
@ -57,6 +66,7 @@ def get_state(module: AnsibleModule) -> State:
|
|||
|
||||
return State(
|
||||
module=module,
|
||||
params=clean_params(module.params),
|
||||
result=Result(),
|
||||
compose_filepath=compose_filepath,
|
||||
before=before,
|
||||
|
@ -70,32 +80,31 @@ def get_state(module: AnsibleModule) -> State:
|
|||
|
||||
|
||||
def update_project(state: State) -> State:
|
||||
"""Ensure that networks/volumes that exist in services also exist in the project."""
|
||||
"""Properly configure top level volume/network elements."""
|
||||
project = copy.deepcopy(state.after)
|
||||
services: dict[str, Any] = project.get("services", {})
|
||||
settings: dict[str, Any] = state.params.get("settings", {})
|
||||
|
||||
project_services: dict[str, Any] = project.get("services", {})
|
||||
project_networks: dict[str, Any] = project.get("networks", {})
|
||||
project_volumes: dict[str, Any] = project.get("volumes", {})
|
||||
volume_sources = [
|
||||
vol["source"] for service in services.values() for vol in service.get("volumes", [])
|
||||
]
|
||||
|
||||
for project_service in [x for x in project_services.values() if x]:
|
||||
if service_volumes := project_service.get("volumes"):
|
||||
service_volume_names = [x["source"] for x in service_volumes]
|
||||
project_volumes.update(
|
||||
{
|
||||
service_volume_name: None
|
||||
for service_volume_name in service_volume_names
|
||||
if service_volume_name not in project_volumes
|
||||
},
|
||||
)
|
||||
network_names = [
|
||||
network for service in services.values() for network in service.get("networks", {})
|
||||
]
|
||||
|
||||
if service_network_names := project_service.get("networks", {}).keys():
|
||||
project_networks.update(
|
||||
{
|
||||
service_network_name: None
|
||||
for service_network_name in service_network_names
|
||||
if service_network_name not in project_networks
|
||||
},
|
||||
)
|
||||
duplicate_sources = [source for source in volume_sources if volume_sources.count(source) > 1]
|
||||
|
||||
project["volumes"] = {source: None for source in duplicate_sources} | {
|
||||
source: {"external": True}
|
||||
for source in volume_sources
|
||||
if source in settings.get("external_volumes", [])
|
||||
}
|
||||
|
||||
project["networks"] = {
|
||||
network: ({"external": True} if network in settings.get("external_networks", []) else None)
|
||||
for network in network_names
|
||||
}
|
||||
|
||||
return replace(state, after=project)
|
||||
|
||||
|
@ -120,13 +129,12 @@ def set_result(state: State) -> State: # noqa: C901
|
|||
return False
|
||||
|
||||
if isinstance(before, list):
|
||||
before = sorted(cast(list[Any], before))
|
||||
after = sorted(after)
|
||||
before = cast(list[Any], before)
|
||||
|
||||
if len(before) != len(after):
|
||||
return True
|
||||
|
||||
for index in before.enumerate():
|
||||
for index, _ in enumerate(before):
|
||||
if _changed(before[index], after[index]):
|
||||
return True
|
||||
|
||||
|
|
|
@ -5,10 +5,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING, Any, Callable
|
||||
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.common import (
|
||||
clean_none,
|
||||
recursive_update,
|
||||
)
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.common import recursive_update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.models import State
|
||||
|
@ -25,10 +22,10 @@ def apply_update(
|
|||
|
||||
|
||||
def get_default_args(state: State, helper_name: str) -> dict[str, Any]:
|
||||
settings: dict[str, Any] = state.module.params.get("settings") or {}
|
||||
label_default_args: dict[str, Any] = settings.get("label_default_args") or {}
|
||||
default_args: dict[str, Any] = label_default_args.get(helper_name) or {}
|
||||
return clean_none(default_args)
|
||||
settings: dict[str, Any] = state.params.get("settings", {})
|
||||
label_default_args: dict[str, Any] = settings.get("label_default_args", {})
|
||||
default_args: dict[str, Any] = label_default_args.get(helper_name, {})
|
||||
return default_args
|
||||
|
||||
|
||||
def run_helper(
|
||||
|
|
|
@ -9,13 +9,13 @@ if TYPE_CHECKING:
|
|||
from ansible_collections.snailed.ez_docker.plugins.module_utils.models import State
|
||||
|
||||
EXTRA_ARGS = {
|
||||
"stop": {"type": "bool", "default": True},
|
||||
"stop": {"type": "bool", "required": True},
|
||||
}
|
||||
|
||||
|
||||
def helper(state: State, _service_name: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
stop: bool = params["stop"]
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
|
||||
update: dict[str, Any] = {}
|
||||
|
||||
|
|
|
@ -10,19 +10,19 @@ if TYPE_CHECKING:
|
|||
|
||||
EXTRA_ARGS = {
|
||||
"proxy_type": {"type": "str", "default": "http"},
|
||||
"name": {"type": "string"},
|
||||
"name": {"type": "str"},
|
||||
"middleware": {"type": "str", "required": True},
|
||||
"settings": {"type": "list", "required": True},
|
||||
"settings": {"type": "dict", "required": True},
|
||||
}
|
||||
|
||||
|
||||
def helper(state: State, service_name: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
middleware: str = params["middleware"]
|
||||
settings: dict[str, str] = params["settings"]
|
||||
proxy_type: str = params["proxy_type"]
|
||||
name: str = (
|
||||
params.get("name") or f"{project_name}_{service_name}_{proxy_type}_{middleware.lower()}"
|
||||
params.get("name", f"{project_name}_{service_name}_{proxy_type}_{middleware.lower()}")
|
||||
)
|
||||
|
||||
prefix = f"traefik.{proxy_type}.middlewares.{name}"
|
||||
|
|
|
@ -20,24 +20,24 @@ EXTRA_ARGS = {
|
|||
|
||||
|
||||
def helper(state: State, service_name: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
rule: str = params["rule"]
|
||||
traefik_service: str | None = params.get("service")
|
||||
entrypoints: list[str] | None = params.get("entrypoints")
|
||||
middlewares: list[str] | None = params.get("middlewares")
|
||||
certresolver: str | None = params.get("certresolver")
|
||||
proxy_type: str = params["proxy_type"]
|
||||
name: str = params.get("name") or f"{project_name}_{service_name}_{proxy_type}"
|
||||
name: str = params.get("name", f"{project_name}_{service_name}_{proxy_type}")
|
||||
|
||||
prefix = f"traefik.{proxy_type}.routers.{name}"
|
||||
|
||||
labels = {
|
||||
"traefik.enable": True,
|
||||
f"traefik.{prefix}.rule": rule,
|
||||
f"{prefix}.rule": rule,
|
||||
}
|
||||
|
||||
if certresolver:
|
||||
labels[f"traefik.{prefix}.tls.certresolver"] = certresolver
|
||||
labels[f"{prefix}.tls.certresolver"] = certresolver
|
||||
|
||||
if entrypoints:
|
||||
labels[f"{prefix}.entrypoints"] = ",".join(entrypoints)
|
||||
|
|
|
@ -10,16 +10,16 @@ if TYPE_CHECKING:
|
|||
|
||||
EXTRA_ARGS = {
|
||||
"proxy_type": {"type": "str", "default": "http"},
|
||||
"name": {"type": "string"},
|
||||
"name": {"type": "str"},
|
||||
"port": {"type": "int"},
|
||||
}
|
||||
|
||||
|
||||
def helper(state: State, service_name: str, params: dict[str, Any]) -> dict[str, Any]:
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
port: int | None = params.get("port")
|
||||
proxy_type: str = params["proxy_type"]
|
||||
name: str = params.get("name") or f"{project_name}_{service_name}_{proxy_type}"
|
||||
name: str = params.get("name", f"{project_name}_{service_name}_{proxy_type}")
|
||||
|
||||
prefix = f"traefik.{proxy_type}.services.{name}"
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ class Result:
|
|||
@dataclass(frozen=True)
|
||||
class State:
|
||||
module: AnsibleModule
|
||||
params: dict[str, Any]
|
||||
result: Result
|
||||
compose_filepath: str
|
||||
before: dict[str, Any]
|
||||
|
|
|
@ -7,10 +7,7 @@ import copy
|
|||
from dataclasses import replace
|
||||
from typing import TYPE_CHECKING, Any, Callable
|
||||
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.common import (
|
||||
clean_none,
|
||||
recursive_update,
|
||||
)
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.common import recursive_update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.models import State
|
||||
|
@ -22,7 +19,7 @@ BASE_ARGS: dict[str, Any] = {
|
|||
|
||||
|
||||
def get_base_definition(state: State, service_name: str) -> dict[str, Any]:
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
return {
|
||||
"container_name": f"{project_name}_{service_name}",
|
||||
"hostname": f"{project_name}_{service_name}",
|
||||
|
@ -38,19 +35,19 @@ def get_base_definition(state: State, service_name: str) -> dict[str, Any]:
|
|||
|
||||
|
||||
def get_default_definition(state: State, service_name: str) -> dict[str, Any]:
|
||||
settings: dict[str, Any] = state.module.params.get("settings") or {}
|
||||
default_definition: dict[str, Any] = settings.get("default_definition") or {}
|
||||
service_default_definitions: dict[str, Any] = settings.get("service_default_definitions") or {}
|
||||
service_default_definition: dict[str, Any] = service_default_definitions.get(service_name) or {}
|
||||
settings: dict[str, Any] = state.params.get("settings", {})
|
||||
default_definition: dict[str, Any] = settings.get("default_definition", {})
|
||||
service_default_definitions: dict[str, Any] = settings.get("service_default_definitions", {})
|
||||
service_default_definition: dict[str, Any] = service_default_definitions.get(service_name, {})
|
||||
|
||||
return default_definition | service_default_definition
|
||||
|
||||
|
||||
def get_default_args(state: State, helper_name: str) -> dict[str, Any]:
|
||||
settings: dict[str, Any] = state.module.params.get("settings") or {}
|
||||
service_default_args: dict[str, Any] = settings.get("service_default_args") or {}
|
||||
default_args: dict[str, Any] = service_default_args.get(helper_name) or {}
|
||||
return clean_none(default_args)
|
||||
settings: dict[str, Any] = state.params.get("settings", {})
|
||||
service_default_args: dict[str, Any] = settings.get("service_default_args", {})
|
||||
default_args: dict[str, Any] = service_default_args.get(helper_name, {})
|
||||
return default_args
|
||||
|
||||
|
||||
def apply_update(state: State, service_name: str, update: dict[str, Any]) -> State:
|
||||
|
@ -58,7 +55,7 @@ def apply_update(state: State, service_name: str, update: dict[str, Any]) -> Sta
|
|||
service = project["services"].get(service_name, {})
|
||||
service = recursive_update(service, update)
|
||||
|
||||
volumes: list[dict[str, Any]] = service.get("volumes") or []
|
||||
volumes: list[dict[str, Any]] = service.get("volumes", [])
|
||||
unique_volumes = list({vol["source"]: vol for vol in volumes if "target" in vol}.values())
|
||||
service["volumes"] = unique_volumes
|
||||
|
||||
|
|
|
@ -6,34 +6,24 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils import label, spec
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.common import clean_none
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.models import (
|
||||
State,
|
||||
)
|
||||
from ansible_collections.snailed.ez_docker.plugins.module_utils.models import State
|
||||
|
||||
FORCE_ARGS = {
|
||||
"name": {"type": "str", "required": True},
|
||||
"definition": {"type": "dict", "required": True},
|
||||
"internal_network": {"type": "bool", "default": False},
|
||||
"label_helpers": spec.label_argument_spec(),
|
||||
}
|
||||
|
||||
|
||||
def helper(state: State, params: dict[str, Any]) -> dict[str, Any]:
|
||||
internal_network: bool = params["internal_network"]
|
||||
|
||||
def helper(state: State, service_params: dict[str, Any]) -> dict[str, Any]:
|
||||
update: dict[str, Any] = {}
|
||||
|
||||
if internal_network:
|
||||
networks = update.get("networks", {})
|
||||
networks["internal"] = None
|
||||
update["networks"] = networks
|
||||
|
||||
for name, args in [(x, y) for x, y in params.get("label_helpers", {}).items() if y]:
|
||||
label_params = label.common.get_default_args(state, name) | clean_none(args)
|
||||
helper = getattr(label, name).helper
|
||||
update = label.common.run_helper(state, params["name"], update, label_params, helper)
|
||||
for name, labels_params in service_params.get("label_helpers", {}).items():
|
||||
for label_params in labels_params:
|
||||
params = label.common.get_default_args(state, name) | label_params
|
||||
helper = getattr(label, name).helper
|
||||
update |= label.common.run_helper(state, service_params["name"], update, params, helper)
|
||||
|
||||
return update
|
||||
|
|
|
@ -15,7 +15,7 @@ EXTRA_ARGS = {
|
|||
|
||||
|
||||
def helper(state: State, params: dict[str, Any]) -> dict[str, Any]:
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
archive: str | None = params.get("archive")
|
||||
backup_volumes: list[str] | None = params["backup_volumes"]
|
||||
service_name = params["name"]
|
||||
|
|
|
@ -19,7 +19,7 @@ EXTRA_ARGS = {
|
|||
|
||||
|
||||
def helper(state: State, params: dict[str, Any]) -> dict[str, Any]:
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
backup: bool = params["backup"]
|
||||
database: str = params["database"]
|
||||
username: str = params["username"]
|
||||
|
|
|
@ -18,7 +18,7 @@ EXTRA_ARGS = {
|
|||
|
||||
|
||||
def helper(state: State, params: dict[str, Any]) -> dict[str, Any]:
|
||||
project_name: str = state.module.params["name"]
|
||||
project_name: str = state.params["name"]
|
||||
backup: bool = params["backup"]
|
||||
database: str = params["database"]
|
||||
username: str = params["username"]
|
||||
|
|
|
@ -48,7 +48,8 @@ def label_argument_spec() -> dict[str, Any]:
|
|||
|
||||
for module_name, module in get_modules(label):
|
||||
label_args["options"][module_name] = {
|
||||
"type": "dict",
|
||||
"type": "list",
|
||||
"elements": "dict",
|
||||
"options": get_module_options(module, label.common.BASE_ARGS),
|
||||
}
|
||||
|
||||
|
@ -80,6 +81,8 @@ def settings_spec() -> dict[str, Any]:
|
|||
"service_default_args": {"type": "dict", "options": {}},
|
||||
"label_default_args": {"type": "dict", "options": {}},
|
||||
"service_default_definitions": {"type": "dict", "options": {}},
|
||||
"external_networks": {"type": "list", "elements": "str"},
|
||||
"external_volumes": {"type": "list", "elements": "str"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict
|
||||
|
||||
# TODO: break this down per module
|
||||
# TODO: generate this by reassembling
|
||||
# TODO: add note about not setting container_name or host_name in defaults
|
||||
|
@ -43,6 +41,16 @@ options:
|
|||
- 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.
|
||||
|
@ -150,23 +158,11 @@ options:
|
|||
description:
|
||||
- Name of the service.
|
||||
type: str
|
||||
image:
|
||||
description:
|
||||
- Image to use for service.
|
||||
type: str
|
||||
defaults:
|
||||
description:
|
||||
- Service definition to be overwritten.
|
||||
type: dict
|
||||
overwrite:
|
||||
definition:
|
||||
description:
|
||||
- Service definition to overwrite with.
|
||||
type: dict
|
||||
internal_network:
|
||||
description:
|
||||
- If true, add internal network to service.
|
||||
type: bool
|
||||
default: false
|
||||
required: true
|
||||
label_helpers:
|
||||
description:
|
||||
- Label helper configurations.
|
||||
|
@ -175,7 +171,8 @@ options:
|
|||
docker_volume_backupper:
|
||||
description:
|
||||
- Docker Volume Backupper label helper configuration.
|
||||
type: dict
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
stop:
|
||||
description:
|
||||
|
@ -185,7 +182,8 @@ options:
|
|||
traefik_middleware:
|
||||
description:
|
||||
- Traefik Middleware label helper configuration.
|
||||
type: dict
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
proxy_type:
|
||||
description:
|
||||
|
@ -210,7 +208,8 @@ options:
|
|||
traefik_router:
|
||||
description:
|
||||
- Traefik Router label helper configuration.
|
||||
type: dict
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
proxy_type:
|
||||
description:
|
||||
|
@ -248,7 +247,8 @@ options:
|
|||
traefik_service:
|
||||
description:
|
||||
- Traefik Service label helper configuration.
|
||||
type: dict
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
proxy_type:
|
||||
description:
|
||||
|
@ -454,6 +454,7 @@ options:
|
|||
type: dict
|
||||
"""
|
||||
|
||||
from dataclasses import asdict
|
||||
from importlib.util import find_spec
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule # pyright: ignore[reportMissingTypeStubs]
|
||||
|
@ -490,11 +491,9 @@ def main() -> None:
|
|||
except Exception as e: # noqa: BLE001
|
||||
module.fail_json(f"Error while reading existing compose file: {e}") # pyright: ignore[reportUnknownMemberType]
|
||||
|
||||
for name, services_params in [(x, y) for x, y in module.params["services"].items() if y]:
|
||||
for name, services_params in state.params.get("services", {}).items():
|
||||
for index, service_params in enumerate(services_params):
|
||||
params = service.common.get_default_args(state, name) | common.clean_none(
|
||||
service_params
|
||||
)
|
||||
params = service.common.get_default_args(state, name) | service_params
|
||||
params["_index"] = index
|
||||
helper = getattr(service, name).helper
|
||||
state = service.common.run_helper(state, params, helper)
|
||||
|
@ -514,5 +513,6 @@ def main() -> None:
|
|||
|
||||
module.exit_json(**ret) # pyright: ignore[reportUnknownMemberType]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -16,10 +16,12 @@ ez_docker_shared_volumes: []
|
|||
# - name: "shared_volume"
|
||||
|
||||
# Directory to put compose projects into
|
||||
ez_docker_project_dir: "/var/lib/ez_compose"
|
||||
ez_docker_project_dir: "/var/lib/ez_docker"
|
||||
|
||||
# Settings for the module
|
||||
ez_docker_settings:
|
||||
external_networks: "{{ ez_docker_shared_networks | map(attribute='name') }}"
|
||||
external_volumes: "{{ ez_docker_shared_volumes | map(attribute='name') }}"
|
||||
default_definition: {}
|
||||
# environment:
|
||||
# TZ: "Europe/Berlin"
|
||||
|
@ -36,14 +38,6 @@ ez_docker_settings:
|
|||
service_default_args: {}
|
||||
# docker_volume_backupper:
|
||||
# archive: "/tank/docker-backups"
|
||||
#
|
||||
ez_cp_controller_files_root: "{{ playbook_dir }}/files/ez_cp/{{ inventory_hostname }}"
|
||||
ez_cp_controller_templates_root: "{{ playbook_dir }}/templates/ez_cp/{{ inventory_hostname }}"
|
||||
|
||||
# To copy into a non-running container we need the UID/GID for the files
|
||||
# TODO: implement this
|
||||
ez_cp_service_owners: {}
|
||||
# project_name:
|
||||
# service_name:
|
||||
# uid: 1000
|
||||
# gid: 1000
|
||||
ez_docker_controller_files_root: "{{ playbook_dir }}/files/ez_docker/{{ inventory_hostname }}"
|
||||
ez_docker_controller_templates_root: "{{ playbook_dir }}/templates/ez_docker/{{ inventory_hostname }}"
|
|
@ -12,7 +12,6 @@
|
|||
)
|
||||
)
|
||||
}}
|
||||
when: not ez_docker_projects
|
||||
|
||||
- name: Login in to registries
|
||||
community.docker.docker_login: "{{ item }}" # noqa: args[module]
|
||||
|
@ -32,6 +31,8 @@
|
|||
|
||||
- name: Import per-project tasks
|
||||
ansible.builtin.include_tasks: project.yml
|
||||
when: >
|
||||
[(project_definition.key | split('_') | last), "all"] | intersect(ansible_run_tags) | length > 0
|
||||
loop: "{{ ez_docker_projects }}"
|
||||
loop_control:
|
||||
loop_var: project_definition
|
||||
|
|
|
@ -1,28 +1,43 @@
|
|||
---
|
||||
- name: "project | Write compose files: {{ project_definition.key }}"
|
||||
- name: "project | Set project vars: {{ project_definition.key | split('_') | last }}"
|
||||
ansible.builtin.set_fact:
|
||||
project_name: "{{ project_definition.key | split('_') | last }}"
|
||||
project_dir: "{{ ez_docker_project_dir }}/{{ project_definition.key | split('_') | last }}"
|
||||
|
||||
|
||||
- name: "project | Create project directory: {{ project_name }}"
|
||||
ansible.builtin.file:
|
||||
path: "{{ project_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: docker
|
||||
mode: "750"
|
||||
|
||||
- name: "project | Write compose files: {{ project_name }}"
|
||||
snailed.ez_docker.compose:
|
||||
name: "{{ project_definition.key | split('_') | last }}"
|
||||
name: "{{ project_name }}"
|
||||
services: "{{ project_definition.value }}"
|
||||
settings: "{{ ez_docker_settings }}"
|
||||
project_dir: "{{ ez_docker_project_dir }}"
|
||||
no_log: true
|
||||
project_dir: "{{ project_dir }}"
|
||||
# no_log: true
|
||||
register: compose
|
||||
|
||||
- name: "project | Start project: {{ project_definition.key }}"
|
||||
- name: "project | Start project: {{ project_name }}"
|
||||
community.docker.docker_compose_v2:
|
||||
pull: missing
|
||||
project_src: "{{ ez_docker_project_dir }}/{{ compose.after.name }}"
|
||||
project_src: "{{ project_dir }}"
|
||||
state: "present"
|
||||
|
||||
- name: "project | Import per-service tasks: {{ project_definition.key }}"
|
||||
- name: "project | Import per-service tasks: {{ project_name }}"
|
||||
ansible.builtin.include_tasks: service.yml
|
||||
loop: compose.diff.after.services | dict2items
|
||||
loop: "{{ compose.diff.after.services | dict2items }}"
|
||||
loop_control:
|
||||
loop_var: service
|
||||
no_log: true
|
||||
|
||||
- name: "project | Restart docker projects: {{ project_definition.key }}"
|
||||
- name: "project | Restart docker projects: {{ project_name }}"
|
||||
community.docker.docker_compose_v2:
|
||||
services: services_changed
|
||||
project_src: "{{ ez_docker_project_dir }}/{{ project_definition.key }}"
|
||||
project_src: "{{ project_dir }}"
|
||||
state: "restarted"
|
||||
when: services_changed
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
container: "{{ service.value.container_name }}"
|
||||
container_path: "/{{ item.path }}"
|
||||
content: >-
|
||||
{{ snailed.ez_docker.slurp(
|
||||
ez_cp_controller_files_root ~ "/" ~ project.key ~ "/" ~ service.key ~ "/" ~ item.path
|
||||
{{ lookup('snailed.ez_docker.slurp',
|
||||
ez_docker_controller_files_root ~ "/" ~ project_name ~ "/" ~ service.key ~ "/" ~ item.path
|
||||
) }}
|
||||
content_is_b64: true
|
||||
mode: "{{ item.mode | int(base=8) }}"
|
||||
when: item.state == "file"
|
||||
with_community.general.filetree:
|
||||
- "{{ ez_cp_controller_files_root }}/{{ project.key }}/{{ service.key }}"
|
||||
- "{{ ez_docker_controller_files_root }}/{{ project_name }}/{{ service.key }}"
|
||||
register: copy
|
||||
|
||||
- name: "service | Template files into container: {{ service.value.container_name }}"
|
||||
|
@ -19,14 +19,14 @@
|
|||
container: "{{ service.value.container_name }}"
|
||||
container_path: "/{{ item.path }}"
|
||||
content: >-
|
||||
{{ ansible.builtin.template(
|
||||
ez_cp_controller_templates_root ~ "/" ~ project.key ~ "/" ~ service.key ~ "/" ~ item.path,
|
||||
convert_data=false
|
||||
{{ lookup('ansible.builtin.template',
|
||||
ez_docker_controller_templates_root ~ "/" ~ project_name ~ "/" ~ service.key ~ "/" ~ item.path,
|
||||
'convert_data=false'
|
||||
) }}
|
||||
mode: "{{ item.mode | int(base=8) }}"
|
||||
when: item.state == "file"
|
||||
with_community.general.filetree:
|
||||
- "{{ ez_cp_controller_templates_root }}/{{ project.key }}/{{ service.key }}"
|
||||
- "{{ ez_docker_controller_templates_root }}/{{ project_name }}/{{ service.key }}"
|
||||
register: template
|
||||
|
||||
- name: "service | Register file changes: {{ service.value.container_name }}"
|
||||
|
|
188
test.json
188
test.json
|
@ -2,66 +2,204 @@
|
|||
"ANSIBLE_MODULE_ARGS": {
|
||||
"_ansible_check_mode": true,
|
||||
"_ansible_diff": true,
|
||||
"name": "taskwarrior",
|
||||
"name": "traefik",
|
||||
"project_dir": "/var/lib/ez_compose",
|
||||
"services": {
|
||||
"custom": [
|
||||
{
|
||||
"definition": {
|
||||
"image": "ghcr.io/gothenburgbitfactory/taskchampion-sync-server:main@sha256:4798edada4b264cdcc82f1c8ea2389cdd5cde02926f74b2361005438056f5729",
|
||||
"networks": {
|
||||
"proxy": null
|
||||
"image": "traefik:v3.3.1",
|
||||
"labels": {
|
||||
"homepage.group": "Services",
|
||||
"homepage.href": "https://traefik.snaile.de/",
|
||||
"homepage.icon": "https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons@master/png/traefik.png",
|
||||
"homepage.name": "Traefik",
|
||||
"homepage.widget.password": "redacted",
|
||||
"homepage.widget.type": "traefik",
|
||||
"homepage.widget.url": "https://traefik.snaile.de",
|
||||
"homepage.widget.username": "luca"
|
||||
},
|
||||
"networks": {
|
||||
"proxy": {}
|
||||
},
|
||||
"ports": [
|
||||
{
|
||||
"app_protocol": "smtp",
|
||||
"mode": "host",
|
||||
"name": "smtp",
|
||||
"protocol": "tcp",
|
||||
"published": 25,
|
||||
"target": 25
|
||||
},
|
||||
{
|
||||
"app_protocol": "http",
|
||||
"mode": "host",
|
||||
"name": "web",
|
||||
"protocol": "tcp",
|
||||
"published": 80,
|
||||
"target": 80
|
||||
},
|
||||
{
|
||||
"app_protocol": "https",
|
||||
"mode": "host",
|
||||
"name": "web-secure",
|
||||
"protocol": "tcp",
|
||||
"published": 443,
|
||||
"target": 443
|
||||
},
|
||||
{
|
||||
"app_protocol": "smtps",
|
||||
"mode": "host",
|
||||
"name": "smtp-ssl",
|
||||
"protocol": "tcp",
|
||||
"published": 465,
|
||||
"target": 465
|
||||
},
|
||||
{
|
||||
"app_protocol": "imaps",
|
||||
"mode": "host",
|
||||
"name": "imap-ssl",
|
||||
"protocol": "tcp",
|
||||
"published": 993,
|
||||
"target": 993
|
||||
},
|
||||
{
|
||||
"app_protocol": "ssh",
|
||||
"mode": "host",
|
||||
"name": "git-ssh",
|
||||
"protocol": "tcp",
|
||||
"published": 2222,
|
||||
"target": 2222
|
||||
},
|
||||
{
|
||||
"app_protocol": "managesieve",
|
||||
"mode": "host",
|
||||
"name": "sieve",
|
||||
"protocol": "tcp",
|
||||
"published": 4190,
|
||||
"target": 4190
|
||||
},
|
||||
{
|
||||
"mode": "host",
|
||||
"name": "matrix-federation",
|
||||
"protocol": "tcp",
|
||||
"published": 8448,
|
||||
"target": 8448
|
||||
}
|
||||
],
|
||||
"volumes": [
|
||||
{
|
||||
"source": "sync_data",
|
||||
"target": "/var/lib/taskchampion-sync-server",
|
||||
"type": "volume"
|
||||
"source": "/var/log/traefik",
|
||||
"target": "/var/log/traefik",
|
||||
"type": "bind"
|
||||
},
|
||||
{
|
||||
"source": "/etc/traefik",
|
||||
"target": "/etc/traefik",
|
||||
"type": "bind"
|
||||
},
|
||||
{
|
||||
"read_only": true,
|
||||
"source": "/etc/localtime",
|
||||
"target": "/etc/localtime",
|
||||
"type": "bind"
|
||||
},
|
||||
{
|
||||
"read_only": true,
|
||||
"source": "/var/run/docker.sock",
|
||||
"target": "/var/run/docker.sock",
|
||||
"type": "bind"
|
||||
}
|
||||
]
|
||||
},
|
||||
"label_helpers": {
|
||||
"docker_volume_backupper": {},
|
||||
"traefik_router": {
|
||||
"rule": "Host(`taskwarrior-sync.snailed.de`)"
|
||||
}
|
||||
"docker_volume_backupper": null,
|
||||
"traefik_middleware": [
|
||||
{
|
||||
"middleware": "basicAuth",
|
||||
"name": "traefik_basicauth",
|
||||
"proxy_type": "http",
|
||||
"settings": {
|
||||
"users": "redacted"
|
||||
}
|
||||
}
|
||||
],
|
||||
"traefik_router": [
|
||||
{
|
||||
"certresolver": null,
|
||||
"entrypoints": null,
|
||||
"middlewares": ["traefik_basicauth"],
|
||||
"name": null,
|
||||
"proxy_type": "http",
|
||||
"rule": "Host(`traefik.snaile.de`)",
|
||||
"service": "api@internal"
|
||||
}
|
||||
],
|
||||
"traefik_service": null
|
||||
},
|
||||
"name": "sync"
|
||||
"name": "traefik"
|
||||
}
|
||||
],
|
||||
"docker_volume_backupper": [
|
||||
{
|
||||
"backup_volumes": ["sync_data"]
|
||||
}
|
||||
]
|
||||
"docker_in_docker": null,
|
||||
"docker_socket_proxy": null,
|
||||
"docker_volume_backupper": null,
|
||||
"mariadb": null,
|
||||
"postgres": null,
|
||||
"redis": null
|
||||
},
|
||||
"settings": {
|
||||
"external_networks": ["proxy"],
|
||||
"default_definition": {
|
||||
"environment": {
|
||||
"TZ": "Europe/Berlin"
|
||||
}
|
||||
},
|
||||
"label_default_args": {
|
||||
"docker_volume_backupper": null,
|
||||
"traefik_middleware": null,
|
||||
"traefik_router": {
|
||||
"certresolver": "letsencrypt",
|
||||
"entrypoints": ["web-secure"]
|
||||
}
|
||||
"entrypoints": ["web-secure"],
|
||||
"middlewares": ["hsts"],
|
||||
"name": null,
|
||||
"proxy_type": null,
|
||||
"rule": null,
|
||||
"service": null
|
||||
},
|
||||
"traefik_service": null
|
||||
},
|
||||
"service_default_args": {
|
||||
"custom": null,
|
||||
"docker_in_docker": null,
|
||||
"docker_socket_proxy": null,
|
||||
"docker_volume_backupper": {
|
||||
"archive": "/tank/docker-backups"
|
||||
}
|
||||
"archive": "/tank/docker-backups",
|
||||
"backup_volumes": null
|
||||
},
|
||||
"mariadb": null,
|
||||
"postgres": null,
|
||||
"redis": null
|
||||
},
|
||||
"service_default_definitions": {
|
||||
"docker_volume_backupper": {
|
||||
"environment": {
|
||||
"BACKUP_CRON_EXPRESSION": "0 6 * * *",
|
||||
"BACKUP_RETENTION_DAYS": "7"
|
||||
"BACKUP_RETENTION_DAYS": "7",
|
||||
"EXEC_FORWARD_OUTPUT": true,
|
||||
"GPG_PASSPHRASE": "redacted",
|
||||
"GZIP_PARALLELISM": "2",
|
||||
"NOTIFICATION_URLS": "redacted",
|
||||
"SSH_HOST_NAME": "redacted",
|
||||
"SSH_PASSWORD": "redacted",
|
||||
"SSH_PORT": "23",
|
||||
"SSH_REMOTE_PATH": "/home/docker-backups",
|
||||
"SSH_USER": "redacted"
|
||||
},
|
||||
"image": "offen/docker-volume-backup:v2.43.0"
|
||||
"image": "offen/docker-volume-backup:v2.43.1"
|
||||
},
|
||||
"custom": null,
|
||||
"docker_in_docker": {
|
||||
"image": "docker:27.4.0-dind"
|
||||
"image": "docker:27.4.1-dind"
|
||||
},
|
||||
"mariadb": {
|
||||
"image": "mariadb:11.6.2"
|
||||
|
@ -70,7 +208,7 @@
|
|||
"image": "postgres:16.6-alpine"
|
||||
},
|
||||
"redis": {
|
||||
"image": "redis:7.4.1-alpine"
|
||||
"image": "redis:7.4.2-alpine"
|
||||
},
|
||||
"docker_socket_proxy": {
|
||||
"image": "tecnativa/docker-socket-proxy:0.3.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue