Skip to content

Commit 2f6d203

Browse files
committed
Add postgresql string
1 parent 87cfead commit 2f6d203

File tree

12 files changed

+240
-65
lines changed

12 files changed

+240
-65
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ RUN groupadd -g "${PGID:-0}" -o fix \
6868
python3-pip \
6969
redis-tools \
7070
mysql-client \
71+
postgresql-client \
7172
&& dpkg -i /usr/local/tmp/arangodb3-client_*.deb \
7273
&& ln -s /usr/bin/busybox /usr/local/bin/vi \
7374
&& ln -s /usr/bin/busybox /usr/local/bin/wget \

README.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
1-
# `fixbackup` - FIX Database Backup System
1+
# `fixbackup` - Fix Database Backup and Restore System
2+
3+
A wrapper tool packaged as a container, that runs as a job, creates and restores backups of various databases, and uploads them to an S3 bucket.
4+
5+
## Docker
6+
7+
```
8+
docker run --it --rm -v /path/to/backups:/backups someengineering/fixbackup --type daily ...
9+
```
210

311
## Usage
412

513
```bash
6-
usage: fixbackup [-h] [--backup-directory BACKUP_DIRECTORY] [--verbose | --trace | --quiet] [--s3-bucket S3_BUCKET] --type {daily,weekly,monthly,yearly}
7-
[--set-lifecycle-policy] [--redis-host REDIS_HOST] [--redis-port REDIS_PORT] [--redis-username REDIS_USERNAME]
8-
[--redis-password REDIS_PASSWORD] [--redis-database-number REDIS_DATABASE_NUMBER] [--redis-cli-args REDIS_CLI_ARGS]
9-
[--mysql-host MYSQL_HOST] [--mysql-port MYSQL_PORT] [--mysql-user MYSQL_USER] [--mysql-password MYSQL_PASSWORD]
10-
[--mysqldump-args MYSQLDUMP_ARGS] [--arangodb-host ARANGODB_HOST] [--arangodb-port ARANGODB_PORT] [--arangodb-username ARANGODB_USERNAME]
11-
[--arangodb-password ARANGODB_PASSWORD] [--arangodb-database ARANGODB_DATABASE] [--arangodump-args ARANGODUMP_ARGS]
14+
usage: fixbackup [-h] [--backup-directory BACKUP_DIRECTORY] [-n ENVIRONMENT] [--sleep] [--restore] [--verbose | --trace | --quiet] [--s3-bucket S3_BUCKET] --type {daily,weekly,monthly,yearly} [--set-lifecycle-policy] [--redis-host REDIS_HOST]
15+
[--redis-port REDIS_PORT] [--redis-username REDIS_USERNAME] [--redis-password REDIS_PASSWORD] [--redis-database-number REDIS_DATABASE_NUMBER] [--redis-cli-args REDIS_CLI_ARGS] [--redis-tls] [--redis-tls-insecure] [--mysql-host MYSQL_HOST]
16+
[--mysql-port MYSQL_PORT] [--mysql-user MYSQL_USER] [--mysql-password MYSQL_PASSWORD] [--mysql-database MYSQL_DATABASE] [--mysqldump-args MYSQLDUMP_ARGS] [--pg-host PG_HOST] [--pg-port PG_PORT] [--pg-user PG_USER] [--pg-password PG_PASSWORD]
17+
[--pg-database PG_DATABASE] [--pg-dump-args PG_DUMP_ARGS] [--arangodb-host ARANGODB_HOST] [--arangodb-port ARANGODB_PORT] [--arangodb-username ARANGODB_USERNAME] [--arangodb-password ARANGODB_PASSWORD] [--arangodb-database ARANGODB_DATABASE]
18+
[--arangodump-args ARANGODUMP_ARGS] [--arangodb-tls]
1219

13-
FIX Database Backup System
20+
Fix Database Backup and Restore System
1421

1522
options:
1623
-h, --help show this help message and exit
1724
--backup-directory BACKUP_DIRECTORY
1825
Directory where backups are created
26+
-n ENVIRONMENT, --name ENVIRONMENT
27+
Name of the environment
28+
--sleep Don't do anything, just sleep forever
29+
--restore Restore databases from directory
1930
--verbose, -v Verbose logging
2031
--trace Trage logging
2132
--quiet Only log errors
@@ -37,6 +48,8 @@ options:
3748
Redis database number
3849
--redis-cli-args REDIS_CLI_ARGS
3950
Extra arguments to pass to redis-cli
51+
--redis-tls Redis uses TLS
52+
--redis-tls-insecure Redis uses TLS without verifying the certificate
4053
--mysql-host MYSQL_HOST
4154
MySQL host
4255
--mysql-port MYSQL_PORT
@@ -45,8 +58,19 @@ options:
4558
MySQL user
4659
--mysql-password MYSQL_PASSWORD
4760
MySQL password
61+
--mysql-database MYSQL_DATABASE
62+
MySQL database
4863
--mysqldump-args MYSQLDUMP_ARGS
4964
Extra arguments to pass to mysqldump
65+
--pg-host PG_HOST PostgreSQL host
66+
--pg-port PG_PORT PostgreSQL port
67+
--pg-user PG_USER PostgreSQL user
68+
--pg-password PG_PASSWORD
69+
PostgreSQL password
70+
--pg-database PG_DATABASE
71+
PostgreSQL database
72+
--pg-dump-args PG_DUMP_ARGS
73+
Extra arguments to pass to pg_dump
5074
--arangodb-host ARANGODB_HOST
5175
ArangoDB host
5276
--arangodb-port ARANGODB_PORT
@@ -59,4 +83,5 @@ options:
5983
ArangoDB database to dump
6084
--arangodump-args ARANGODUMP_ARGS
6185
Extra arguments to pass to arangodump
86+
--arangodb-tls ArangoDB uses TLS
6287
```

fixbackup/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111
__author__ = "Some Engineering Inc."
1212
__license__ = "Apache 2.0"
1313
__copyright__ = "Copyright © 2023 Some Engineering Inc."
14-
__version__ = "0.0.7"
14+
__version__ = "0.0.10"

fixbackup/__main__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import sys
22
import os
3+
import time
34
from pathlib import Path
45
from typing import List
56
from .logger import add_args as logging_add_args, log
@@ -12,11 +13,24 @@
1213
def main() -> None:
1314
args = parse_args([logging_add_args, s3_add_args, *backup_add_args])
1415
exit_code = 0
15-
log.info("Starting FIX Databases Backup System")
16+
log.info("Starting Fix Databases Backup System")
1617

1718
if not verify_binaries():
1819
sys.exit(1)
1920

21+
if args.sleep:
22+
# This option is used to keep the container running for debugging purposes.
23+
# It allows you to connect to it inside of e.g. a K8s environment
24+
# and manually test the backup process. Alternatively, you could
25+
# override the entrypoint of the container and sleep indefinitely.
26+
log.info("Sleeping forever")
27+
try:
28+
while True:
29+
time.sleep(300)
30+
finally:
31+
log.info("Shutdown complete")
32+
sys.exit(0)
33+
2034
backup_directory = Path(args.backup_directory)
2135
rmdir_backup_directory = True
2236
if backup_directory.exists() and not backup_directory.is_dir():

fixbackup/args.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
def parse_args(add_args: List[Callable[[ArgumentParser], None]]) -> Namespace:
7-
arg_parser = ArgumentParser(prog="fixbackup", description="FIX Database Backup System")
7+
arg_parser = ArgumentParser(prog="fixbackup", description="Fix Database Backup and Restore System")
88
arg_parser.add_argument(
99
"--backup-directory",
1010
help="Directory where backups are created",
@@ -19,6 +19,20 @@ def parse_args(add_args: List[Callable[[ArgumentParser], None]]) -> Namespace:
1919
help="Name of the environment",
2020
default=os.getenv("FIX_ENVIRONMENT", "dev"),
2121
)
22+
arg_parser.add_argument(
23+
"--sleep",
24+
help="Don't do anything, just sleep forever",
25+
dest="sleep",
26+
action="store_true",
27+
default=False,
28+
)
29+
arg_parser.add_argument(
30+
"--restore",
31+
help="Restore databases from directory",
32+
dest="restore",
33+
action="store_true",
34+
default=False,
35+
)
2236

2337
for add_arg in add_args:
2438
add_arg(arg_parser)

fixbackup/backup/__init__.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
from typing import List, Tuple
55
from .redis import backup as redis_backup, add_args as redis_add_args
66
from .mysql import backup as mysql_backup, add_args as mysql_add_args
7+
from .postgresql import backup as pg_backup, add_args as postgresql_add_args
78
from .arangodb import backup as arangodb_backup, add_args as arangodb_add_args
89
from ..utils import valid_hostname, valid_ip, valid_dbname
910

10-
add_args = [redis_add_args, mysql_add_args, arangodb_add_args]
11+
add_args = [redis_add_args, mysql_add_args, postgresql_add_args, arangodb_add_args]
1112

1213

1314
def backup(args: Namespace, backup_directory: Path) -> Tuple[List[Path], bool]:
@@ -39,14 +40,29 @@ def backup(args: Namespace, backup_directory: Path) -> Tuple[List[Path], bool]:
3940
else:
4041
all_success = False
4142

43+
if args.pg_host and (valid_hostname(args.pg_host) or valid_ip(args.pg_host)):
44+
if args.pg_database:
45+
db = str(args.pg_database)
46+
if not valid_dbname(db):
47+
raise ValueError(f"Invalid database name: {db}")
48+
else:
49+
db = "all"
50+
pg_backup_file = backup_directory / f"{environment}-{date_prefix}-postgresql-{args.pg_host}-{db}.sql.gz"
51+
if pg_backup(args, pg_backup_file):
52+
result.append(pg_backup_file)
53+
else:
54+
all_success = False
55+
4256
if args.arangodb_host and (valid_hostname(args.arangodb_host)):
4357
if args.arangodb_database:
4458
db = str(args.arangodb_database)
4559
if not valid_dbname(db):
4660
raise ValueError(f"Invalid database name: {db}")
4761
else:
4862
db = "all"
49-
arangodb_backup_file = backup_directory / f"{environment}-{date_prefix}-arangodb-{args.arangodb_host}-{db}.tar.gz"
63+
arangodb_backup_file = (
64+
backup_directory / f"{environment}-{date_prefix}-arangodb-{args.arangodb_host}-{db}.tar.gz"
65+
)
5066
if arangodb_backup(args, arangodb_backup_file):
5167
result.append(arangodb_backup_file)
5268
else:

fixbackup/backup/postgresql.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import os
2+
import subprocess
3+
from pathlib import Path
4+
from argparse import ArgumentParser, Namespace
5+
from ..utils import BackupFile
6+
from ..logger import log
7+
8+
9+
def add_args(arg_parser: ArgumentParser) -> None:
10+
arg_parser.add_argument(
11+
"--pg-host",
12+
help="PostgreSQL host",
13+
dest="pg_host",
14+
type=str,
15+
default=os.getenv("PG_HOST"),
16+
)
17+
18+
arg_parser.add_argument(
19+
"--pg-port",
20+
help="PostgreSQL port",
21+
dest="pg_port",
22+
type=int,
23+
default=os.getenv("PG_PORT", 5432),
24+
)
25+
26+
arg_parser.add_argument(
27+
"--pg-user",
28+
help="PostgreSQL user",
29+
dest="pg_user",
30+
type=str,
31+
default=os.getenv("PG_USER", "postgres"),
32+
)
33+
34+
arg_parser.add_argument(
35+
"--pg-password",
36+
help="PostgreSQL password",
37+
dest="pg_password",
38+
type=str,
39+
default=os.getenv("PG_PASSWORD"),
40+
)
41+
42+
arg_parser.add_argument(
43+
"--pg-database",
44+
help="PostgreSQL database",
45+
dest="pg_database",
46+
type=str,
47+
default=os.getenv("PG_DATABASE"),
48+
)
49+
50+
arg_parser.add_argument(
51+
"--pg-dump-args",
52+
help="Extra arguments to pass to pg_dump",
53+
dest="pg_dump_args",
54+
action="append",
55+
default=[],
56+
)
57+
58+
59+
def backup(args: Namespace, backup_file_path: Path, timeout: int = 900, compress: bool = True) -> bool:
60+
log.info("Starting PostgreSQL backup...")
61+
62+
if not args.pg_host:
63+
return False
64+
65+
env = os.environ.copy()
66+
command = [
67+
"pg_dump",
68+
"-w",
69+
"-c",
70+
"--if-exists",
71+
"--inserts",
72+
"-h",
73+
str(args.pg_host),
74+
"-p",
75+
str(args.pg_port),
76+
"-U",
77+
str(args.pg_user),
78+
*args.pg_dump_args,
79+
]
80+
if args.pg_database:
81+
command.append("-d")
82+
command.append(args.pg_database)
83+
else:
84+
command[0] = "pg_dumpall"
85+
86+
if args.pg_password:
87+
env["PGPASSWORD"] = args.pg_password
88+
89+
log.debug(f"Running command: {' '.join(command)}")
90+
91+
try:
92+
with BackupFile(backup_file_path, compress) as backup_fd:
93+
process = subprocess.Popen(command, stdout=backup_fd, stderr=subprocess.PIPE, env=env)
94+
_, stderr = process.communicate(timeout=timeout)
95+
96+
if process.returncode == 0:
97+
log.info(f"PostgreSQL backup completed successfully. Saved to {backup_file_path}")
98+
if stderr:
99+
log.debug(stderr.decode().strip())
100+
return True
101+
else:
102+
log.error(f"PostgreSQL backup failed with return code: {process.returncode}")
103+
if stderr:
104+
log.error(stderr.decode().strip())
105+
except subprocess.TimeoutExpired:
106+
log.error(f"PostgreSQL backup failed with timeout after {timeout} seconds")
107+
process.kill()
108+
process.communicate()
109+
110+
return False

fixbackup/logger.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -126,20 +126,15 @@ def log_to_root(message: str, *args: Any, **kwargs: Any) -> None:
126126

127127

128128
class FixLogger(Logger):
129-
def debug2(self, msg: str, *args: Any, **kwargs: Any) -> None:
130-
...
129+
def debug2(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
131130

132-
def debug3(self, msg: str, *args: Any, **kwargs: Any) -> None:
133-
...
131+
def debug3(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
134132

135-
def debug4(self, msg: str, *args: Any, **kwargs: Any) -> None:
136-
...
133+
def debug4(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
137134

138-
def debug5(self, msg: str, *args: Any, **kwargs: Any) -> None:
139-
...
135+
def debug5(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
140136

141-
def trace(self, msg: str, *args: Any, **kwargs: Any) -> None:
142-
...
137+
def trace(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
143138

144139

145140
def get_fix_logger(name: Optional[str] = None) -> FixLogger:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "fixbackup"
3-
version = "0.0.7"
3+
version = "0.0.10"
44
authors = [{name="Some Engineering Inc."}]
55
description = "FIX Database Backup System"
66
license = {file="LICENSE"}

0 commit comments

Comments
 (0)