backup-docker-to-local 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {backup_docker_to_local-1.0.0/src/backup_docker_to_local.egg-info → backup_docker_to_local-1.1.0}/PKG-INFO +1 -1
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/pyproject.toml +1 -1
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0/src/backup_docker_to_local.egg-info}/PKG-INFO +1 -1
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/app.py +6 -2
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/cli.py +3 -19
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/compose.py +3 -1
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/db.py +12 -3
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/docker.py +3 -1
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/volume.py +6 -2
- backup_docker_to_local-1.1.0/src/baudolo/restore/__init__.py +1 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/__main__.py +3 -1
- backup_docker_to_local-1.1.0/src/baudolo/restore/db/__init__.py +1 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/db/mariadb.py +22 -4
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/files.py +3 -1
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/seed/__main__.py +29 -11
- backup_docker_to_local-1.0.0/src/baudolo/restore/__init__.py +0 -1
- backup_docker_to_local-1.0.0/src/baudolo/restore/db/__init__.py +0 -1
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/LICENSE +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/README.md +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/setup.cfg +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/backup_docker_to_local.egg-info/SOURCES.txt +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/backup_docker_to_local.egg-info/dependency_links.txt +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/backup_docker_to_local.egg-info/entry_points.txt +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/backup_docker_to_local.egg-info/requires.txt +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/backup_docker_to_local.egg-info/top_level.txt +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/__init__.py +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/__init__.py +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/__main__.py +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/shell.py +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/db/postgres.py +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/paths.py +0 -0
- {backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/run.py +0 -0
|
@@ -51,7 +51,9 @@ def is_image_ignored(container: str, images_no_backup_required: list[str]) -> bo
|
|
|
51
51
|
return any(pat in img for pat in images_no_backup_required)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def volume_is_fully_ignored(
|
|
54
|
+
def volume_is_fully_ignored(
|
|
55
|
+
containers: list[str], images_no_backup_required: list[str]
|
|
56
|
+
) -> bool:
|
|
55
57
|
"""
|
|
56
58
|
Skip file backup only if all containers linked to the volume are ignored.
|
|
57
59
|
"""
|
|
@@ -178,6 +180,8 @@ def main() -> int:
|
|
|
178
180
|
print("Finished volume backups.", flush=True)
|
|
179
181
|
|
|
180
182
|
print("Handling Docker Compose services...", flush=True)
|
|
181
|
-
handle_docker_compose_services(
|
|
183
|
+
handle_docker_compose_services(
|
|
184
|
+
args.compose_dir, args.docker_compose_hard_restart_required
|
|
185
|
+
)
|
|
182
186
|
|
|
183
187
|
return 0
|
|
@@ -2,22 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import os
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _default_repo_name() -> str:
|
|
9
|
-
"""
|
|
10
|
-
Derive the repository name from the folder that contains `src/`.
|
|
11
|
-
|
|
12
|
-
Expected layout:
|
|
13
|
-
<repo-root>/src/baudolo/backup/cli.py
|
|
14
|
-
|
|
15
|
-
=> parents[0]=backup, [1]=baudolo, [2]=src, [3]=repo-root
|
|
16
|
-
"""
|
|
17
|
-
try:
|
|
18
|
-
return Path(__file__).resolve().parents[3].name
|
|
19
|
-
except Exception:
|
|
20
|
-
return "backup-docker-to-local"
|
|
21
5
|
|
|
22
6
|
|
|
23
7
|
def parse_args() -> argparse.Namespace:
|
|
@@ -41,7 +25,7 @@ def parse_args() -> argparse.Namespace:
|
|
|
41
25
|
|
|
42
26
|
p.add_argument(
|
|
43
27
|
"--repo-name",
|
|
44
|
-
default=
|
|
28
|
+
default="backup-docker-to-local",
|
|
45
29
|
help="Backup repo folder name under <backups-dir>/<machine-id>/ (default: git repo folder name)",
|
|
46
30
|
)
|
|
47
31
|
p.add_argument(
|
|
@@ -51,8 +35,8 @@ def parse_args() -> argparse.Namespace:
|
|
|
51
35
|
)
|
|
52
36
|
p.add_argument(
|
|
53
37
|
"--backups-dir",
|
|
54
|
-
default="/
|
|
55
|
-
help="Backup root directory (default: /
|
|
38
|
+
default="/var/lib/backup/",
|
|
39
|
+
help="Backup root directory (default: /var/lib/backup/)",
|
|
56
40
|
)
|
|
57
41
|
|
|
58
42
|
p.add_argument(
|
|
@@ -10,7 +10,9 @@ def hard_restart_docker_services(dir_path: str) -> None:
|
|
|
10
10
|
subprocess.run(["docker-compose", "up", "-d"], cwd=dir_path, check=True)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def handle_docker_compose_services(
|
|
13
|
+
def handle_docker_compose_services(
|
|
14
|
+
parent_directory: str, hard_restart_required: list[str]
|
|
15
|
+
) -> None:
|
|
14
16
|
for entry in os.scandir(parent_directory):
|
|
15
17
|
if not entry.is_dir():
|
|
16
18
|
continue
|
|
@@ -5,9 +5,12 @@ import pathlib
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
7
|
import pandas
|
|
8
|
+
import logging
|
|
8
9
|
|
|
9
10
|
from .shell import BackupException, execute_shell_command
|
|
10
11
|
|
|
12
|
+
log = logging.getLogger(__name__)
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
def get_instance(container: str, database_containers: list[str]) -> str:
|
|
13
16
|
if container in database_containers:
|
|
@@ -15,7 +18,9 @@ def get_instance(container: str, database_containers: list[str]) -> str:
|
|
|
15
18
|
return re.split(r"(_|-)(database|db|postgres)", container)[0]
|
|
16
19
|
|
|
17
20
|
|
|
18
|
-
def fallback_pg_dumpall(
|
|
21
|
+
def fallback_pg_dumpall(
|
|
22
|
+
container: str, username: str, password: str, out_file: str
|
|
23
|
+
) -> None:
|
|
19
24
|
cmd = (
|
|
20
25
|
f"PGPASSWORD={password} docker exec -i {container} "
|
|
21
26
|
f"pg_dumpall -U {username} -h localhost > {out_file}"
|
|
@@ -34,7 +39,8 @@ def backup_database(
|
|
|
34
39
|
instance_name = get_instance(container, database_containers)
|
|
35
40
|
entries = databases_df.loc[databases_df["instance"] == instance_name]
|
|
36
41
|
if entries.empty:
|
|
37
|
-
|
|
42
|
+
log.warning("No entry found for instance '%s'", instance_name)
|
|
43
|
+
return
|
|
38
44
|
|
|
39
45
|
out_dir = os.path.join(volume_dir, "sql")
|
|
40
46
|
pathlib.Path(out_dir).mkdir(parents=True, exist_ok=True)
|
|
@@ -68,6 +74,9 @@ def backup_database(
|
|
|
68
74
|
execute_shell_command(cmd)
|
|
69
75
|
except BackupException as e:
|
|
70
76
|
print(f"pg_dump failed: {e}", flush=True)
|
|
71
|
-
print(
|
|
77
|
+
print(
|
|
78
|
+
f"Falling back to pg_dumpall for instance '{instance_name}'",
|
|
79
|
+
flush=True,
|
|
80
|
+
)
|
|
72
81
|
fallback_pg_dumpall(container, user, password, cluster_file)
|
|
73
82
|
continue
|
|
@@ -37,7 +37,9 @@ def change_containers_status(containers: list[str], status: str) -> None:
|
|
|
37
37
|
def docker_volume_exists(volume: str) -> bool:
|
|
38
38
|
# Avoid throwing exceptions for exists checks.
|
|
39
39
|
try:
|
|
40
|
-
execute_shell_command(
|
|
40
|
+
execute_shell_command(
|
|
41
|
+
f"docker volume inspect {volume} >/dev/null 2>&1 && echo OK"
|
|
42
|
+
)
|
|
41
43
|
return True
|
|
42
44
|
except Exception:
|
|
43
45
|
return False
|
|
@@ -13,7 +13,9 @@ def get_storage_path(volume_name: str) -> str:
|
|
|
13
13
|
return f"{path}/"
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def get_last_backup_dir(
|
|
16
|
+
def get_last_backup_dir(
|
|
17
|
+
versions_dir: str, volume_name: str, current_backup_dir: str
|
|
18
|
+
) -> str | None:
|
|
17
19
|
versions = sorted(os.listdir(versions_dir), reverse=True)
|
|
18
20
|
for version in versions:
|
|
19
21
|
candidate = os.path.join(versions_dir, version, volume_name, "files", "")
|
|
@@ -37,6 +39,8 @@ def backup_volume(versions_dir: str, volume_name: str, volume_dir: str) -> None:
|
|
|
37
39
|
execute_shell_command(cmd)
|
|
38
40
|
except BackupException as e:
|
|
39
41
|
if "file has vanished" in str(e):
|
|
40
|
-
print(
|
|
42
|
+
print(
|
|
43
|
+
"Warning: Some files vanished before transfer. Continuing.", flush=True
|
|
44
|
+
)
|
|
41
45
|
else:
|
|
42
46
|
raise
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = ["main"]
|
{backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/__main__.py
RENAMED
|
@@ -66,7 +66,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
66
66
|
# ------------------------------------------------------------------
|
|
67
67
|
# mariadb
|
|
68
68
|
# ------------------------------------------------------------------
|
|
69
|
-
p_mdb = sub.add_parser(
|
|
69
|
+
p_mdb = sub.add_parser(
|
|
70
|
+
"mariadb", help="Restore a single MariaDB/MySQL-compatible dump"
|
|
71
|
+
)
|
|
70
72
|
_add_common_backup_args(p_mdb)
|
|
71
73
|
p_mdb.add_argument("--container", required=True)
|
|
72
74
|
p_mdb.add_argument("--db-name", required=True)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Database restore handlers (Postgres, MariaDB/MySQL)."""
|
{backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/db/mariadb.py
RENAMED
|
@@ -23,7 +23,9 @@ exit 42
|
|
|
23
23
|
raise RuntimeError("empty client detection output")
|
|
24
24
|
return out
|
|
25
25
|
except Exception as e:
|
|
26
|
-
print(
|
|
26
|
+
print(
|
|
27
|
+
"ERROR: neither 'mariadb' nor 'mysql' found in container.", file=sys.stderr
|
|
28
|
+
)
|
|
27
29
|
raise e
|
|
28
30
|
|
|
29
31
|
|
|
@@ -47,7 +49,14 @@ def restore_mariadb_sql(
|
|
|
47
49
|
# MariaDB 11 images may not contain the mysql binary at all.
|
|
48
50
|
docker_exec(
|
|
49
51
|
container,
|
|
50
|
-
[
|
|
52
|
+
[
|
|
53
|
+
client,
|
|
54
|
+
"-u",
|
|
55
|
+
user,
|
|
56
|
+
f"--password={password}",
|
|
57
|
+
"-e",
|
|
58
|
+
"SET FOREIGN_KEY_CHECKS=0;",
|
|
59
|
+
],
|
|
51
60
|
)
|
|
52
61
|
|
|
53
62
|
result = docker_exec(
|
|
@@ -80,10 +89,19 @@ def restore_mariadb_sql(
|
|
|
80
89
|
|
|
81
90
|
docker_exec(
|
|
82
91
|
container,
|
|
83
|
-
[
|
|
92
|
+
[
|
|
93
|
+
client,
|
|
94
|
+
"-u",
|
|
95
|
+
user,
|
|
96
|
+
f"--password={password}",
|
|
97
|
+
"-e",
|
|
98
|
+
"SET FOREIGN_KEY_CHECKS=1;",
|
|
99
|
+
],
|
|
84
100
|
)
|
|
85
101
|
|
|
86
102
|
with open(sql_path, "rb") as f:
|
|
87
|
-
docker_exec(
|
|
103
|
+
docker_exec(
|
|
104
|
+
container, [client, "-u", user, f"--password={password}", db_name], stdin=f
|
|
105
|
+
)
|
|
88
106
|
|
|
89
107
|
print(f"MariaDB/MySQL restore complete for db '{db_name}'.")
|
|
@@ -6,7 +6,9 @@ import sys
|
|
|
6
6
|
from .run import run, docker_volume_exists
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
def restore_volume_files(
|
|
9
|
+
def restore_volume_files(
|
|
10
|
+
volume_name: str, backup_files_dir: str, *, rsync_image: str
|
|
11
|
+
) -> int:
|
|
10
12
|
if not os.path.isdir(backup_files_dir):
|
|
11
13
|
print(f"ERROR: backup files dir not found: {backup_files_dir}", file=sys.stderr)
|
|
12
14
|
return 2
|
|
@@ -2,21 +2,24 @@ import pandas as pd
|
|
|
2
2
|
import argparse
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
def check_and_add_entry(file_path, instance, database, username, password):
|
|
6
7
|
# Check if the file exists and is not empty
|
|
7
8
|
if os.path.exists(file_path) and os.path.getsize(file_path) > 0:
|
|
8
9
|
# Read the existing CSV file with header
|
|
9
|
-
df = pd.read_csv(file_path, sep=
|
|
10
|
+
df = pd.read_csv(file_path, sep=";")
|
|
10
11
|
else:
|
|
11
12
|
# Create a new DataFrame with columns if file does not exist
|
|
12
|
-
df = pd.DataFrame(columns=[
|
|
13
|
+
df = pd.DataFrame(columns=["instance", "database", "username", "password"])
|
|
13
14
|
|
|
14
15
|
# Check if the entry exists and remove it
|
|
15
16
|
mask = (
|
|
16
|
-
(df[
|
|
17
|
-
(
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
(df["instance"] == instance)
|
|
18
|
+
& (
|
|
19
|
+
(df["database"] == database)
|
|
20
|
+
| (((df["database"].isna()) | (df["database"] == "")) & (database == ""))
|
|
21
|
+
)
|
|
22
|
+
& (df["username"] == username)
|
|
20
23
|
)
|
|
21
24
|
|
|
22
25
|
if not df[mask].empty:
|
|
@@ -26,25 +29,40 @@ def check_and_add_entry(file_path, instance, database, username, password):
|
|
|
26
29
|
print("Adding new entry.")
|
|
27
30
|
|
|
28
31
|
# Create a new DataFrame for the new entry
|
|
29
|
-
new_entry = pd.DataFrame(
|
|
32
|
+
new_entry = pd.DataFrame(
|
|
33
|
+
[
|
|
34
|
+
{
|
|
35
|
+
"instance": instance,
|
|
36
|
+
"database": database,
|
|
37
|
+
"username": username,
|
|
38
|
+
"password": password,
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
)
|
|
30
42
|
|
|
31
43
|
# Add (or replace) the entry using concat
|
|
32
44
|
df = pd.concat([df, new_entry], ignore_index=True)
|
|
33
45
|
|
|
34
46
|
# Save the updated CSV file
|
|
35
|
-
df.to_csv(file_path, sep=
|
|
47
|
+
df.to_csv(file_path, sep=";", index=False)
|
|
48
|
+
|
|
36
49
|
|
|
37
50
|
def main():
|
|
38
|
-
parser = argparse.ArgumentParser(
|
|
51
|
+
parser = argparse.ArgumentParser(
|
|
52
|
+
description="Check and replace (or add) a database entry in a CSV file."
|
|
53
|
+
)
|
|
39
54
|
parser.add_argument("file_path", help="Path to the CSV file")
|
|
40
55
|
parser.add_argument("instance", help="Database instance")
|
|
41
56
|
parser.add_argument("database", help="Database name")
|
|
42
57
|
parser.add_argument("username", help="Username")
|
|
43
|
-
parser.add_argument("password", nargs=
|
|
58
|
+
parser.add_argument("password", nargs="?", default="", help="Password (optional)")
|
|
44
59
|
|
|
45
60
|
args = parser.parse_args()
|
|
46
61
|
|
|
47
|
-
check_and_add_entry(
|
|
62
|
+
check_and_add_entry(
|
|
63
|
+
args.file_path, args.instance, args.database, args.username, args.password
|
|
64
|
+
)
|
|
65
|
+
|
|
48
66
|
|
|
49
67
|
if __name__ == "__main__":
|
|
50
68
|
main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__all__ = ["main"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Database restore handlers (Postgres, MariaDB/MySQL)."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/__init__.py
RENAMED
|
File without changes
|
{backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/backup/__main__.py
RENAMED
|
File without changes
|
|
File without changes
|
{backup_docker_to_local-1.0.0 → backup_docker_to_local-1.1.0}/src/baudolo/restore/db/postgres.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|