From 82e15d0697f71aa7df7343fa2eecd39debc98517 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Sat, 6 Jul 2024 15:52:06 -0400 Subject: [PATCH] feat: volume backup script --- .gitignore | 3 ++ README.md | 7 +++- backup-podman-volumes.py | 78 ++++++++++++++++++++++++++++++++++++++++ bootstrap.sh | 15 ++++++++ lock-deps.sh | 3 ++ pyproject.toml | 8 +++++ requirements.txt | 34 ++++++++++++++++++ 7 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 backup-podman-volumes.py create mode 100644 bootstrap.sh create mode 100755 lock-deps.sh create mode 100644 pyproject.toml create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 737ace7..31794ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ *.env .services +config.json +*.egg* +*.venv diff --git a/README.md b/README.md index 7e59fac..3c1796e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # scripts -_Quick and dirty utilities to build, start and stop services._ +_Utilities to manage the machine Spadinastan runs on._ + +## Setup + +Python dependencies associated with the scripts can be installed via `./bootstrap.sh`, which provides a venv to run the +scripts in. ## Usage diff --git a/backup-podman-volumes.py b/backup-podman-volumes.py new file mode 100644 index 0000000..787118a --- /dev/null +++ b/backup-podman-volumes.py @@ -0,0 +1,78 @@ +""" +backup-podman-volumes + +This script exports volumes defined in the configuration to +S3 and to a local path. + +Usage: python /backup-podman-volumes.py +""" + +import subprocess +import json +import sys +import pathlib +import logging +import shutil + +import pydantic +import boto3 + +logger = logging.getLogger(__file__) +logging.basicConfig(level=logging.INFO) + +class Config(pydantic.BaseModel): + # List of volumes to back up. + volumes: list[str] + # Local directory to save a copy of the latest volume to. + local_backup_path: str + # Bucket name to push the backed up archive to. + bucket_name: str + + +def export_volume(volume_name: str, destination: pathlib.Path) -> str: + """ + Exports a Podman volume identified by as a file + located at . + """ + result = subprocess.run( + f'podman volume export {volume_name} -o "{str(destination)}"', + shell=True, + check=True, + ) + + logger.info(f"Exported {volume_name} to {destination}.") + + +def push_to_s3(source: pathlib.Path, bucket: str): + """ + Pushes the given file to S3. + + The file name is reused as the object name, and the file + located at is pushed to bucket under that + name. + """ + s3 = boto3.client("s3") + object_name = source.name + s3.upload_file(str(source), bucket, object_name) + + logger.info(f"Pushed {source} to {bucket} as {object_name}") + + +if __name__ == "__main__": + config_path = sys.argv[1] + + if not pathlib.Path(config_path).exists(): + raise RuntimeError(f"Did not find configuration at {config_path}") + + config = Config.parse_file(config_path) + + for volume in config.volumes: + exported_fn = f"{volume}.tar" + exported_path = pathlib.Path("/tmp", exported_fn) + + export_volume(volume, exported_path) + local_backup_path = pathlib.Path(config.local_backup_path, exported_fn) + push_to_s3(exported_path, config.bucket_name) + shutil.copy(exported_path, local_backup_path) + + logger.info(f"Backed up {volume} to S3 and local.") diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..a8dae7e --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +python -m venv scripts.venv + +. scripts.venv/bin/activate + +python -m pip install pip~=24.0 pip-tools~=7.4 + +if [[ ! -f requirements.txt ]]; then + touch requirements.txt +fi + +pip-sync requirements.txt + +pip install -e . diff --git a/lock-deps.sh b/lock-deps.sh new file mode 100755 index 0000000..15f1fbc --- /dev/null +++ b/lock-deps.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +pip-compile -o requirements.txt pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c3fa64a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[project] +name = "scripts" +version = "0" +dependencies = [ + "boto3", + "pydantic", +] +requires-python = "~= 3.12" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..30997bc --- /dev/null +++ b/requirements.txt @@ -0,0 +1,34 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --output-file=requirements.txt pyproject.toml +# +annotated-types==0.7.0 + # via pydantic +boto3==1.34.140 + # via scripts (pyproject.toml) +botocore==1.34.140 + # via + # boto3 + # s3transfer +jmespath==1.0.1 + # via + # boto3 + # botocore +pydantic==2.8.2 + # via scripts (pyproject.toml) +pydantic-core==2.20.1 + # via pydantic +python-dateutil==2.9.0.post0 + # via botocore +s3transfer==0.10.2 + # via boto3 +six==1.16.0 + # via python-dateutil +typing-extensions==4.12.2 + # via + # pydantic + # pydantic-core +urllib3==2.2.2 + # via botocore