Skip to content

Commit a708b40

Browse files
fix: S3 retention and JSON formatting issues in logical-backup dump script
1 parent 421bd6d commit a708b40

File tree

1 file changed

+75
-18
lines changed

1 file changed

+75
-18
lines changed

logical-backup/dump.sh

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,54 +51,111 @@ function az_upload {
5151
}
5252

5353
function aws_delete_objects {
54-
args=(
54+
local keys=("$@")
55+
local batch=()
56+
local batch_size=1000
57+
58+
for ((i=0; i<${#keys[@]}; i++)); do
59+
batch+=("${keys[$i]}")
60+
if ((${#batch[@]} >= batch_size)); then
61+
aws_delete_objects_batch "${batch[@]}"
62+
batch=()
63+
fi
64+
done
65+
if ((${#batch[@]} > 0)); then
66+
aws_delete_objects_batch "${batch[@]}"
67+
fi
68+
}
69+
70+
function aws_delete_objects_batch {
71+
local keys=("$@")
72+
local keys_json=$(printf '%s\n' "${keys[@]}" | jq -R . | jq -s .)
73+
local objects_json=$(jq -n --argjson keys "$keys_json" '{Objects: [$keys[] | {Key: .}], Quiet: true}')
74+
75+
local args=(
5576
"--bucket=$LOGICAL_BACKUP_S3_BUCKET"
5677
)
5778

5879
[[ ! -z "$LOGICAL_BACKUP_S3_ENDPOINT" ]] && args+=("--endpoint-url=$LOGICAL_BACKUP_S3_ENDPOINT")
5980
[[ ! -z "$LOGICAL_BACKUP_S3_REGION" ]] && args+=("--region=$LOGICAL_BACKUP_S3_REGION")
6081

61-
aws s3api delete-objects "${args[@]}" --delete Objects=["$(printf {Key=%q}, "$@")"],Quiet=true
82+
local result
83+
result=$(aws s3api delete-objects "${args[@]}" --delete "$objects_json" 2>&1)
84+
local exit_code=$?
85+
86+
if [[ $exit_code -ne 0 ]]; then
87+
echo "WARNING: failed to delete some objects: $result"
88+
fi
6289
}
6390
export -f aws_delete_objects
91+
export -f aws_delete_objects_batch
6492

6593
function aws_delete_outdated {
6694
if [[ -z "$LOGICAL_BACKUP_S3_RETENTION_TIME" ]] ; then
6795
echo "no retention time configured: skip cleanup of outdated backups"
6896
return 0
6997
fi
7098

71-
# define cutoff date for outdated backups (day precision)
72-
cutoff_date=$(date -d "$LOGICAL_BACKUP_S3_RETENTION_TIME ago" +%F)
99+
cutoff_timestamp=$(date -d "$LOGICAL_BACKUP_S3_RETENTION_TIME ago" +%s)
73100

74-
# mimic bucket setup from Spilo
75101
prefix=$LOGICAL_BACKUP_S3_BUCKET_PREFIX"/"$SCOPE$LOGICAL_BACKUP_S3_BUCKET_SCOPE_SUFFIX"/logical_backups/"
76102

77103
args=(
78-
"--no-paginate"
79-
"--output=text"
104+
"--output=json"
80105
"--prefix=$prefix"
81106
"--bucket=$LOGICAL_BACKUP_S3_BUCKET"
82107
)
83108

84109
[[ ! -z "$LOGICAL_BACKUP_S3_ENDPOINT" ]] && args+=("--endpoint-url=$LOGICAL_BACKUP_S3_ENDPOINT")
85110
[[ ! -z "$LOGICAL_BACKUP_S3_REGION" ]] && args+=("--region=$LOGICAL_BACKUP_S3_REGION")
86111

87-
# list objects older than the cutoff date
88-
aws s3api list-objects "${args[@]}" --query="Contents[?LastModified<='$cutoff_date'].[Key]" > /tmp/outdated-backups
89-
90-
# spare the last backup
91-
sed -i '$d' /tmp/outdated-backups
112+
: > /tmp/outdated-backups
113+
114+
local continuation_token=""
115+
while true; do
116+
local result
117+
if [[ -n "$continuation_token" ]]; then
118+
result=$(aws s3api list-objects-v2 "${args[@]}" --continuation-token "$continuation_token" 2>/dev/null)
119+
else
120+
result=$(aws s3api list-objects-v2 "${args[@]}" 2>/dev/null)
121+
fi
122+
123+
if [[ -z "$result" ]] || [[ "$result" == "{}" ]]; then
124+
break
125+
fi
126+
127+
echo "$result" | jq -r '.Contents[] | select(.LastModified != null) | "\(.Key)\n\(.LastModified)"' | \
128+
while read -r key && read -r last_modified; do
129+
set +e
130+
file_timestamp=$(date -d "$last_modified" +%s 2>/dev/null)
131+
set -e
132+
if [[ -n "$file_timestamp" ]] && [[ "$file_timestamp" -lt "$cutoff_timestamp" ]]; then
133+
echo "$key" >> /tmp/outdated-backups
134+
fi
135+
done
136+
137+
local is_truncated=$(echo "$result" | jq -r '.IsTruncated')
138+
if [[ "$is_truncated" != "true" ]]; then
139+
break
140+
fi
141+
142+
continuation_token=$(echo "$result" | jq -r '.NextContinuationToken // empty')
143+
if [[ -z "$continuation_token" ]]; then
144+
break
145+
fi
146+
done
92147

93148
count=$(wc -l < /tmp/outdated-backups)
94149
if [[ $count == 0 ]] ; then
95150
echo "no outdated backups to delete"
96151
return 0
97152
fi
98-
echo "deleting $count outdated backups created before $cutoff_date"
153+
echo "deleting $count outdated backups older than $LOGICAL_BACKUP_S3_RETENTION_TIME"
154+
155+
mapfile -t keys_array < /tmp/outdated-backups
156+
aws_delete_objects "${keys_array[@]}"
99157

100-
# deleted outdated files in batches with 100 at a time
101-
tr '\n' '\0' < /tmp/outdated-backups | xargs -0 -P1 -n100 bash -c 'aws_delete_objects "$@"' _
158+
echo "cleanup completed"
102159
}
103160

104161
function aws_upload {
@@ -111,12 +168,12 @@ function aws_upload {
111168

112169
args=()
113170

114-
[[ ! -z "$EXPECTED_SIZE" ]] && args+=("--expected-size=$EXPECTED_SIZE")
171+
[[ "$EXPECTED_SIZE" -gt 0 ]] && args+=("--expected-size=$EXPECTED_SIZE")
115172
[[ ! -z "$LOGICAL_BACKUP_S3_ENDPOINT" ]] && args+=("--endpoint-url=$LOGICAL_BACKUP_S3_ENDPOINT")
116173
[[ ! -z "$LOGICAL_BACKUP_S3_REGION" ]] && args+=("--region=$LOGICAL_BACKUP_S3_REGION")
117174
[[ ! -z "$LOGICAL_BACKUP_S3_SSE" ]] && args+=("--sse=$LOGICAL_BACKUP_S3_SSE")
118175

119-
aws s3 cp - "$PATH_TO_BACKUP" "${args[@]//\'/}"
176+
aws s3 cp - "$PATH_TO_BACKUP" "${args[@]}"
120177
}
121178

122179
function gcs_upload {
@@ -136,7 +193,7 @@ function gcs_upload {
136193
GSUTIL_OPTIONS[1]="GoogleCompute:service_account=default"
137194
fi
138195

139-
gsutil ${GSUTIL_OPTIONS[@]} cp - "$PATH_TO_BACKUP"
196+
gsutil "${GSUTIL_OPTIONS[@]}" cp - "$PATH_TO_BACKUP"
140197
}
141198

142199
function upload {

0 commit comments

Comments
 (0)