@@ -51,54 +51,111 @@ function az_upload {
5151}
5252
5353function 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}
6390export -f aws_delete_objects
91+ export -f aws_delete_objects_batch
6492
6593function 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
104161function 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
122179function 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
142199function upload {
0 commit comments