Merge pull request #590 from versity/test_cmdline_create_multipart

Test cmdline create multipart
This commit is contained in:
Ben McClelland
2024-05-28 09:53:35 -07:00
committed by GitHub
8 changed files with 382 additions and 78 deletions

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
complete_multipart_upload() {
if [[ $# -ne 4 ]]; then
log 2 "'complete multipart upload' command requires bucket, key, upload ID, parts list"
return 1
fi
log 5 "complete multipart upload id: $3, parts: $4"
error=$(aws --no-verify-ssl s3api complete-multipart-upload --bucket "$1" --key "$2" --upload-id "$3" --multipart-upload '{"Parts": '"$4"'}' 2>&1) || local completed=$?
if [[ $completed -ne 0 ]]; then
log 2 "error completing multipart upload: $error"
return 1
fi
log 5 "complete multipart upload error: $error"
return 0
}

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# initialize a multipart upload
# params: bucket, key
# return 0 for success, 1 for failure
create_multipart_upload() {
if [ $# -ne 2 ]; then
log 2 "create multipart upload function must have bucket, key"
return 1
fi
local multipart_data
multipart_data=$(aws --no-verify-ssl s3api create-multipart-upload --bucket "$1" --key "$2") || local created=$?
if [[ $created -ne 0 ]]; then
log 2 "Error creating multipart upload: $upload_id"
return 1
fi
upload_id=$(echo "$multipart_data" | jq '.UploadId')
upload_id="${upload_id//\"/}"
export upload_id
}
create_multipart_upload_params() {
if [ $# -ne 8 ]; then
log 2 "create multipart upload function with params must have bucket, key, content type, metadata, object lock legal hold status, " \
"object lock mode, object lock retain until date, and tagging"
return 1
fi
local multipart_data
multipart_data=$(aws --no-verify-ssl s3api create-multipart-upload \
--bucket "$1" \
--key "$2" \
--content-type "$3" \
--metadata "$4" \
--object-lock-legal-hold-status "$5" \
--object-lock-mode "$6" \
--object-lock-retain-until-date "$7" \
--tagging "$8" 2>&1) || local create_result=$?
if [[ $create_result -ne 0 ]]; then
log 2 "error creating multipart upload with params: $multipart_data"
return 1
fi
export multipart_data
upload_id=$(echo "$multipart_data" | grep -v "InsecureRequestWarning" | jq '.UploadId')
upload_id="${upload_id//\"/}"
export upload_id
return 0
}
create_multipart_upload_custom() {
if [ $# -lt 2 ]; then
log 2 "create multipart upload custom function must have at least bucket and key"
return 1
fi
local multipart_data
log 5 "additional create multipart params"
for i in "$@"; do
log 5 $i
done
log 5 "${*:3}"
log 5 "aws --no-verify-ssl s3api create-multipart-upload --bucket $1 --key $2 ${*:3}"
multipart_data=$(aws --no-verify-ssl s3api create-multipart-upload --bucket "$1" --key "$2" 2>&1) || local result=$?
if [[ $result -ne 0 ]]; then
log 2 "error creating custom multipart data command: $multipart_data"
return 1
fi
export multipart_data
log 5 "multipart data: $multipart_data"
upload_id=$(echo "$multipart_data" | grep -v "InsecureRequestWarning" | jq '.UploadId')
upload_id="${upload_id//\"/}"
log 5 "upload id: $upload_id"
export upload_id
return 0
}

View File

@@ -2,7 +2,7 @@
get_object() {
if [ $# -ne 4 ]; then
echo "get object command requires command type, bucket, key, destination"
log 2 "get object command requires command type, bucket, key, destination"
return 1
fi
local exit_code=0
@@ -16,12 +16,25 @@ get_object() {
elif [[ $1 == 'mc' ]]; then
error=$(mc --insecure get "$MC_ALIAS/$2/$3" "$4" 2>&1) || exit_code=$?
else
echo "'get object' command not implemented for '$1'"
log 2 "'get object' command not implemented for '$1'"
return 1
fi
log 5 "get object exit code: $exit_code"
if [ $exit_code -ne 0 ]; then
echo "error putting object into bucket: $error"
log 2 "error getting object: $error"
return 1
fi
return 0
}
get_object_with_range() {
if [[ $# -ne 4 ]]; then
log 2 "'get object with range' requires bucket, key, range, outfile"
return 1
fi
error=$(aws --no-verify-ssl s3api get-object --bucket "$1" --key "$2" --range "$3" "$4" 2>&1) || local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
log 2 "error getting object with range: $error"
return 1
fi
return 0

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
get_object_tagging() {
if [ $# -ne 3 ]; then
log 2 "get object tag command missing command type, bucket, and/or key"
return 1
fi
local result
if [[ $1 == 'aws' ]]; then
tags=$(aws --no-verify-ssl s3api get-object-tagging --bucket "$2" --key "$3" 2>&1) || result=$?
elif [[ $1 == 'mc' ]]; then
tags=$(mc --insecure tag list "$MC_ALIAS"/"$2"/"$3" 2>&1) || result=$?
else
log 2 "invalid command type $1"
return 1
fi
if [[ $result -ne 0 ]]; then
if [[ "$tags" == *"NoSuchTagSet"* ]] || [[ "$tags" == *"No tags found"* ]]; then
tags=
else
log 2 "error getting object tags: $tags"
return 1
fi
else
log 5 "$tags"
tags=$(echo "$tags" | grep -v "InsecureRequestWarning")
fi
export tags
}

View File

@@ -2,28 +2,29 @@
head_object() {
if [ $# -ne 3 ]; then
echo "head-object missing command, bucket name, object name"
log 2 "head-object missing command, bucket name, object name"
return 2
fi
local exit_code=0
local error=""
if [[ $1 == 'aws' ]] || [[ $1 == 's3api' ]] || [[ $1 == 's3' ]]; then
error=$(aws --no-verify-ssl s3api head-object --bucket "$2" --key "$3" 2>&1) || exit_code="$?"
metadata=$(aws --no-verify-ssl s3api head-object --bucket "$2" --key "$3" 2>&1) || exit_code="$?"
elif [[ $1 == 's3cmd' ]]; then
error=$(s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate info s3://"$2/$3" 2>&1) || exit_code="$?"
metadata=$(s3cmd "${S3CMD_OPTS[@]}" --no-check-certificate info s3://"$2/$3" 2>&1) || exit_code="$?"
elif [[ $1 == 'mc' ]]; then
error=$(mc --insecure stat "$MC_ALIAS/$2/$3" 2>&1) || exit_code=$?
metadata=$(mc --insecure stat "$MC_ALIAS/$2/$3" 2>&1) || exit_code=$?
else
echo "invalid command type $1"
log 2 "invalid command type $1"
return 2
fi
if [ $exit_code -ne 0 ]; then
if [[ "$error" == *"404"* ]] || [[ "$error" == *"does not exist"* ]]; then
if [[ "$metadata" == *"404"* ]] || [[ "$metadata" == *"does not exist"* ]]; then
log 5 "file doesn't exist ($metadata)"
return 1
else
echo "error checking if object exists: $error"
log 2 "error checking if object exists: $metadata"
return 2
fi
fi
export metadata
return 0
}

View File

@@ -18,6 +18,7 @@ source ./tests/commands/get_object_attributes.sh
source ./tests/commands/get_object_legal_hold.sh
source ./tests/commands/get_object_lock_configuration.sh
source ./tests/commands/get_object_retention.sh
source ./tests/commands/get_object_tagging.sh
source ./tests/commands/list_object_versions.sh
source ./tests/commands/put_bucket_acl.sh
source ./tests/commands/put_bucket_policy.sh
@@ -85,7 +86,78 @@ source ./tests/commands/select_object_content.sh
test_common_create_delete_bucket "aws"
}
# create-multipart-upload - test_complete_multipart_upload
# create-multipart-upload
@test "test_create_multipart_upload_properties" {
local bucket_file="bucket-file"
local bucket_file_data="test file\n"
local expected_content_type="application/zip"
local expected_meta_key="testKey"
local expected_meta_val="testValue"
local expected_hold_status="ON"
local expected_retention_mode="GOVERNANCE"
local expected_tag_key="TestTag"
local expected_tag_val="TestTagVal"
local five_seconds_later
os_name="$(uname)"
if [[ "$os_name" == "Darwin" ]]; then
now=$(date -u +"%Y-%m-%dT%H:%M:%S")
five_seconds_later=$(date -j -v +5S -f "%Y-%m-%dT%H:%M:%S" "$now" +"%Y-%m-%dT%H:%M:%S")
else
now=$(date +"%Y-%m-%dT%H:%M:%S")
five_seconds_later=$(date -d "$now 5 seconds" +"%Y-%m-%dT%H:%M:%S")
fi
create_test_files "$bucket_file" || fail "error creating test file"
printf "%s" "$bucket_file_data" > "$test_file_folder"/$bucket_file
delete_bucket_if_exists "s3api" "$BUCKET_ONE_NAME" || fail "error deleting bucket, or checking for existence"
create_bucket_object_lock_enabled "$BUCKET_ONE_NAME" || fail "error creating bucket"
log 5 "$five_seconds_later"
multipart_upload_with_params "$BUCKET_ONE_NAME" "$bucket_file" "$test_file_folder"/"$bucket_file" 4 \
"$expected_content_type" \
"{\"$expected_meta_key\": \"$expected_meta_val\"}" \
"$expected_hold_status" \
"$expected_retention_mode" \
"$five_seconds_later" \
"$expected_tag_key=$expected_tag_val" || fail "error performing multipart upload"
head_object "s3api" "$BUCKET_ONE_NAME" "$bucket_file" || fail "error getting metadata"
raw_metadata=$(echo "$metadata" | grep -v "InsecureRequestWarning")
log 5 "raw metadata: $raw_metadata"
content_type=$(echo "$raw_metadata" | jq -r ".ContentType")
[[ $content_type == "$expected_content_type" ]] || fail "content type mismatch ($content_type, $expected_content_type)"
meta_val=$(echo "$raw_metadata" | jq -r ".Metadata.$expected_meta_key")
[[ $meta_val == "$expected_meta_val" ]] || fail "metadata val mismatch ($meta_val, $expected_meta_val)"
hold_status=$(echo "$raw_metadata" | jq -r ".ObjectLockLegalHoldStatus")
[[ $hold_status == "$expected_hold_status" ]] || fail "hold status mismatch ($hold_status, $expected_hold_status)"
retention_mode=$(echo "$raw_metadata" | jq -r ".ObjectLockMode")
[[ $retention_mode == "$expected_retention_mode" ]] || fail "retention mode mismatch ($retention_mode, $expected_retention_mode)"
retain_until_date=$(echo "$raw_metadata" | jq -r ".ObjectLockRetainUntilDate")
[[ $retain_until_date == "$five_seconds_later"* ]] || fail "retention date mismatch ($retain_until_date, $five_seconds_later)"
get_object_tagging "aws" "$BUCKET_ONE_NAME" "$bucket_file" || fail "error getting tagging"
log 5 "tags: $tags"
tag_key=$(echo "$tags" | jq -r ".TagSet[0].Key")
[[ $tag_key == "$expected_tag_key" ]] || fail "tag mismatch ($tag_key, $expected_tag_key)"
tag_val=$(echo "$tags" | jq -r ".TagSet[0].Value")
[[ $tag_val == "$expected_tag_val" ]] || fail "tag mismatch ($tag_val, $expected_tag_val)"
put_object_legal_hold "$BUCKET_ONE_NAME" "$bucket_file" "OFF" || fail "error disabling legal hold"
head_object "s3api" "$BUCKET_ONE_NAME" "$bucket_file" || fail "error getting metadata"
get_object "s3api" "$BUCKET_ONE_NAME" "$bucket_file" "$test_file_folder/$bucket_file-copy" || fail "error getting object"
compare_files "$test_file_folder/$bucket_file" "$test_file_folder/$bucket_file-copy" || fail "files not equal"
sleep 2
delete_bucket_or_contents "aws" "$BUCKET_ONE_NAME"
delete_test_files $bucket_file
}
# delete-bucket - test_create_delete_bucket_aws
@@ -99,6 +171,35 @@ source ./tests/commands/select_object_content.sh
test_common_set_get_delete_bucket_tags "aws"
}
#@test "test_get_object_invalid_range" {
# bucket_file="bucket_file"
#
# create_test_files "$bucket_file" || local created=$?
# [[ $created -eq 0 ]] || fail "Error creating test files"
# setup_bucket "s3api" "$BUCKET_ONE_NAME" || local setup_result=$?
# [[ $setup_result -eq 0 ]] || fail "error setting up bucket"
# put_object "s3api" "$test_file_folder/$bucket_file" "$BUCKET_ONE_NAME" "$bucket_file" || fail "error putting object"
# get_object_with_range "$BUCKET_ONE_NAME" "$bucket_file" "bytes=0-0" "$test_file_folder/$bucket_file-range" || local get_result=$?
# [[ $get_result -ne 0 ]] || fail "Get object with zero range returned no error"
#}
#@test "test_get_object_full_range" {
# bucket_file="bucket_file"
#
# create_test_files "$bucket_file" || local created=$?
# [[ $created -eq 0 ]] || fail "Error creating test files"
# echo -n "0123456789" > "$test_file_folder/$bucket_file"
# setup_bucket "s3api" "$BUCKET_ONE_NAME" || local setup_result=$?
# [[ $setup_result -eq 0 ]] || fail "error setting up bucket"
# put_object "s3api" "$test_file_folder/$bucket_file" "$BUCKET_ONE_NAME" "$bucket_file" || fail "error putting object"
# get_object_with_range "$BUCKET_ONE_NAME" "$bucket_file" "bytes=9-15" "$test_file_folder/$bucket_file-range" || fail "error getting range"
# cat "$test_file_folder/$bucket_file"
# cat "$test_file_folder/$bucket_file-range"
# ls -l "$test_file_folder/$bucket_file"
# ls -l "$test_file_folder/$bucket_file-range"
# compare_files "$test_file_folder/$bucket_file" "$test_file_folder/$bucket_file-range" || fail "files not equal"
#}
@test "test_put_object" {
bucket_file="bucket_file"

View File

@@ -10,6 +10,7 @@ source ./tests/commands/get_bucket_acl.sh
source ./tests/commands/get_bucket_location.sh
source ./tests/commands/get_bucket_tagging.sh
source ./tests/commands/get_object.sh
source ./tests/commands/get_object_tagging.sh
source ./tests/commands/list_buckets.sh
source ./tests/commands/put_bucket_acl.sh
source ./tests/commands/put_object.sh
@@ -326,7 +327,7 @@ test_common_set_get_object_tags() {
put_object "$1" "$test_file_folder"/"$bucket_file" "$BUCKET_ONE_NAME" "$bucket_file" || local copy_result=$?
[[ $copy_result -eq 0 ]] || fail "Failed to add object to bucket '$BUCKET_ONE_NAME'"
get_object_tags "$1" "$BUCKET_ONE_NAME" $bucket_file || local get_result=$?
get_object_tagging "$1" "$BUCKET_ONE_NAME" $bucket_file || local get_result=$?
[[ $get_result -eq 0 ]] || fail "Error getting object tags"
if [[ $1 == 'aws' ]]; then
tag_set=$(echo "$tags" | jq '.TagSet')
@@ -336,7 +337,7 @@ test_common_set_get_object_tags() {
fi
put_object_tag "$1" "$BUCKET_ONE_NAME" $bucket_file $key $value
get_object_tags "$1" "$BUCKET_ONE_NAME" "$bucket_file" || local get_result_two=$?
get_object_tagging "$1" "$BUCKET_ONE_NAME" "$bucket_file" || local get_result_two=$?
[[ $get_result_two -eq 0 ]] || fail "Error getting object tags"
if [[ $1 == 'aws' ]]; then
tag_set_key=$(echo "$tags" | jq -r '.TagSet[0].Key')

View File

@@ -4,10 +4,13 @@ source ./tests/util_bucket_create.sh
source ./tests/util_mc.sh
source ./tests/logger.sh
source ./tests/commands/abort_multipart_upload.sh
source ./tests/commands/complete_multipart_upload.sh
source ./tests/commands/create_multipart_upload.sh
source ./tests/commands/create_bucket.sh
source ./tests/commands/delete_bucket.sh
source ./tests/commands/delete_object.sh
source ./tests/commands/get_bucket_tagging.sh
source ./tests/commands/get_object_tagging.sh
source ./tests/commands/head_bucket.sh
source ./tests/commands/head_object.sh
source ./tests/commands/list_objects.sh
@@ -501,7 +504,7 @@ check_object_tags_empty() {
echo "bucket tags empty check requires command type, bucket, and key"
return 2
fi
get_object_tags "$1" "$2" "$3" || get_result=$?
get_object_tagging "$1" "$2" "$3" || get_result=$?
if [[ $get_result -ne 0 ]]; then
echo "failed to get tags"
return 2
@@ -571,7 +574,7 @@ get_and_verify_object_tags() {
echo "get and verify object tags missing command type, bucket, key, tag key, tag value"
return 1
fi
get_object_tags "$1" "$2" "$3" || get_result=$?
get_object_tagging "$1" "$2" "$3" || get_result=$?
if [[ $get_result -ne 0 ]]; then
echo "failed to get tags"
return 1
@@ -595,37 +598,6 @@ get_and_verify_object_tags() {
return 0
}
# get object tags
# params: bucket
# export 'tags' on success, return 1 for error
get_object_tags() {
if [ $# -ne 3 ]; then
echo "get object tag command missing command type, bucket, and/or key"
return 1
fi
local result
if [[ $1 == 'aws' ]]; then
tags=$(aws --no-verify-ssl s3api get-object-tagging --bucket "$2" --key "$3" 2>&1) || result=$?
elif [[ $1 == 'mc' ]]; then
tags=$(mc --insecure tag list "$MC_ALIAS"/"$2"/"$3" 2>&1) || result=$?
else
echo "invalid command type $1"
return 1
fi
if [[ $result -ne 0 ]]; then
if [[ "$tags" == *"NoSuchTagSet"* ]] || [[ "$tags" == *"No tags found"* ]]; then
tags=
else
echo "error getting object tags: $tags"
return 1
fi
else
log 5 "$tags"
tags=$(echo "$tags" | grep -v "InsecureRequestWarning")
fi
export tags
}
# list objects in bucket, v1
# param: bucket
# export objects on success, return 1 for failure
@@ -662,27 +634,6 @@ list_objects_s3api_v2() {
export objects
}
# initialize a multipart upload
# params: bucket, key
# return 0 for success, 1 for failure
create_multipart_upload() {
if [ $# -ne 2 ]; then
echo "create multipart upload function must have bucket, key"
return 1
fi
local multipart_data
multipart_data=$(aws --no-verify-ssl s3api create-multipart-upload --bucket "$1" --key "$2") || local created=$?
if [[ $created -ne 0 ]]; then
echo "Error creating multipart upload: $upload_id"
return 1
fi
upload_id=$(echo "$multipart_data" | jq '.UploadId')
upload_id="${upload_id//\"/}"
export upload_id
}
# upload a single part of a multipart upload
# params: bucket, key, upload ID, original (unsplit) file name, part number
# return: 0 for success, 1 for failure
@@ -706,24 +657,25 @@ upload_part() {
# return: 0 for success, 1 for failure
multipart_upload_before_completion() {
if [ $# -ne 4 ]; then
echo "multipart upload pre-completion command missing bucket, key, file, and/or part count"
log 2 "multipart upload pre-completion command missing bucket, key, file, and/or part count"
return 1
fi
split_file "$3" "$4" || split_result=$?
if [[ $split_result -ne 0 ]]; then
echo "error splitting file"
log 2 "error splitting file"
return 1
fi
create_multipart_upload "$1" "$2" || create_result=$?
if [[ $create_result -ne 0 ]]; then
echo "error creating multpart upload"
log 2 "error creating multpart upload"
return 1
fi
parts="["
for ((i = 1; i <= $4; i++)); do
# shellcheck disable=SC2154
upload_part "$1" "$2" "$upload_id" "$3" "$i" || local upload_result=$?
if [[ $upload_result -ne 0 ]]; then
echo "error uploading part $i"
@@ -739,24 +691,140 @@ multipart_upload_before_completion() {
export parts
}
multipart_upload_before_completion_with_params() {
if [ $# -ne 10 ]; then
log 2 "multipart upload command missing bucket, key, file, part count, content type, metadata, hold status, lock mode, retain until date, tagging"
return 1
fi
split_file "$3" "$4" || split_result=$?
if [[ $split_result -ne 0 ]]; then
log 2 "error splitting file"
return 1
fi
create_multipart_upload_params "$1" "$2" "$5" "$6" "$7" "$8" "$9" "${10}" || local create_result=$?
if [[ $create_result -ne 0 ]]; then
log 2 "error creating multpart upload"
return 1
fi
parts="["
for ((i = 1; i <= $4; i++)); do
upload_part "$1" "$2" "$upload_id" "$3" "$i" || local upload_result=$?
if [[ $upload_result -ne 0 ]]; then
log 2 "error uploading part $i"
return 1
fi
parts+="{\"ETag\": $etag, \"PartNumber\": $i}"
if [[ $i -ne $4 ]]; then
parts+=","
fi
done
parts+="]"
export parts
}
multipart_upload_before_completion_custom() {
if [ $# -lt 4 ]; then
log 2 "multipart upload custom command missing bucket, key, file, part count, and/or optional params"
return 1
fi
split_file "$3" "$4" || local split_result=$?
if [[ $split_result -ne 0 ]]; then
log 2 "error splitting file"
return 1
fi
# shellcheck disable=SC2048
create_multipart_upload_custom "$1" "$2" ${*:5} || local create_result=$?
if [[ $create_result -ne 0 ]]; then
log 2 "error creating multipart upload"
return 1
fi
log 5 "upload ID: $upload_id"
parts="["
for ((i = 1; i <= $4; i++)); do
upload_part "$1" "$2" "$upload_id" "$3" "$i" || local upload_result=$?
if [[ $upload_result -ne 0 ]]; then
log 2 "error uploading part $i"
return 1
fi
parts+="{\"ETag\": $etag, \"PartNumber\": $i}"
if [[ $i -ne $4 ]]; then
parts+=","
fi
done
parts+="]"
export parts
}
multipart_upload_custom() {
if [ $# -lt 4 ]; then
log 2 "multipart upload custom command missing bucket, key, file, part count, and/or optional additional params"
return 1
fi
# shellcheck disable=SC2048
multipart_upload_before_completion_custom "$1" "$2" "$3" "$4" ${*:5} || local result=$?
if [[ $result -ne 0 ]]; then
log 2 "error performing pre-completion multipart upload"
return 1
fi
log 5 "upload ID: $upload_id, parts: $parts"
complete_multipart_upload "$1" "$2" "$upload_id" "$parts" || local completed=$?
if [[ $completed -ne 0 ]]; then
log 2 "Error completing upload"
return 1
fi
return 0
}
multipart_upload() {
if [ $# -ne 4 ]; then
log 2 "multipart upload command missing bucket, key, file, and/or part count"
return 1
fi
multipart_upload_before_completion "$1" "$2" "$3" "$4" || local result=$?
if [[ $result -ne 0 ]]; then
log 2 "error performing pre-completion multipart upload"
return 1
fi
complete_multipart_upload "$1" "$2" "$upload_id" "$parts" || local completed=$?
if [[ $completed -ne 0 ]]; then
log 2 "Error completing upload"
return 1
fi
return 0
}
# perform a multi-part upload
# params: bucket, key, source file location, number of parts
# return 0 for success, 1 for failure
multipart_upload() {
if [ $# -ne 4 ]; then
echo "multipart upload command missing bucket, key, file, and/or part count"
multipart_upload_with_params() {
if [ $# -ne 10 ]; then
log 2 "multipart upload command requires bucket, key, file, part count, content type, metadata, hold status, lock mode, retain until date, tagging"
return 1
fi
log 5 "1: $1, 2: $2, 3: $3, 4: $4, 5: $5, 6: $6, 7: $7, 8: $8, 9: $9, 10: ${10}"
multipart_upload_before_completion "$1" "$2" "$3" "$4" || result=$?
multipart_upload_before_completion_with_params "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9" "${10}" || result=$?
if [[ $result -ne 0 ]]; then
echo "error performing pre-completion multipart upload"
log 2 "error performing pre-completion multipart upload"
return 1
fi
log 5 "Upload parts: $parts"
error=$(aws --no-verify-ssl s3api complete-multipart-upload --bucket "$1" --key "$2" --upload-id "$upload_id" --multipart-upload '{"Parts": '"$parts"'}') || local completed=$?
complete_multipart_upload "$1" "$2" "$upload_id" "$parts" || local completed=$?
if [[ $completed -ne 0 ]]; then
echo "Error completing upload: $error"
log 2 "Error completing upload"
return 1
fi
return 0