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"
registries:
- "./registry/directory_actions.py"
- "./registry/borg_actions.py"
- "./registry/restic_actions.py"
steps:
- name: "backup dir1"
+30 -64
View File
@@ -5,7 +5,6 @@ import sys
import yaml
import json
import time
import datetime
import importlib.util
from typing import Dict, List, Any, Optional
from dataclasses import dataclass
@@ -17,41 +16,43 @@ from playbook.premade_steps_registry import registry
class StepIF(ABC):
""" Interface for plays. """
@abstractmethod
def pre(self) -> StepLog:
""" Run at the start to validate the configurations, inputs, etc. """
def run(self) -> StepLog:
"""Perform the actions."""
pass
@abstractmethod
def play(self) -> StepLog:
""" Steps to actually run the backup. """
@abstractmethod
def post(self) -> StepLog:
""" Run at the end to validate the actions made. """
@abstractmethod
def get_action_names(self) -> Dict[str, str]:
def get_action_names(self) -> List[str]:
""" Return a mapping of action roles (pre, play, post) to function names. """
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."""
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
return log
def run(self, name: str) -> StepLog:
def run(self) -> StepLog:
""" Run the step. """
start_time: int = time.perf_counter_ns()
substeps: List[StepLog] = []
status: Status = Status.GOOD
for op in [self.pre, self.play, self.post]:
for op in self.__actions:
log: StepLog = self.__timed_run(op)
status = Status.BAD if log.failed else Status.GOOD
@@ -70,47 +71,11 @@ class StepIF(ABC):
msg = "success"
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,
)
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):
""" Base exception for Playbook parsing and execution failures. """
pass
@@ -187,7 +152,7 @@ class Play(object):
)
for s in self.__steps:
log: StepLog = s.step.run(s.name)
log: StepLog = s.step.run()
playLog.substeps.append(log)
@@ -214,17 +179,18 @@ class Play(object):
raise ValueError(f"no function with name '{fn_name}' was found")
def __build_custom_step(self, name, actions, ctx) -> CustomStep:
try:
pre_fn = self.__get_function_from_registry(actions["pre"])
play_fn = self.__get_function_from_registry(actions["play"])
post_fn = self.__get_function_from_registry(actions["post"])
except KeyError as e:
raise PlaybookError(f"Step '{name}' is missing required action field: {e}") from e
functionList: List[ActionFn] = []
for f in actions:
try:
fn = self.__get_function_from_registry(actions[f])
functionList.append(fn)
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
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. """
try:
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 argparse
import traceback
import importlib.util
from typing import List
from playbook import StepLog, CustomStep, Play, Status
from playbook import Play
from dataclasses import asdict
from playbook.action_registry import ActionRegistry
@@ -22,24 +19,24 @@ def parse_args() -> argparse.Namespace:
help="Path to the playbook yaml file"
)
# Flag for the custom registry files
parser.add_argument(
"-r", "--registry",
nargs="+",
default=[],
help="One or more python files with actions"
)
# NOTE: For now I will only take the registries from the yaml playbook
# # Flag for the custom registry files
# parser.add_argument(
# "-r", "--registry",
# nargs="+",
# default=[],
# help="One or more python files with actions"
# )
return parser.parse_args()
def main():
args: argparse.Namespace = parse_args()
extra_regs: List[ActionRegistry] = []
newPlay: Play
try:
newPlay = Play.from_yaml(args.playbook, registries=extra_regs)
newPlay = Play.from_yaml(args.playbook)
except Exception as e:
print(f"Failed to load playbook: {e}")
traceback.print_exc()