mirror of
https://github.com/kevinveenbirkenbach/docker-volume-backup.git
synced 2026-07-17 14:15:13 +00:00
feat(backup)!: exact --images-* matching and --hard-restart-projects
Match --images-no-stop-required and --images-no-backup-required against the container's exact .Config.Image instead of a substring, so callers pass full repo:tag references (registry prefix included) and near-miss image names no longer flip the stop/skip decision. Rename the opt-in --hard-compose-restart flag to --hard-restart-projects. The e2e suite pins a SPOT for the DB images and in-container data dirs (postgres:alpine at /var/lib/postgresql, mariadb:latest at /var/lib/mysql) and passes exact image refs to the --images-* flags. BREAKING CHANGE: --images-* now require exact image references, not substrings; --hard-compose-restart is renamed to --hard-restart-projects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,7 +52,7 @@ def is_image_ignored(container: str, images_no_backup_required: list[str]) -> bo
|
|||||||
if not images_no_backup_required:
|
if not images_no_backup_required:
|
||||||
return False
|
return False
|
||||||
img = get_image_info(container)
|
img = get_image_info(container)
|
||||||
return any(pat in img for pat in images_no_backup_required)
|
return img in images_no_backup_required
|
||||||
|
|
||||||
|
|
||||||
def volume_is_fully_ignored(
|
def volume_is_fully_ignored(
|
||||||
@@ -68,15 +68,15 @@ def volume_is_fully_ignored(
|
|||||||
|
|
||||||
def requires_stop(containers: list[str], images_no_stop_required: list[str]) -> bool:
|
def requires_stop(containers: list[str], images_no_stop_required: list[str]) -> bool:
|
||||||
"""
|
"""
|
||||||
Stop is required if ANY stoppable container image is NOT in the
|
Stop is required if ANY stoppable container image is NOT in the exact
|
||||||
whitelist patterns. Swarm task containers never count: baudolo must
|
image whitelist. Swarm task containers never count: baudolo must
|
||||||
not cycle them (see docker.is_swarm_task).
|
not cycle them (see docker.is_swarm_task).
|
||||||
"""
|
"""
|
||||||
for c in containers:
|
for c in containers:
|
||||||
if is_swarm_task(c):
|
if is_swarm_task(c):
|
||||||
continue
|
continue
|
||||||
img = get_image_info(c)
|
img = get_image_info(c)
|
||||||
if not any(pat in img for pat in images_no_stop_required):
|
if img not in images_no_stop_required:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -247,6 +247,6 @@ def main() -> int:
|
|||||||
print("Finished volume backups.", flush=True)
|
print("Finished volume backups.", flush=True)
|
||||||
|
|
||||||
print("Handling Docker Compose services...", flush=True)
|
print("Handling Docker Compose services...", flush=True)
|
||||||
handle_docker_compose_services(args.compose_dir, args.hard_compose_restart)
|
handle_docker_compose_services(args.compose_dir, args.hard_restart_projects)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def parse_args() -> argparse.Namespace:
|
|||||||
help="Path to the parent directory containing docker-compose setups",
|
help="Path to the parent directory containing docker-compose setups",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--hard-compose-restart",
|
"--hard-restart-projects",
|
||||||
nargs="*",
|
nargs="*",
|
||||||
default=[],
|
default=[],
|
||||||
help="Compose dir names that require 'docker-compose down && up -d' (default: none; pass e.g. 'mailu' under compose where the DB cannot be backed up hot)",
|
help="Compose dir names that require 'docker-compose down && up -d' (default: none; pass e.g. 'mailu' under compose where the DB cannot be backed up hot)",
|
||||||
@@ -49,13 +49,13 @@ def parse_args() -> argparse.Namespace:
|
|||||||
"--images-no-stop-required",
|
"--images-no-stop-required",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
required=True,
|
required=True,
|
||||||
help="Image name patterns for which containers should not be stopped during file backup",
|
help="Exact image references (repo:tag, incl. any registry prefix) whose containers must not be stopped during file backup",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--images-no-backup-required",
|
"--images-no-backup-required",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
default=[],
|
default=[],
|
||||||
help="Image name patterns for which no backup should be performed",
|
help="Exact image references (repo:tag, incl. any registry prefix) for which no backup should be performed",
|
||||||
)
|
)
|
||||||
|
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ import time
|
|||||||
import uuid
|
import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# SPOT for the database images and their in-container data dirs the e2e
|
||||||
|
# suite runs against. postgres:alpine tracks latest (18+), which mounts at
|
||||||
|
# /var/lib/postgresql (not /var/lib/postgresql/data); bump here only.
|
||||||
|
POSTGRES_IMAGE = "postgres:alpine"
|
||||||
|
POSTGRES_DATA_DIR = "/var/lib/postgresql"
|
||||||
|
MARIADB_IMAGE = "mariadb:latest"
|
||||||
|
MARIADB_DATA_DIR = "/var/lib/mysql"
|
||||||
|
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
cmd: list[str],
|
cmd: list[str],
|
||||||
@@ -172,7 +180,7 @@ def backup_run(
|
|||||||
"baudolo",
|
"baudolo",
|
||||||
"--compose-dir",
|
"--compose-dir",
|
||||||
compose_dir,
|
compose_dir,
|
||||||
"--docker-compose-hard-restart-required",
|
"--hard-restart-projects",
|
||||||
"mailu",
|
"mailu",
|
||||||
"--repo-name",
|
"--repo-name",
|
||||||
repo_name,
|
repo_name,
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
POSTGRES_IMAGE,
|
||||||
|
POSTGRES_DATA_DIR,
|
||||||
backup_path,
|
backup_path,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
create_minimal_compose_dir,
|
create_minimal_compose_dir,
|
||||||
@@ -50,8 +52,8 @@ class TestE2EDumpOnlyFallbackToFiles(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
"POSTGRES_USER=postgres",
|
"POSTGRES_USER=postgres",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.pg_volume}:/var/lib/postgresql/data",
|
f"{cls.pg_volume}:{POSTGRES_DATA_DIR}",
|
||||||
"postgres:16",
|
POSTGRES_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
||||||
@@ -65,7 +67,7 @@ class TestE2EDumpOnlyFallbackToFiles(unittest.TestCase):
|
|||||||
cls.pg_container,
|
cls.pg_container,
|
||||||
"sh",
|
"sh",
|
||||||
"-lc",
|
"-lc",
|
||||||
f"echo '{cls.marker}' > /var/lib/postgresql/data/marker.txt",
|
f"echo '{cls.marker}' > {POSTGRES_DATA_DIR}/marker.txt",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -79,7 +81,7 @@ class TestE2EDumpOnlyFallbackToFiles(unittest.TestCase):
|
|||||||
"baudolo",
|
"baudolo",
|
||||||
"--compose-dir",
|
"--compose-dir",
|
||||||
cls.compose_dir,
|
cls.compose_dir,
|
||||||
"--docker-compose-hard-restart-required",
|
"--hard-restart-projects",
|
||||||
"mailu",
|
"mailu",
|
||||||
"--repo-name",
|
"--repo-name",
|
||||||
cls.repo_name,
|
cls.repo_name,
|
||||||
@@ -90,10 +92,7 @@ class TestE2EDumpOnlyFallbackToFiles(unittest.TestCase):
|
|||||||
"--database-containers",
|
"--database-containers",
|
||||||
cls.pg_container,
|
cls.pg_container,
|
||||||
"--images-no-stop-required",
|
"--images-no-stop-required",
|
||||||
"postgres",
|
POSTGRES_IMAGE,
|
||||||
"mariadb",
|
|
||||||
"mysql",
|
|
||||||
"alpine",
|
|
||||||
"--dump-only-sql",
|
"--dump-only-sql",
|
||||||
]
|
]
|
||||||
cp = run(cmd, capture=True, check=True)
|
cp = run(cmd, capture=True, check=True)
|
||||||
@@ -116,8 +115,6 @@ class TestE2EDumpOnlyFallbackToFiles(unittest.TestCase):
|
|||||||
cls.repo_name,
|
cls.repo_name,
|
||||||
"--source-volume",
|
"--source-volume",
|
||||||
cls.pg_volume,
|
cls.pg_volume,
|
||||||
"--rsync-image",
|
|
||||||
"ghcr.io/kevinveenbirkenbach/alpine-rsync",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
POSTGRES_IMAGE,
|
||||||
|
POSTGRES_DATA_DIR,
|
||||||
backup_path,
|
backup_path,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
create_minimal_compose_dir,
|
create_minimal_compose_dir,
|
||||||
@@ -70,8 +72,8 @@ class TestE2EDumpOnlySqlMixedRun(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
f"POSTGRES_PASSWORD={cls.pg_password}",
|
f"POSTGRES_PASSWORD={cls.pg_password}",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.db_volume}:/var/lib/postgresql/data",
|
f"{cls.db_volume}:{POSTGRES_DATA_DIR}",
|
||||||
"postgres:16-alpine",
|
POSTGRES_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
||||||
@@ -122,10 +124,7 @@ class TestE2EDumpOnlySqlMixedRun(unittest.TestCase):
|
|||||||
"--database-containers",
|
"--database-containers",
|
||||||
cls.pg_container,
|
cls.pg_container,
|
||||||
"--images-no-stop-required",
|
"--images-no-stop-required",
|
||||||
"alpine",
|
POSTGRES_IMAGE,
|
||||||
"postgres",
|
|
||||||
"mariadb",
|
|
||||||
"mysql",
|
|
||||||
"--dump-only-sql",
|
"--dump-only-sql",
|
||||||
"--backups-dir",
|
"--backups-dir",
|
||||||
cls.backups_dir,
|
cls.backups_dir,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class TestE2EFilesFull(unittest.TestCase):
|
|||||||
compose_dir=cls.compose_dir,
|
compose_dir=cls.compose_dir,
|
||||||
databases_csv=cls.databases_csv,
|
databases_csv=cls.databases_csv,
|
||||||
database_containers=["dummy-db"],
|
database_containers=["dummy-db"],
|
||||||
images_no_stop_required=["alpine", "postgres", "mariadb", "mysql"],
|
images_no_stop_required=["alpine:3.20"],
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.hash, cls.version = latest_version_dir(cls.backups_dir, cls.repo_name)
|
cls.hash, cls.version = latest_version_dir(cls.backups_dir, cls.repo_name)
|
||||||
@@ -94,8 +94,6 @@ class TestE2EFilesFull(unittest.TestCase):
|
|||||||
self.repo_name,
|
self.repo_name,
|
||||||
"--source-volume",
|
"--source-volume",
|
||||||
self.volume_src,
|
self.volume_src,
|
||||||
"--rsync-image",
|
|
||||||
"ghcr.io/kevinveenbirkenbach/alpine-rsync",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class TestE2EFilesNoCopy(unittest.TestCase):
|
|||||||
compose_dir=cls.compose_dir,
|
compose_dir=cls.compose_dir,
|
||||||
databases_csv=cls.databases_csv,
|
databases_csv=cls.databases_csv,
|
||||||
database_containers=["dummy-db"],
|
database_containers=["dummy-db"],
|
||||||
images_no_stop_required=["alpine", "postgres", "mariadb", "mysql"],
|
images_no_stop_required=["alpine:3.20"],
|
||||||
dump_only_sql=True,
|
dump_only_sql=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class TestE2EImagesNoBackupRequiredEarlySkip(unittest.TestCase):
|
|||||||
"baudolo",
|
"baudolo",
|
||||||
"--compose-dir",
|
"--compose-dir",
|
||||||
cls.compose_dir,
|
cls.compose_dir,
|
||||||
"--docker-compose-hard-restart-required",
|
"--hard-restart-projects",
|
||||||
"mailu",
|
"mailu",
|
||||||
"--repo-name",
|
"--repo-name",
|
||||||
cls.repo_name,
|
cls.repo_name,
|
||||||
@@ -87,13 +87,9 @@ class TestE2EImagesNoBackupRequiredEarlySkip(unittest.TestCase):
|
|||||||
"--database-containers",
|
"--database-containers",
|
||||||
"dummy-db",
|
"dummy-db",
|
||||||
"--images-no-stop-required",
|
"--images-no-stop-required",
|
||||||
"alpine",
|
"redis:alpine",
|
||||||
"redis",
|
|
||||||
"postgres",
|
|
||||||
"mariadb",
|
|
||||||
"mysql",
|
|
||||||
"--images-no-backup-required",
|
"--images-no-backup-required",
|
||||||
"redis",
|
"redis:alpine",
|
||||||
]
|
]
|
||||||
cp = run(cmd, capture=True, check=True)
|
cp = run(cmd, capture=True, check=True)
|
||||||
cls.stdout = cp.stdout or ""
|
cls.stdout = cp.stdout or ""
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ import pandas
|
|||||||
from baudolo.backup import db as db_mod
|
from baudolo.backup import db as db_mod
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
MARIADB_IMAGE,
|
||||||
|
MARIADB_DATA_DIR,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
require_docker,
|
require_docker,
|
||||||
run,
|
run,
|
||||||
@@ -69,8 +71,8 @@ class TestE2EMariaDBAnonymousPreemption(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
f"MARIADB_ROOT_PASSWORD={cls.root_password}",
|
f"MARIADB_ROOT_PASSWORD={cls.root_password}",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.db_volume}:/var/lib/mysql",
|
f"{cls.db_volume}:{MARIADB_DATA_DIR}",
|
||||||
"mariadb:12.2",
|
MARIADB_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
MARIADB_IMAGE,
|
||||||
|
MARIADB_DATA_DIR,
|
||||||
backup_run,
|
backup_run,
|
||||||
backup_path,
|
backup_path,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
@@ -56,8 +58,8 @@ class TestE2EMariaDBFull(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
f"MARIADB_PASSWORD={cls.db_password}",
|
f"MARIADB_PASSWORD={cls.db_password}",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.db_volume}:/var/lib/mysql",
|
f"{cls.db_volume}:{MARIADB_DATA_DIR}",
|
||||||
"mariadb:11",
|
MARIADB_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +99,7 @@ class TestE2EMariaDBFull(unittest.TestCase):
|
|||||||
compose_dir=cls.compose_dir,
|
compose_dir=cls.compose_dir,
|
||||||
databases_csv=cls.databases_csv,
|
databases_csv=cls.databases_csv,
|
||||||
database_containers=[cls.db_container],
|
database_containers=[cls.db_container],
|
||||||
images_no_stop_required=["mariadb", "mysql", "alpine", "postgres"],
|
images_no_stop_required=[MARIADB_IMAGE],
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.hash, cls.version = latest_version_dir(cls.backups_dir, cls.repo_name)
|
cls.hash, cls.version = latest_version_dir(cls.backups_dir, cls.repo_name)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
MARIADB_IMAGE,
|
||||||
|
MARIADB_DATA_DIR,
|
||||||
backup_run,
|
backup_run,
|
||||||
backup_path,
|
backup_path,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
@@ -55,8 +57,8 @@ class TestE2EMariaDBNoCopy(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
f"MARIADB_PASSWORD={cls.db_password}",
|
f"MARIADB_PASSWORD={cls.db_password}",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.db_volume}:/var/lib/mysql",
|
f"{cls.db_volume}:{MARIADB_DATA_DIR}",
|
||||||
"mariadb:11",
|
MARIADB_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,7 +96,7 @@ class TestE2EMariaDBNoCopy(unittest.TestCase):
|
|||||||
compose_dir=cls.compose_dir,
|
compose_dir=cls.compose_dir,
|
||||||
databases_csv=cls.databases_csv,
|
databases_csv=cls.databases_csv,
|
||||||
database_containers=[cls.db_container],
|
database_containers=[cls.db_container],
|
||||||
images_no_stop_required=["mariadb", "mysql", "alpine", "postgres"],
|
images_no_stop_required=[MARIADB_IMAGE],
|
||||||
dump_only_sql=True,
|
dump_only_sql=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
POSTGRES_IMAGE,
|
||||||
|
POSTGRES_DATA_DIR,
|
||||||
backup_run,
|
backup_run,
|
||||||
backup_path,
|
backup_path,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
@@ -47,8 +49,8 @@ class TestE2EPostgresFull(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
"POSTGRES_USER=postgres",
|
"POSTGRES_USER=postgres",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.pg_volume}:/var/lib/postgresql/data",
|
f"{cls.pg_volume}:{POSTGRES_DATA_DIR}",
|
||||||
"postgres:16",
|
POSTGRES_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
||||||
@@ -76,7 +78,7 @@ class TestE2EPostgresFull(unittest.TestCase):
|
|||||||
compose_dir=cls.compose_dir,
|
compose_dir=cls.compose_dir,
|
||||||
databases_csv=cls.databases_csv,
|
databases_csv=cls.databases_csv,
|
||||||
database_containers=[cls.pg_container],
|
database_containers=[cls.pg_container],
|
||||||
images_no_stop_required=["postgres", "mariadb", "mysql", "alpine"],
|
images_no_stop_required=[POSTGRES_IMAGE],
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.hash, cls.version = latest_version_dir(cls.backups_dir, cls.repo_name)
|
cls.hash, cls.version = latest_version_dir(cls.backups_dir, cls.repo_name)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
POSTGRES_IMAGE,
|
||||||
|
POSTGRES_DATA_DIR,
|
||||||
backup_run,
|
backup_run,
|
||||||
backup_path,
|
backup_path,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
@@ -46,8 +48,8 @@ class TestE2EPostgresNoCopy(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
"POSTGRES_USER=postgres",
|
"POSTGRES_USER=postgres",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.pg_volume}:/var/lib/postgresql/data",
|
f"{cls.pg_volume}:{POSTGRES_DATA_DIR}",
|
||||||
"postgres:16",
|
POSTGRES_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
||||||
@@ -74,7 +76,7 @@ class TestE2EPostgresNoCopy(unittest.TestCase):
|
|||||||
compose_dir=cls.compose_dir,
|
compose_dir=cls.compose_dir,
|
||||||
databases_csv=cls.databases_csv,
|
databases_csv=cls.databases_csv,
|
||||||
database_containers=[cls.pg_container],
|
database_containers=[cls.pg_container],
|
||||||
images_no_stop_required=["postgres", "mariadb", "mysql", "alpine"],
|
images_no_stop_required=[POSTGRES_IMAGE],
|
||||||
dump_only_sql=True,
|
dump_only_sql=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
|
POSTGRES_IMAGE,
|
||||||
|
POSTGRES_DATA_DIR,
|
||||||
backup_path,
|
backup_path,
|
||||||
cleanup_docker,
|
cleanup_docker,
|
||||||
create_minimal_compose_dir,
|
create_minimal_compose_dir,
|
||||||
@@ -66,8 +68,8 @@ class TestE2ESeedStarAndDbEntriesBackupPostgres(unittest.TestCase):
|
|||||||
"-e",
|
"-e",
|
||||||
f"POSTGRES_PASSWORD={cls.pg_password}",
|
f"POSTGRES_PASSWORD={cls.pg_password}",
|
||||||
"-v",
|
"-v",
|
||||||
f"{cls.db_volume}:/var/lib/postgresql/data",
|
f"{cls.db_volume}:{POSTGRES_DATA_DIR}",
|
||||||
"postgres:16-alpine",
|
POSTGRES_IMAGE,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
wait_for_postgres(cls.pg_container, user="postgres", timeout_s=90)
|
||||||
@@ -165,10 +167,7 @@ class TestE2ESeedStarAndDbEntriesBackupPostgres(unittest.TestCase):
|
|||||||
"--database-containers",
|
"--database-containers",
|
||||||
cls.pg_container,
|
cls.pg_container,
|
||||||
"--images-no-stop-required",
|
"--images-no-stop-required",
|
||||||
"alpine",
|
POSTGRES_IMAGE,
|
||||||
"postgres",
|
|
||||||
"mariadb",
|
|
||||||
"mysql",
|
|
||||||
"--dump-only-sql",
|
"--dump-only-sql",
|
||||||
"--backups-dir",
|
"--backups-dir",
|
||||||
cls.backups_dir,
|
cls.backups_dir,
|
||||||
|
|||||||
@@ -279,15 +279,15 @@ class HardRestartArgTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_default_is_empty(self) -> None:
|
def test_default_is_empty(self) -> None:
|
||||||
args = self._parse([])
|
args = self._parse([])
|
||||||
self.assertEqual(args.hard_compose_restart, [])
|
self.assertEqual(args.hard_restart_projects, [])
|
||||||
|
|
||||||
def test_empty_flag_stays_empty(self) -> None:
|
def test_empty_flag_stays_empty(self) -> None:
|
||||||
args = self._parse(["--hard-compose-restart"])
|
args = self._parse(["--hard-restart-projects"])
|
||||||
self.assertEqual(args.hard_compose_restart, [])
|
self.assertEqual(args.hard_restart_projects, [])
|
||||||
|
|
||||||
def test_explicit_names_preserved(self) -> None:
|
def test_explicit_names_preserved(self) -> None:
|
||||||
args = self._parse(["--hard-compose-restart", "mailu", "foo"])
|
args = self._parse(["--hard-restart-projects", "mailu", "foo"])
|
||||||
self.assertEqual(args.hard_compose_restart, ["mailu", "foo"])
|
self.assertEqual(args.hard_restart_projects, ["mailu", "foo"])
|
||||||
|
|
||||||
def test_backups_dir_is_required(self) -> None:
|
def test_backups_dir_is_required(self) -> None:
|
||||||
import sys
|
import sys
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ class TestRequiresStop(unittest.TestCase):
|
|||||||
def test_requires_stop_false_when_all_images_are_whitelisted(
|
def test_requires_stop_false_when_all_images_are_whitelisted(
|
||||||
self, mock_get_image_info, _mock_is_swarm_task
|
self, mock_get_image_info, _mock_is_swarm_task
|
||||||
):
|
):
|
||||||
# All containers use images containing allowed substrings
|
|
||||||
mock_get_image_info.side_effect = [
|
mock_get_image_info.side_effect = [
|
||||||
"repo/mastodon:v4",
|
"repo/mastodon:v4",
|
||||||
"repo/wordpress:latest",
|
"repo/wordpress:latest",
|
||||||
]
|
]
|
||||||
containers = ["c1", "c2"]
|
containers = ["c1", "c2"]
|
||||||
whitelist = ["mastodon", "wordpress"]
|
whitelist = ["repo/mastodon:v4", "repo/wordpress:latest"]
|
||||||
self.assertFalse(requires_stop(containers, whitelist))
|
self.assertFalse(requires_stop(containers, whitelist))
|
||||||
|
|
||||||
@patch("baudolo.backup.app.get_image_info")
|
@patch("baudolo.backup.app.get_image_info")
|
||||||
@@ -28,9 +27,17 @@ class TestRequiresStop(unittest.TestCase):
|
|||||||
"repo/nginx:latest",
|
"repo/nginx:latest",
|
||||||
]
|
]
|
||||||
containers = ["c1", "c2"]
|
containers = ["c1", "c2"]
|
||||||
whitelist = ["mastodon", "wordpress"]
|
whitelist = ["repo/mastodon:v4", "repo/wordpress:latest"]
|
||||||
self.assertTrue(requires_stop(containers, whitelist))
|
self.assertTrue(requires_stop(containers, whitelist))
|
||||||
|
|
||||||
|
@patch("baudolo.backup.app.get_image_info")
|
||||||
|
def test_requires_stop_true_on_substring_only_match(
|
||||||
|
self, mock_get_image_info, _mock_is_swarm_task
|
||||||
|
):
|
||||||
|
mock_get_image_info.return_value = "reg:5000/repo/mastodon:v4"
|
||||||
|
self.assertTrue(requires_stop(["c1"], ["mastodon"]))
|
||||||
|
self.assertTrue(requires_stop(["c1"], ["repo/mastodon:v4"]))
|
||||||
|
|
||||||
@patch("baudolo.backup.app.get_image_info")
|
@patch("baudolo.backup.app.get_image_info")
|
||||||
def test_requires_stop_true_when_whitelist_empty(
|
def test_requires_stop_true_when_whitelist_empty(
|
||||||
self, mock_get_image_info, _mock_is_swarm_task
|
self, mock_get_image_info, _mock_is_swarm_task
|
||||||
|
|||||||
Reference in New Issue
Block a user