instead of having a pre established pre, play and post now its just a list of tasks
This commit is contained in:
+1
-1
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user