Skip to content

Commit dc743c9

Browse files
committed
add logging improvement
1 parent 189d422 commit dc743c9

File tree

5 files changed

+154
-15
lines changed

5 files changed

+154
-15
lines changed

backup_and_restore_database/backup_database/.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ DB_PASSWORD=
77
AWS_ENDPOINT_URL=
88
AWS_ACCESS_KEY_ID=
99
AWS_SECRET_ACCESS_KEY=
10-
AWS_DEFAULT_REGION=ca-central-1
1110
BUCKET_NAME=
Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22
import subprocess
33
import sys
4-
from datetime import date, datetime
4+
from datetime import datetime, date
5+
import threading
56

67
DB_HOST = os.environ["DB_HOST"]
78
DB_PORT = os.environ["DB_PORT"]
@@ -11,6 +12,12 @@
1112
ENDPOINT_URL = os.environ["AWS_ENDPOINT_URL"]
1213
S3_PATH = f"s3://{os.getenv('BUCKET_NAME')}/{date.today()}-{DB_NAME}.dump"
1314
os.environ["PGPASSWORD"] = DB_PASSWORD
15+
16+
def log_stream(stream, prefix):
17+
"""Stream stderr output with timestamps"""
18+
for line in stream:
19+
print(f"{datetime.now()} [{prefix}] {line.decode().strip()}")
20+
1421
pg_dump_cmd = [
1522
"pg_dump",
1623
"-h", DB_HOST,
@@ -28,22 +35,36 @@
2835
"--expected-size", "60000000000"
2936
]
3037

31-
print("Streaming dump into S3 bucket...")
38+
print(f"Starting backup to {S3_PATH} at {datetime.now()}")
39+
40+
41+
42+
try:
43+
dump_proc = subprocess.Popen(pg_dump_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
44+
45+
aws_proc = subprocess.Popen(aws_cp_cmd, stdin=dump_proc.stdout, stderr=subprocess.PIPE)
46+
47+
dump_proc.stdout.close()
3248

33-
dump_proc = subprocess.Popen(pg_dump_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
49+
dump_thread = threading.Thread(target=log_stream, args=(dump_proc.stderr, "pg_dump"))
50+
aws_thread = threading.Thread(target=log_stream, args=(aws_proc.stderr, "aws"))
3451

35-
aws_proc = subprocess.Popen(aws_cp_cmd, stdin=dump_proc.stdout, stderr=subprocess.PIPE)
52+
dump_thread.start()
53+
aws_thread.start()
3654

37-
dump_proc.stdout.close()
55+
dump_thread.join()
56+
aws_thread.join()
3857

39-
for line in dump_proc.stderr:
40-
print(f"{datetime.now()} {line.decode().strip()}")
58+
if dump_proc.wait() != 0:
59+
print("pg_dump failed!")
60+
sys.exit(1)
61+
if aws_proc.wait() != 0:
62+
print("AWS upload failed!")
63+
sys.exit(1)
4164

42-
# Wait for both to finish and get final stderr from aws
43-
_, aws_stderr = aws_proc.communicate()
65+
print(f"Backup completed successfully at {datetime.now()}")
66+
print(f"Saved to: {S3_PATH}")
4467

45-
if aws_proc.returncode != 0:
46-
print("[aws s3 cp] ERROR:")
47-
print(aws_stderr.decode())
48-
if dump_proc.wait() != 0:
49-
print("pg_dump exited with a non-zero status.")
68+
except Exception as e:
69+
print(f"Backup failed at {datetime.now()}: {str(e)}")
70+
sys.exit(1)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
DB_HOST=
2+
DB_PORT=
3+
DB_NAME=
4+
DB_USER=
5+
DB_PASSWORD=
6+
7+
AWS_ENDPOINT_URL=
8+
AWS_ACCESS_KEY_ID=
9+
AWS_SECRET_ACCESS_KEY=
10+
BUCKET_NAME=
11+
BACKUP_FILE=
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
FROM postgres:17-bookworm as pgclient
2+
3+
FROM python:3.12-slim
4+
5+
COPY --from=pgclient /usr/lib/postgresql/17/bin/pg_restore /usr/bin/pg_restore
6+
COPY --from=pgclient /usr/lib/postgresql/17/bin/psql /usr/bin/psql
7+
8+
RUN apt-get update && \
9+
apt-get install -y \
10+
curl \
11+
unzip \
12+
&& \
13+
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
14+
unzip awscliv2.zip && \
15+
./aws/install && \
16+
rm -rf awscliv2.zip aws /var/lib/apt/lists/*
17+
18+
ENV PYTHONDONTWRITEBYTECODE=1 \
19+
PYTHONUNBUFFERED=1 \
20+
AWS_DEFAULT_REGION=ca-central-1 \
21+
AWS_RESPONSE_CHECKSUM_VALIDATION=when_required \
22+
AWS_REQUEST_CHECKSUM_CALCULATION=when_required
23+
24+
RUN useradd -m appuser && \
25+
mkdir -p /app && \
26+
chown -R appuser:appuser /app
27+
28+
WORKDIR /app
29+
USER appuser
30+
31+
# Copy restore script
32+
COPY --chown=appuser:appuser restore_database_from_s3.py .
33+
34+
CMD ["python3", "restore_database_from_s3.py"]
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import os
2+
import subprocess
3+
import sys
4+
from datetime import datetime
5+
import threading
6+
7+
DB_HOST = os.environ["DB_HOST"]
8+
DB_PORT = os.environ["DB_PORT"]
9+
DB_NAME = os.environ["DB_NAME"]
10+
DB_USER = os.environ["DB_USER"]
11+
DB_PASSWORD = os.environ["DB_PASSWORD"]
12+
ENDPOINT_URL = os.environ["AWS_ENDPOINT_URL"]
13+
BACKUP_FILE = os.environ["BACKUP_FILE"] # Format: "<date>-<database-name>.dump"
14+
BUCKET_NAME = os.environ["BUCKET_NAME"]
15+
16+
S3_PATH = f"s3://{BUCKET_NAME}/{BACKUP_FILE}"
17+
os.environ["PGPASSWORD"] = DB_PASSWORD
18+
19+
def log_stream(stream, prefix):
20+
for line in stream:
21+
print(f"{datetime.now()} [{prefix}] {line.decode().strip()}")
22+
23+
aws_cp_cmd = [
24+
"aws", "s3", "cp", S3_PATH, "-",
25+
"--endpoint-url", ENDPOINT_URL,
26+
"--expected-size", "60000000000"
27+
]
28+
29+
pg_restore_cmd = [
30+
"pg_restore",
31+
"-h", DB_HOST,
32+
"-p", DB_PORT,
33+
"-U", DB_USER,
34+
"-d", DB_NAME,
35+
"-Fc",
36+
"-v",
37+
"--clean",
38+
"--if-exists"
39+
]
40+
41+
print(f"Starting restore from {S3_PATH} to {DB_NAME}...")
42+
43+
try:
44+
aws_proc = subprocess.Popen(aws_cp_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
45+
46+
restore_proc = subprocess.Popen(
47+
pg_restore_cmd,
48+
stdin=aws_proc.stdout,
49+
stderr=subprocess.PIPE
50+
)
51+
aws_proc.stdout.close()
52+
53+
54+
aws_thread = threading.Thread(target=log_stream, args=(aws_proc.stderr, "aws"))
55+
restore_thread = threading.Thread(target=log_stream, args=(restore_proc.stderr, "pg_restore"))
56+
aws_thread.start()
57+
restore_thread.start()
58+
59+
aws_thread.join()
60+
restore_thread.join()
61+
62+
if aws_proc.wait() != 0:
63+
print("AWS download failed!")
64+
sys.exit(1)
65+
66+
if restore_proc.wait() != 0:
67+
print("pg_restore failed!")
68+
sys.exit(1)
69+
70+
print("Restore completed successfully!")
71+
72+
except Exception as e:
73+
print(f"Restore failed: {str(e)}")
74+
sys.exit(1)

0 commit comments

Comments
 (0)