From 7fa44596a5d357ae1dde8448c14777cbc3a758cd Mon Sep 17 00:00:00 2001 From: Praveen Date: Wed, 15 Jan 2025 19:25:12 +0530 Subject: [PATCH 1/4] Fix for Sentry --- Dockerfile | 4 +- application/backup.sh | 120 ++++++++++++++++++++++++++++++++---------- 2 files changed, 95 insertions(+), 29 deletions(-) diff --git a/Dockerfile b/Dockerfile index b8f9e5e..064fce8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,9 @@ RUN apk update \ postgresql14-client \ py3-magic \ py3-dateutil \ - s3cmd + s3cmd \ + curl \ + jq COPY application/ /data/ WORKDIR /data diff --git a/application/backup.sh b/application/backup.sh index 956b54c..66df7b7 100755 --- a/application/backup.sh +++ b/application/backup.sh @@ -1,32 +1,96 @@ #!/usr/bin/env bash +# Initialize logging with timestamp +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +# Generate UUID v4 +generate_uuid() { + if [ -f /proc/sys/kernel/random/uuid ]; then + cat /proc/sys/kernel/random/uuid + else + date +%s%N | sha256sum | head -c 32 + fi +} + +# Parse Sentry DSN +parse_sentry_dsn() { + local dsn=$1 + # Extract components using basic string manipulation + local project_id=$(echo "$dsn" | sed 's/.*\///') + local key=$(echo "$dsn" | sed 's|https://||' | sed 's/@.*//') + local host=$(echo "$dsn" | sed 's|https://[^@]*@||' | sed 's|/.*||') + echo "$project_id|$key|$host" +} + # Function to send error to Sentry -send_error_to_sentry() { +error_to_sentry() { local error_message="$1" local db_name="$2" local status_code="$3" - if [ -n "${SENTRY_DSN}" ]; then - wget -q --header="Content-Type: application/json" \ - --post-data="{ - \"message\": \"${error_message}\", - \"level\": \"error\", - \"extra\": { - \"database\": \"${db_name}\", - \"status_code\": \"${status_code}\", - \"hostname\": \"$(hostname)\" - } - }" \ - -O - "${SENTRY_DSN}" + # Check if SENTRY_DSN is set + if [ -z "${SENTRY_DSN:-}" ]; then + log "ERROR: SENTRY_DSN not set" + return 1 fi + + # Parse DSN + local dsn_parts=($(parse_sentry_dsn "$SENTRY_DSN" | tr '|' ' ')) + local project_id="${dsn_parts[0]}" + local key="${dsn_parts[1]}" + local host="${dsn_parts[2]}" + + # Generate event ID and timestamp + local event_id=$(generate_uuid) + local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z") + + # Create JSON payload + local payload=$(cat <&1) + + if [ $? -ne 0 ]; then + log "ERROR: Failed to send event to Sentry: ${response}" + return 1 + fi + + log "Error event sent to Sentry: ${error_message}" } MYNAME="postgresql-backup-restore" STATUS=0 -echo "${MYNAME}: backup: Started" +log "${MYNAME}: backup: Started" -echo "${MYNAME}: Backing up ${DB_NAME}" +log "${MYNAME}: Backing up ${DB_NAME}" start=$(date +%s) $(PGPASSWORD=${DB_USERPASSWORD} pg_dump --host=${DB_HOST} --username=${DB_USER} --create --clean ${DB_OPTIONS} --dbname=${DB_NAME} > /tmp/${DB_NAME}.sql) || STATUS=$? @@ -34,11 +98,11 @@ end=$(date +%s) if [ $STATUS -ne 0 ]; then error_message="${MYNAME}: FATAL: Backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" "${DB_NAME}" + log "${error_message}" + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" exit $STATUS else - echo "${MYNAME}: Backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds, ($(stat -c %s /tmp/${DB_NAME}.sql) bytes)." + log "${MYNAME}: Backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds, ($(stat -c %s /tmp/${DB_NAME}.sql) bytes)." fi start=$(date +%s) @@ -47,11 +111,11 @@ end=$(date +%s) if [ $STATUS -ne 0 ]; then error_message="${MYNAME}: FATAL: Compressing backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" "${DB_NAME}" + log "${error_message}" + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" exit $STATUS else - echo "${MYNAME}: Compressing backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "${MYNAME}: Compressing backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." fi start=$(date +%s) @@ -60,11 +124,11 @@ end=$(date +%s) if [ $STATUS -ne 0 ]; then error_message="${MYNAME}: FATAL: Copy backup to ${S3_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" "${DB_NAME}" + log "${error_message}" + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" exit $STATUS else - echo "${MYNAME}: Copy backup to ${S3_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "${MYNAME}: Copy backup to ${S3_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." fi if [ "${B2_BUCKET}" != "" ]; then @@ -79,12 +143,12 @@ if [ "${B2_BUCKET}" != "" ]; then end=$(date +%s) if [ $STATUS -ne 0 ]; then error_message="${MYNAME}: FATAL: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - echo "${error_message}" - send_error_to_sentry "${error_message}" "${STATUS}" + log "${error_message}" + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" exit $STATUS else - echo "${MYNAME}: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "${MYNAME}: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." fi fi -echo "${MYNAME}: backup: Completed" +log "${MYNAME}: backup: Completed" From 469ee58430362888a90b425f01906b218cd9f8c6 Mon Sep 17 00:00:00 2001 From: Praveen Date: Fri, 17 Jan 2025 12:40:55 +0530 Subject: [PATCH 2/4] Added Sentrycli and changed the method to use sentrycli --- Dockerfile | 3 ++ application/backup.sh | 112 ++++++++++++++---------------------------- 2 files changed, 39 insertions(+), 76 deletions(-) diff --git a/Dockerfile b/Dockerfile index 064fce8..683570a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,9 @@ RUN apk update \ curl \ jq +# Install sentry-cli +RUN curl -sL https://sentry.io/get-cli/ | bash + COPY application/ /data/ WORKDIR /data diff --git a/application/backup.sh b/application/backup.sh index 66df7b7..2e885a7 100755 --- a/application/backup.sh +++ b/application/backup.sh @@ -2,87 +2,41 @@ # Initialize logging with timestamp log() { - echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" + local level="${1:-INFO}" + local message="$2" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${level}: ${message}" } -# Generate UUID v4 -generate_uuid() { - if [ -f /proc/sys/kernel/random/uuid ]; then - cat /proc/sys/kernel/random/uuid - else - date +%s%N | sha256sum | head -c 32 - fi -} - -# Parse Sentry DSN -parse_sentry_dsn() { - local dsn=$1 - # Extract components using basic string manipulation - local project_id=$(echo "$dsn" | sed 's/.*\///') - local key=$(echo "$dsn" | sed 's|https://||' | sed 's/@.*//') - local host=$(echo "$dsn" | sed 's|https://[^@]*@||' | sed 's|/.*||') - echo "$project_id|$key|$host" -} - -# Function to send error to Sentry +# Sentry reporting with validation and backwards compatibility error_to_sentry() { local error_message="$1" local db_name="$2" local status_code="$3" - - # Check if SENTRY_DSN is set + + # Check if SENTRY_DSN is configured - ensures backup continues if [ -z "${SENTRY_DSN:-}" ]; then - log "ERROR: SENTRY_DSN not set" - return 1 + log "DEBUG" "Sentry logging skipped - SENTRY_DSN not configured" + return 0 fi - # Parse DSN - local dsn_parts=($(parse_sentry_dsn "$SENTRY_DSN" | tr '|' ' ')) - local project_id="${dsn_parts[0]}" - local key="${dsn_parts[1]}" - local host="${dsn_parts[2]}" - - # Generate event ID and timestamp - local event_id=$(generate_uuid) - local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z") - - # Create JSON payload - local payload=$(cat <&1) - - if [ $? -ne 0 ]; then - log "ERROR: Failed to send event to Sentry: ${response}" - return 1 + # Validate SENTRY_DSN format + if ! [[ "${SENTRY_DSN}" =~ ^https://[^@]+@[^/]+/[0-9]+$ ]]; then + log "WARN" "Invalid SENTRY_DSN format - Sentry logging will be skipped" + return 0 + fi + + # Attempt to send event to Sentry + if sentry-cli send-event \ + --message "${error_message}" \ + --level error \ + --tag "database:${db_name}" \ + --tag "status:${status_code}"; then + log "DEBUG" "Successfully sent error to Sentry - Message: ${error_message}, Database: ${db_name}, Status: ${status_code}" + else + log "WARN" "Failed to send error to Sentry, but continuing backup process" fi - log "Error event sent to Sentry: ${error_message}" + return 0 } MYNAME="postgresql-backup-restore" @@ -105,6 +59,7 @@ else log "${MYNAME}: Backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds, ($(stat -c %s /tmp/${DB_NAME}.sql) bytes)." fi +# Compression start=$(date +%s) gzip -f /tmp/${DB_NAME}.sql || STATUS=$? end=$(date +%s) @@ -118,10 +73,10 @@ else log "${MYNAME}: Compressing backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." fi +# S3 Upload start=$(date +%s) s3cmd put /tmp/${DB_NAME}.sql.gz ${S3_BUCKET} || STATUS=$? end=$(date +%s) - if [ $STATUS -ne 0 ]; then error_message="${MYNAME}: FATAL: Copy backup to ${S3_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." log "${error_message}" @@ -131,14 +86,15 @@ else log "${MYNAME}: Copy backup to ${S3_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." fi +# Backblaze B2 Upload if [ "${B2_BUCKET}" != "" ]; then start=$(date +%s) s3cmd \ - --access_key=${B2_APPLICATION_KEY_ID} \ - --secret_key=${B2_APPLICATION_KEY} \ - --host=${B2_HOST} \ - --host-bucket='%(bucket)s.'"${B2_HOST}" \ - put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz + --access_key=${B2_APPLICATION_KEY_ID} \ + --secret_key=${B2_APPLICATION_KEY} \ + --host=${B2_HOST} \ + --host-bucket='%(bucket)s.'"${B2_HOST}" \ + put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz STATUS=$? end=$(date +%s) if [ $STATUS -ne 0 ]; then @@ -151,4 +107,8 @@ if [ "${B2_BUCKET}" != "" ]; then fi fi +echo "postgresql-backup-restore: backup: Completed" + log "${MYNAME}: backup: Completed" + +exit $STATUS \ No newline at end of file From 7e026c161dd48fd94afd395270234310f55691db Mon Sep 17 00:00:00 2001 From: Praveen Date: Mon, 20 Jan 2025 11:48:05 +0530 Subject: [PATCH 3/4] Update application/backup.sh Co-authored-by: briskt <3172830+briskt@users.noreply.github.com> --- application/backup.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/application/backup.sh b/application/backup.sh index 2e885a7..9dbf2e4 100755 --- a/application/backup.sh +++ b/application/backup.sh @@ -90,11 +90,11 @@ fi if [ "${B2_BUCKET}" != "" ]; then start=$(date +%s) s3cmd \ - --access_key=${B2_APPLICATION_KEY_ID} \ - --secret_key=${B2_APPLICATION_KEY} \ - --host=${B2_HOST} \ - --host-bucket='%(bucket)s.'"${B2_HOST}" \ - put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz + --access_key=${B2_APPLICATION_KEY_ID} \ + --secret_key=${B2_APPLICATION_KEY} \ + --host=${B2_HOST} \ + --host-bucket='%(bucket)s.'"${B2_HOST}" \ + put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz STATUS=$? end=$(date +%s) if [ $STATUS -ne 0 ]; then From c193b548777de1eab30dbad6c270526b109158f0 Mon Sep 17 00:00:00 2001 From: Praveen Date: Mon, 20 Jan 2025 12:35:08 +0530 Subject: [PATCH 4/4] Fixed paramenter usage --- application/backup.sh | 115 +++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/application/backup.sh b/application/backup.sh index 9dbf2e4..04136d9 100755 --- a/application/backup.sh +++ b/application/backup.sh @@ -2,27 +2,27 @@ # Initialize logging with timestamp log() { - local level="${1:-INFO}" - local message="$2" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${level}: ${message}" + local level="$1"; + local message="$2"; + echo "[$(date '+%Y-%m-%d %H:%M:%S')] ${level}: ${message}"; } # Sentry reporting with validation and backwards compatibility error_to_sentry() { - local error_message="$1" - local db_name="$2" - local status_code="$3" + local error_message="$1"; + local db_name="$2"; + local status_code="$3"; # Check if SENTRY_DSN is configured - ensures backup continues if [ -z "${SENTRY_DSN:-}" ]; then - log "DEBUG" "Sentry logging skipped - SENTRY_DSN not configured" - return 0 + log "DEBUG" "Sentry logging skipped - SENTRY_DSN not configured"; + return 0; fi # Validate SENTRY_DSN format if ! [[ "${SENTRY_DSN}" =~ ^https://[^@]+@[^/]+/[0-9]+$ ]]; then - log "WARN" "Invalid SENTRY_DSN format - Sentry logging will be skipped" - return 0 + log "WARN" "Invalid SENTRY_DSN format - Sentry logging will be skipped"; + return 0; fi # Attempt to send event to Sentry @@ -31,84 +31,83 @@ error_to_sentry() { --level error \ --tag "database:${db_name}" \ --tag "status:${status_code}"; then - log "DEBUG" "Successfully sent error to Sentry - Message: ${error_message}, Database: ${db_name}, Status: ${status_code}" + log "DEBUG" "Successfully sent error to Sentry - Message: ${error_message}, Database: ${db_name}, Status: ${status_code}"; else - log "WARN" "Failed to send error to Sentry, but continuing backup process" + log "WARN" "Failed to send error to Sentry, but continuing backup process"; fi - return 0 + return 0; } -MYNAME="postgresql-backup-restore" -STATUS=0 +MYNAME="postgresql-backup-restore"; +STATUS=0; -log "${MYNAME}: backup: Started" +log "INFO" "${MYNAME}: backup: Started"; +log "INFO" "${MYNAME}: Backing up ${DB_NAME}"; -log "${MYNAME}: Backing up ${DB_NAME}" - -start=$(date +%s) -$(PGPASSWORD=${DB_USERPASSWORD} pg_dump --host=${DB_HOST} --username=${DB_USER} --create --clean ${DB_OPTIONS} --dbname=${DB_NAME} > /tmp/${DB_NAME}.sql) || STATUS=$? -end=$(date +%s) +start=$(date +%s); +$(PGPASSWORD=${DB_USERPASSWORD} pg_dump --host=${DB_HOST} --username=${DB_USER} --create --clean ${DB_OPTIONS} --dbname=${DB_NAME} > /tmp/${DB_NAME}.sql) || STATUS=$?; +end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - log "${error_message}" - error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" - exit $STATUS + error_message="${MYNAME}: FATAL: Backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - log "${MYNAME}: Backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds, ($(stat -c %s /tmp/${DB_NAME}.sql) bytes)." + log "INFO" "${MYNAME}: Backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds, ($(stat -c %s /tmp/${DB_NAME}.sql) bytes)."; fi # Compression -start=$(date +%s) -gzip -f /tmp/${DB_NAME}.sql || STATUS=$? -end=$(date +%s) +start=$(date +%s); +gzip -f /tmp/${DB_NAME}.sql || STATUS=$?; +end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Compressing backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - log "${error_message}" - error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" - exit $STATUS + error_message="${MYNAME}: FATAL: Compressing backup of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - log "${MYNAME}: Compressing backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "INFO" "${MYNAME}: Compressing backup of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds."; fi # S3 Upload -start=$(date +%s) -s3cmd put /tmp/${DB_NAME}.sql.gz ${S3_BUCKET} || STATUS=$? -end=$(date +%s) +start=$(date +%s); +s3cmd put /tmp/${DB_NAME}.sql.gz ${S3_BUCKET} || STATUS=$?; +end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Copy backup to ${S3_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - log "${error_message}" - error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" - exit $STATUS + error_message="${MYNAME}: FATAL: Copy backup to ${S3_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - log "${MYNAME}: Copy backup to ${S3_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "INFO" "${MYNAME}: Copy backup to ${S3_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds."; fi # Backblaze B2 Upload if [ "${B2_BUCKET}" != "" ]; then - start=$(date +%s) + start=$(date +%s); s3cmd \ - --access_key=${B2_APPLICATION_KEY_ID} \ - --secret_key=${B2_APPLICATION_KEY} \ - --host=${B2_HOST} \ - --host-bucket='%(bucket)s.'"${B2_HOST}" \ - put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz - STATUS=$? - end=$(date +%s) + --access_key=${B2_APPLICATION_KEY_ID} \ + --secret_key=${B2_APPLICATION_KEY} \ + --host=${B2_HOST} \ + --host-bucket='%(bucket)s.'"${B2_HOST}" \ + put /tmp/${DB_NAME}.sql.gz s3://${B2_BUCKET}/${DB_NAME}.sql.gz; + STATUS=$?; + end=$(date +%s); if [ $STATUS -ne 0 ]; then - error_message="${MYNAME}: FATAL: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds." - log "${error_message}" - error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}" - exit $STATUS + error_message="${MYNAME}: FATAL: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} returned non-zero status ($STATUS) in $(expr ${end} - ${start}) seconds."; + log "ERROR" "${error_message}"; + error_to_sentry "${error_message}" "${DB_NAME}" "${STATUS}"; + exit $STATUS; else - log "${MYNAME}: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds." + log "INFO" "${MYNAME}: Copy backup to Backblaze B2 bucket ${B2_BUCKET} of ${DB_NAME} completed in $(expr ${end} - ${start}) seconds."; fi fi -echo "postgresql-backup-restore: backup: Completed" +echo "postgresql-backup-restore: backup: Completed"; -log "${MYNAME}: backup: Completed" +log "INFO" "${MYNAME}: backup: Completed"; -exit $STATUS \ No newline at end of file +exit $STATUS;