stackup/drone.py

181 lines
6.9 KiB
Python
Raw Normal View History

import dataclasses
import datetime
import os
import pathlib
import sys
from typing import List
from typing import Optional
import plumbum.cmd
import plumbum.machines
@dataclasses.dataclass
class Config:
repo_path: pathlib.Path
archive_path: List[str]
check: bool = True
compact: bool = True
repo_init: bool = True
repo_init_append_only: bool = False
repo_init_quota: Optional[str] = None
2023-10-17 02:34:23 +00:00
repo_init_encryption: Optional[str] = None
repo_init_overwrite_key: bool = False
repo_key_file: pathlib.Path = pathlib.Path("/keys")
repo_passphrase: Optional[str] = None
repo_passphrase_file: Optional[pathlib.Path] = None
prune: bool = False
prune_interval: Optional[datetime.timedelta] = None
prune_secondly: Optional[int] = None
prune_minutely: Optional[int] = None
prune_hourly: Optional[int] = None
prune_daily: Optional[int] = None
prune_weekly: Optional[int] = None
prune_monthly: Optional[int] = None
prune_yearly: Optional[int] = None
archive_name: Optional[str] = None
archive_comment: Optional[str] = None
archive_exclude_pattern: Optional[str] = None
archive_exclude_caches: bool = False
archive_exclude_if_present: Optional[str] = None
archive_keep_exclude_tags: bool = False
@classmethod
def build(cls):
2023-10-17 02:34:23 +00:00
prefix = "BORGDRONE_"
try:
return cls(
repo_path=pathlib.Path(os.environ[f"{prefix}REPO_PATH"]),
archive_path=[
pathlib.Path(item.strip())
for item in os.environ[f"{prefix}ARCHIVE_PATH"].split(",")
if item
],
compact=os.environ.get(f"{prefix}COMPACT", str(cls.compact)).lower()
== "true",
check=os.environ.get(f"{prefix}CHECK", str(cls.check)).lower()
== "true",
repo_init=os.environ.get(
f"{prefix}REPO_INIT", str(cls.repo_init)
).lower()
== "true",
repo_init_append_only=os.environ.get(
f"{prefix}REPO_INIT_APPEND_ONLY", str(cls.repo_init_append_only)
).lower()
== "true",
repo_init_quota=os.environ[f"{prefix}REPO_INIT_QUOTA"]
if f"{prefix}REPO_INIT_QUOTA" in os.environ
else cls.repo_init_quota,
repo_init_encryption=os.environ[f"{prefix}REPO_INIT_ENCRYPTION"]
if f"{prefix}REPO_INIT_ENCRYPTION" in os.environ
else cls.repo_init_encryption,
repo_init_overwrite_key=os.environ.get(
f"{prefix}REPO_INIT_OVERWRITE_KEY", str(cls.repo_init_overwrite_key)
).lower()
== "true",
repo_key_file=pathlib.Path(os.environ[f"{prefix}REPO_KEY_FILE"])
if f"{prefix}REPO_KEY_FILE" in os.environ
else cls.repo_key_file,
repo_passphrase=os.environ[f"{prefix}REPO_PASSPHRASE"]
if f"{prefix}REPO_PASSPHRASE" in os.environ
else cls.repo_passphrase,
repo_passphrase_file=pathlib.Path(
os.environ[f"{prefix}REPO_PASSPHRASE_FILE"]
)
if f"{prefix}REPO_PASSPHRASE_FILE" in os.environ
else cls.repo_passphrase_file,
prune=os.environ.get(f"{prefix}PRUNE", str(cls.prune)).lower()
== "true",
prune_interval=datetime.timedelta(
seconds=int(os.environ[f"{prefix}PRUNE_INTERVAL"])
)
if f"{prefix}PRUNE_INTERVAL" in os.environ
else cls.prune_interval,
prune_secondly=int(
os.environ.get(f"{prefix}PRUNE_SECONDLY", cls.prune_secondly)
),
prune_minutely=int(
os.environ.get(f"{prefix}PRUNE_MINUTELY", cls.prune_minutely)
),
prune_hourly=int(
os.environ.get(f"{prefix}PRUNE_HOURLY", cls.prune_hourly)
),
prune_daily=int(
os.environ.get(f"{prefix}PRUNE_DAILY", cls.prune_daily)
),
prune_weekly=int(
os.environ.get(f"{prefix}PRUNE_WEEKLY", cls.prune_weekly)
),
prune_monthly=int(
os.environ.get(f"{prefix}PRUNE_MONTHLY", cls.prune_monthly)
),
prune_yearly=int(
os.environ.get(f"{prefix}PRUNE_YEARLY", cls.prune_yearly)
),
archive_name=os.environ[f"{prefix}ARCHIVE_NAME"]
if f"{prefix}ARCHIVE_NAME" in os.environ
else cls.archive_name,
archive_comment=os.environ[f"{prefix}ARCHIVE_COMMENT"]
if f"{prefix}ARCHIVE_COMMENT" in os.environ
else cls.archive_comment,
archive_exclude_pattern=os.environ[f"{prefix}ARCHIVE_EXCLUDE_PATTERN"]
if f"{prefix}ARCHIVE_EXCLUDE_PATTERN" in os.environ
else cls.archive_exclude_pattern,
archive_exclude_caches=os.environ.get(
f"{prefix}ARCHIVE_EXCLUDE_CACHES", str(cls.archive_exclude_caches)
).lower()
== "true",
archive_exclude_if_present=os.environ[
f"{prefix}ARCHIVE_EXCLUDE_IF_PRESENT"
]
if f"{prefix}ARCHIVE_EXCLUDE_IF_PRESENT" in os.environ
else cls.archive_exclude_if_present,
archive_keep_exclude_tags=os.environ.get(
f"{prefix}ARCHIVE_KEEP_EXCLUDE_TAGS",
str(cls.archive_keep_exclude_tags),
).lower()
== "true",
)
except KeyError as err:
raise RuntimeError(f"Required configuration parameter {err} not provided")
@staticmethod
def _parse_bool(env: str, value: str) -> bool:
if value.lower().strip() == "true":
return True
elif value.lower().strip() == "false":
return False
else:
raise RuntimeError(
f"Environment variable {env} contains an unexpected value '{value}', expected one of: 'true', 'false'"
)
def check_version(borg: plumbum.machines.LocalCommand):
version = borg["--version"]().partition(" ")[-1].strip()
if int(version.partition(".")[0]) < 2:
raise RuntimeError("BorgDrone requires BorgBackup version 2.0 or higher")
print(f"Using BorgBackup {version} from {borg.executable}", file=sys.stderr)
def main() -> int:
borg = plumbum.cmd.borg
code = 0
try:
check_version(borg)
config = Config.build()
except RuntimeError as err:
print(f"FATAL: {err}", file=sys.stderr)
code = 1
return code
if __name__ == "__main__":
sys.exit(main())