instead of having a pre established pre, play and post now its just a list of tasks

This commit is contained in:
2026-07-26 23:27:25 +01:00
parent 1acb32a074
commit f3d430d772
5 changed files with 95 additions and 112 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
playbook_name: "test_playbook" playbook_name: "test_playbook"
registries: registries:
- "./registry/directory_actions.py" - "./registry/directory_actions.py"
- "./registry/borg_actions.py" - "./registry/restic_actions.py"
steps: steps:
- name: "backup dir1" - name: "backup dir1"
+30 -64
View File
@@ -5,7 +5,6 @@ import sys
import yaml import yaml
import json import json
import time import time
import datetime
import importlib.util import importlib.util
from typing import Dict, List, Any, Optional from typing import Dict, List, Any, Optional
from dataclasses import dataclass from dataclasses import dataclass
@@ -17,41 +16,43 @@ from playbook.premade_steps_registry import registry
class StepIF(ABC): class StepIF(ABC):
""" Interface for plays. """
@abstractmethod @abstractmethod
def pre(self) -> StepLog: def run(self) -> StepLog:
""" Run at the start to validate the configurations, inputs, etc. """ """Perform the actions."""
pass pass
@abstractmethod
def play(self) -> StepLog:
""" Steps to actually run the backup. """
@abstractmethod @abstractmethod
def post(self) -> StepLog: def get_action_names(self) -> List[str]:
""" Run at the end to validate the actions made. """
@abstractmethod
def get_action_names(self) -> Dict[str, str]:
""" Return a mapping of action roles (pre, play, post) to function names. """ """ Return a mapping of action roles (pre, play, post) to function names. """
pass pass
def __timed_run(self, op) -> StepLog:
class CustomStep(StepIF):
""" Setup custom steps. """
def __init__( self, name: str, actions: List[ActionFn], context: Dict[str, Any]):
self.name: str = name
self.__actions = actions
self.__context = context
def get_action_names(self) -> List[str]:
return [action.__name__ for action in self.__actions]
def __timed_run(self, op: ActionFn) -> StepLog:
"""Function that sets the duration_sec field on the log.""" """Function that sets the duration_sec field on the log."""
start_time: int = time.perf_counter_ns() start_time: int = time.perf_counter_ns()
log: StepLog = op() log: StepLog = op(self.__context, self.name)
log.duration_sec = time.perf_counter_ns() - start_time log.duration_sec = time.perf_counter_ns() - start_time
return log return log
def run(self, name: str) -> StepLog: def run(self) -> StepLog:
""" Run the step. """ """ Run the step. """
start_time: int = time.perf_counter_ns() start_time: int = time.perf_counter_ns()
substeps: List[StepLog] = [] substeps: List[StepLog] = []
status: Status = Status.GOOD status: Status = Status.GOOD
for op in [self.pre, self.play, self.post]: for op in self.__actions:
log: StepLog = self.__timed_run(op) log: StepLog = self.__timed_run(op)
status = Status.BAD if log.failed else Status.GOOD status = Status.BAD if log.failed else Status.GOOD
@@ -70,47 +71,11 @@ class StepIF(ABC):
msg = "success" msg = "success"
return StepLog( return StepLog(
step_name=name, status=status, duration_sec=delta, step_name=self.name, status=status, duration_sec=delta,
msg=msg, error=errors, substeps=substeps, msg=msg, error=errors, substeps=substeps,
) )
class CustomStep(StepIF):
""" Setup custom steps. """
def __init__(
self,
name: str,
pre_fn: ActionFn,
play_fn: ActionFn,
post_fn: ActionFn,
context: Dict[str, Any]
):
self.name: str = name
self.pre_fn = pre_fn
self.play_fn = play_fn
self.post_fn = post_fn
self.__context = context
def pre(self) -> StepLog:
return self.pre_fn(self.__context, self.name)
def play(self) -> StepLog:
return self.play_fn(self.__context, self.name)
def post(self) -> StepLog:
return self.post_fn(self.__context, self.name)
def get_action_names(self) -> Dict[str, str]:
return {
"pre": self.pre_fn.__name__,
"play": self.pre_fn.__name__,
"post": self.pre_fn.__name__,
}
class PlaybookError(Exception): class PlaybookError(Exception):
""" Base exception for Playbook parsing and execution failures. """ """ Base exception for Playbook parsing and execution failures. """
pass pass
@@ -187,7 +152,7 @@ class Play(object):
) )
for s in self.__steps: for s in self.__steps:
log: StepLog = s.step.run(s.name) log: StepLog = s.step.run()
playLog.substeps.append(log) playLog.substeps.append(log)
@@ -214,17 +179,18 @@ class Play(object):
raise ValueError(f"no function with name '{fn_name}' was found") raise ValueError(f"no function with name '{fn_name}' was found")
def __build_custom_step(self, name, actions, ctx) -> CustomStep: def __build_custom_step(self, name, actions, ctx) -> CustomStep:
try: functionList: List[ActionFn] = []
pre_fn = self.__get_function_from_registry(actions["pre"]) for f in actions:
play_fn = self.__get_function_from_registry(actions["play"]) try:
post_fn = self.__get_function_from_registry(actions["post"]) fn = self.__get_function_from_registry(actions[f])
except KeyError as e: functionList.append(fn)
raise PlaybookError(f"Step '{name}' is missing required action field: {e}") from e except KeyError as e:
raise PlaybookError(f"Step '{name}' is missing required action field: {e}") from e
return CustomStep(name=name, pre_fn=pre_fn, play_fn=play_fn, post_fn=post_fn, context=ctx) return CustomStep(name, functionList, ctx)
@classmethod @classmethod
def from_yaml(cls, fp: str, registries: Optional[List[ActionRegistry]] = None) -> Play: def from_yaml(cls, fp: str) -> Play:
""" Loads the playbook from a given YAML file. """ """ Loads the playbook from a given YAML file. """
try: try:
with open(fp, "r") as f: with open(fp, "r") as f:
-34
View File
@@ -1,34 +0,0 @@
# borg actions registry
from playbook.action_registry import ActionRegistry
from playbook.models import StepLog, Status
borgact = ActionRegistry("borg_actions_reg")
@borgact.register(name="pre", version="preborg1.0")
def pre(ctx, name) -> StepLog:
log: StepLog = StepLog(
step_name=name,
status=Status.GOOD,
msg="source directory exists",
)
return log
@borgact.register(name="play", version="playborg1.0")
def play(ctx, name) -> StepLog:
log: StepLog = StepLog(
step_name=name,
status=Status.GOOD,
msg="directory was copied/movied/borged/rsynced",
)
return log
@borgact.register(name="post", version="postborg1.0")
def post(ctx, name) -> StepLog:
log: StepLog = StepLog(
step_name=name,
status=Status.GOOD,
msg="copied/movied/borged/rsynced exists and is valid",
)
return log
+54
View File
@@ -0,0 +1,54 @@
# borg actions registry
from playbook.action_registry import ActionRegistry
from playbook.models import StepLog, Status
restic = ActionRegistry("restic_actions_reg")
@restic.register(name="check_repo_exists", version="preborg1.0")
def check_repo_exists(ctx, name) -> StepLog:
passwordFile: str = ctx["passwordFile"]
repoPath: str = ctx["repoPath"]
if passwordFile == "":
return StepLog(
step_name=name,
status=Status.BAD,
error=["password file variable is empty"],
)
if repoPath == "":
return StepLog(
step_name=name,
status=Status.BAD,
error=["password file variable is empty"]
)
# If the path doesn't exist restic will make sure of warning us about it
return StepLog(
step_name=name,
status=Status.GOOD,
msg="password file variable is empty",
)
@restic.register(name="backup_data_to_repo", version="playborg1.0")
def backup_data_to_repo(ctx, name) -> StepLog:
log: StepLog = StepLog(
step_name=name,
status=Status.GOOD,
msg="directory was copied/movied/borged/rsynced",
)
return log
@restic.register(name="post", version="postborg1.0")
def validate_data_backup(ctx, name) -> StepLog:
log: StepLog = StepLog(
step_name=name,
status=Status.GOOD,
msg="copied/movied/borged/rsynced exists and is valid",
)
return log
+10 -13
View File
@@ -1,11 +1,8 @@
import os
import sys
import json import json
import argparse import argparse
import traceback import traceback
import importlib.util
from typing import List from typing import List
from playbook import StepLog, CustomStep, Play, Status from playbook import Play
from dataclasses import asdict from dataclasses import asdict
from playbook.action_registry import ActionRegistry from playbook.action_registry import ActionRegistry
@@ -22,24 +19,24 @@ def parse_args() -> argparse.Namespace:
help="Path to the playbook yaml file" help="Path to the playbook yaml file"
) )
# Flag for the custom registry files # NOTE: For now I will only take the registries from the yaml playbook
parser.add_argument( # # Flag for the custom registry files
"-r", "--registry", # parser.add_argument(
nargs="+", # "-r", "--registry",
default=[], # nargs="+",
help="One or more python files with actions" # default=[],
) # help="One or more python files with actions"
# )
return parser.parse_args() return parser.parse_args()
def main(): def main():
args: argparse.Namespace = parse_args() args: argparse.Namespace = parse_args()
extra_regs: List[ActionRegistry] = []
newPlay: Play newPlay: Play
try: try:
newPlay = Play.from_yaml(args.playbook, registries=extra_regs) newPlay = Play.from_yaml(args.playbook)
except Exception as e: except Exception as e:
print(f"Failed to load playbook: {e}") print(f"Failed to load playbook: {e}")
traceback.print_exc() traceback.print_exc()