grouping acts so we can run multiple backups for a given service, for example
main / Explore-Gitea-Actions (push) Successful in 8s

This commit is contained in:
2026-07-29 14:02:40 +01:00
parent 867a5d6ab1
commit a68d7b1d59
4 changed files with 117 additions and 55 deletions
+39 -33
View File
@@ -10,38 +10,44 @@ global_context:
repoPath: "/home/esilva/Trash/test_borgmatic/destiny2" repoPath: "/home/esilva/Trash/test_borgmatic/destiny2"
passwordFile: "/home/esilva/Trash/test_borgmatic/pass_file" passwordFile: "/home/esilva/Trash/test_borgmatic/pass_file"
steps: acts:
- name: "find database container" - name: "DB Dump and Backup"
actions: steps:
find_container: "get_service_container_name" - name: "find database container"
context: actions:
service_name: "gitea-db" find_container: "get_service_container_name"
context:
service_name: "gitea-db"
- name: "dump database"
actions:
dump_database: "dump_container_pg_db"
context:
dump_path: "/tmp/db_dump.sql"
db_user: "gitea"
database: "gitea"
- name: "restic database backup"
actions:
dump_database: "backup_data_to_restic_repo"
context:
source_path: "/tmp/db_dump.sql"
tags: ["gitea", "db-dump"]
- name: "dump database" - name: "Backup gitea data"
actions: steps:
dump_database: "dump_container_pg_db" - name: "backup gitea mount points data"
context: actions:
dump_path: "/tmp/db_dump.sql" backup_data_to_restic_repo: "backup_data_to_restic_repo"
db_user: "gitea" context:
database: "gitea" source_path: "/home/esilva/Trash/docker-compose/resources/gitea/gitea_data/"
tags: ["gitea", "data"]
- name: "restic database backup" - name: "Backup gitea PG data"
actions: steps:
dump_database: "backup_data_to_restic_repo" - name: "backup gitea database mount points data"
context: actions:
source_path: "/tmp/db_dump.sql" backup_data_to_restic_repo: "backup_data_to_restic_repo"
tags: ["gitea", "db-dump"] context:
source_path: "/home/esilva/Trash/docker-compose/resources/gitea/postgres_data/"
- name: "backup gitea mount points data" tags: ["gitea", "db-data"]
actions:
backup_data_to_restic_repo: "backup_data_to_restic_repo"
context:
source_path: "/home/esilva/Trash/docker-compose/resources/gitea/gitea_data/"
tags: ["gitea", "data"]
- name: "backup gitea database mount points data"
actions:
backup_data_to_restic_repo: "backup_data_to_restic_repo"
context:
source_path: "/home/esilva/Trash/docker-compose/resources/gitea/postgres_data/"
tags: ["gitea", "db-data"]
+74 -20
View File
@@ -5,7 +5,7 @@ import sys
import yaml import yaml
import json import json
import importlib.util import importlib.util
from datetime import datetime, timezone from datetime import datetime, timedelta, timezone
from typing import Dict, List, Any, Optional, Tuple from typing import Dict, List, Any, Optional, Tuple
from dataclasses import MISSING, asdict, dataclass from dataclasses import MISSING, asdict, dataclass
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
@@ -95,6 +95,7 @@ class Play(object):
self.name: str = name self.name: str = name
self.log_dir: str = "" self.log_dir: str = ""
self._context: Dict[str, Any] = {} self._context: Dict[str, Any] = {}
self._acts: Dict[str, List[CustomStep]] = {}
self._steps: List[StepEntry] = [] self._steps: List[StepEntry] = []
self._registries: Dict[str, ActionRegistry] = {registry.name: registry} | registries self._registries: Dict[str, ActionRegistry] = {registry.name: registry} | registries
@@ -126,12 +127,23 @@ class Play(object):
self._steps.append(StepEntry(name=name, step=stepRunner)) self._steps.append(StepEntry(name=name, step=stepRunner))
def view_playbook(self) -> str: def view_playbook(self) -> str:
steps_info = [] acts_info = {}
for s in self._steps: for act_name, step_list in self._acts.items():
steps_info.append({ steps_info = []
"name": s.name, for s in step_list:
"actions": s.step.get_action_names() steps_info.append({
}) "name": s.name,
"actions": s.get_action_names()
})
new_act = {
"name": act_name,
"steps": steps_info
}
acts_info[act_name] = new_act
registry_data = [] registry_data = []
for reg in self._registries.values(): for reg in self._registries.values():
@@ -140,14 +152,14 @@ class Play(object):
data = { data = {
"name": self.name, "name": self.name,
"registries": registry_data, "registries": registry_data,
"number_of_steps": len(steps_info), "number_of_acts": len(acts_info),
"steps": steps_info, "acts": acts_info,
} }
return json.dumps(data) return json.dumps(data)
def play(self) -> StepLog: def play(self) -> StepLog:
log: StepLog = timed_run(self._play) log: StepLog = timed_run(self._play_act)
if self.log_dir != "": if self.log_dir != "":
log.log_file_path = os.path.join( log.log_file_path = os.path.join(
@@ -174,7 +186,33 @@ class Play(object):
raise PlaybookError(f"failed to create log file {e}") from e raise PlaybookError(f"failed to create log file {e}") from e
return "" return ""
def _play(self) -> StepLog: def _play_act(self) -> StepLog:
playLog: StepLog = StepLog(
step_name=self.name,
status=Status.GOOD,
duration_sec=0,
msg="",
error=[],
substeps=[],
)
for a in self._acts:
log: StepLog = self._play_steps(self._acts[a])
if log.failed:
playLog.error.append({
"status": "failed",
"output": f"failed in step: {log.step_name}"
})
playLog.status = Status.BAD
break
if playLog.status == Status.GOOD:
playLog.msg = "success"
return playLog
def _play_steps(self, steps: List[CustomStep]) -> StepLog:
playLog: StepLog = StepLog( playLog: StepLog = StepLog(
step_name=self.name, step_name=self.name,
status=Status.GOOD, status=Status.GOOD,
@@ -185,14 +223,17 @@ class Play(object):
) )
prev_ctx: Dict[str, Any] = {} prev_ctx: Dict[str, Any] = {}
for s in self._steps: for step in steps:
log: StepLog = timed_run(s.step.run, self._context | prev_ctx) log: StepLog = timed_run(step.run, self._context | prev_ctx)
prev_ctx = log.pipe_ctx prev_ctx = log.pipe_ctx
playLog.substeps.append(log) playLog.substeps.append(log)
if log.failed: if log.failed:
playLog.error.append({"status": "failed", "output": f"failed in step: {s.name}"}) playLog.error.append({
"status": "failed",
"output": f"failed in step: {step.name}"
})
playLog.status = Status.BAD playLog.status = Status.BAD
break break
@@ -254,21 +295,34 @@ class Play(object):
return custom_registries return custom_registries
# NOTE: yes, I know, I have to refactor this
def __from_yaml_str_load_acts(self, data):
for idx, act in enumerate(data.get("acts", [])):
act_name = act.get("name", f"step_{idx}")
steps = act.get("steps", [])
self._acts[act_name] = []
try:
self.__from_yaml_str_load_steps(act_name, steps)
except Exception as e:
raise PlaybookError(f"error loading steps for act {act_name}") from e
# NOTE: Move to factory # NOTE: Move to factory
def __from_yaml_str_load_steps(self, data): def __from_yaml_str_load_steps(self, key, data):
for idx, step_cfg in enumerate(data.get("steps", [])): for step_cfg in data:
step_name = step_cfg.get("name", f"step_{idx}") step_name = step_cfg.get("name", f"")
actions = step_cfg.get("actions", {}) actions = step_cfg.get("actions", {})
context = step_cfg.get("context", {}) context = step_cfg.get("context", {})
if not isinstance(actions, dict) or len(actions) <= 0: if not isinstance(actions, dict) or len(actions) <= 0:
raise PlaybookError( raise PlaybookError(
f"Step '{step_name}' (index {idx}) must define at least one action " f"Step '{step_name}' (index ) must define at least one action "
) )
try: try:
customStep: CustomStep = self._build_custom_step(step_name, actions, context) customStep: CustomStep = self._build_custom_step(step_name, actions, context)
self.add_step(step_cfg["name"], customStep) self._acts[key].append(customStep)
except Exception as e: except Exception as e:
raise PlaybookError(f"Error configuring step '{step_name}': {e}") from e raise PlaybookError(f"Error configuring step '{step_name}': {e}") from e
@@ -291,7 +345,7 @@ class Play(object):
playbook = cls(data["playbook_name"], registries=custom_registries) playbook = cls(data["playbook_name"], registries=custom_registries)
try: try:
playbook.__from_yaml_str_load_steps(data) playbook.__from_yaml_str_load_acts(data)
except Exception as e: except Exception as e:
raise PlaybookError(f"steps load error: {e}") from e raise PlaybookError(f"steps load error: {e}") from e
+2
View File
@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import time import time
import functools
from enum import StrEnum from enum import StrEnum
from typing import List, Dict, Any, Callable from typing import List, Dict, Any, Callable
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -51,6 +52,7 @@ class ContextChecker:
@classmethod @classmethod
def requires(cls, *args): def requires(cls, *args):
def decorator(func: ActionFn): def decorator(func: ActionFn):
@functools.wraps(func)
def wrapper(ctx: Dict[str, Any], name: str) -> StepLog: def wrapper(ctx: Dict[str, Any], name: str) -> StepLog:
if all(key in ctx for key in args): if all(key in ctx for key in args):
return func(ctx, name) return func(ctx, name)
+2 -2
View File
@@ -33,8 +33,8 @@ def main():
traceback.print_exc() traceback.print_exc()
exit(1) exit(1)
# print(newPlay.view_playbook()) print(newPlay.view_playbook())
print(json.dumps(asdict(newPlay.play()), indent=2)) # print(json.dumps(asdict(newPlay.play()), indent=2))
if __name__ == "__main__": if __name__ == "__main__":