s3: error handling has changed in v3 api

This commit is contained in:
Girish Ramakrishnan
2025-06-20 16:04:59 +02:00
parent 6da8396c76
commit 241053e1a8

View File

@@ -36,12 +36,12 @@ const assert = require('assert'),
{ PassThrough } = require('node:stream'),
path = require('path'),
{ Readable } = require('stream'),
{ S3 } = require('@aws-sdk/client-s3'),
{ S3, NoSuchKey, NoSuchBucket } = require('@aws-sdk/client-s3'),
safe = require('safetydance'),
{ Upload } = require('@aws-sdk/lib-storage');
function S3_NOT_FOUND(error) {
return error.code === 'NoSuchKey' || error.code === 'NotFound' || error.code === 'ENOENT';
return error instanceof NoSuchKey || error instanceof NoSuchBucket;
}
const RETRY_STRATEGY = new ConfiguredRetryStrategy(10 /* max attempts */, (/* attempt */) => 20000 /* constant backoff */);
@@ -124,7 +124,7 @@ async function upload(apiConfig, backupFilePath) {
stream: passThrough,
async finish() {
const [error, data] = await safe(uploadPromise);
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Upload error: code: ${error.code} message: ${error.message}`); // sometimes message is null
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Upload error: code: ${error.Code} message: ${error.message}`); // sometimes message is null
debug(`Upload finished. ${JSON.stringify(data)}`);
}
};
@@ -144,7 +144,7 @@ async function exists(apiConfig, backupFilePath) {
const [error, response] = await safe(s3.headObject(params));
if (error && S3_NOT_FOUND(error)) return false;
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error headObject ${backupFilePath}. Message: ${error.message} HTTP Code: ${error.code}`);
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error headObject ${backupFilePath}. Message: ${error.message} HTTP Code: ${error.$metadata.httpStatusCode}`);
if (!response || typeof response.Metadata !== 'object') throw new BoxError(BoxError.EXTERNAL_ERROR, 'not a s3 endpoint');
return true;
@@ -156,7 +156,7 @@ async function exists(apiConfig, backupFilePath) {
};
const [error, listData] = await safe(s3.listObjectsV2(listParams));
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error listing objects ${backupFilePath}. Message: ${error.message} HTTP Code: ${error.code}`);
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error listing objects ${backupFilePath}. Message: ${error.message} HTTP Code: ${error.$metadata.httpStatusCode}`);
return listData.Contents.length !== 0;
}
@@ -186,7 +186,7 @@ class S3MultipartDownloadStream extends Readable {
this.destroy(new BoxError(BoxError.NOT_FOUND, `Backup not found: ${this._path}`));
} else {
debug(`download: ${this._path} s3 stream error. %o`, error);
this.destroy(new BoxError(BoxError.EXTERNAL_ERROR, `Error multipartDownload ${this._path}. Message: ${error.message} HTTP Code: ${error.code}`));
this.destroy(new BoxError(BoxError.EXTERNAL_ERROR, `Error multipartDownload ${this._path}. Message: ${error.message} HTTP Code: ${error.$metadata.httpStatusCode}`));
}
}
@@ -270,7 +270,7 @@ async function listDir(apiConfig, dir, batchSize, marker) {
if (marker) listParams.ContinuationToken = marker;
const [error, listData] = await safe(s3.listObjectsV2(listParams));
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error listing objects in ${dir}. Message: ${error.message} HTTP Code: ${error.code}`);
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error listing objects in ${dir}. Message: ${error.message} HTTP Code: ${error.$metadata.httpStatusCode}`);
if (listData.Contents.length === 0) return { entries: [], marker: null }; // no more
const entries = listData.Contents.map(function (c) { return { fullPath: c.Key, size: c.Size }; });
return { entries, marker: !listData.IsTruncated ? null : listData.NextContinuationToken };
@@ -303,7 +303,7 @@ async function copyFile(apiConfig, oldFilePath, newFilePath, entry, progressCall
if (error) debug(`copy: s3 copy error when copying ${entry.fullPath}: ${error}`);
if (error && S3_NOT_FOUND(error)) throw new BoxError(BoxError.NOT_FOUND, `Old backup not found: ${entry.fullPath}`);
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error copying ${entry.fullPath} (${entry.size} bytes): ${error.code || ''} ${error}`);
if (error) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error copying ${entry.fullPath} (${entry.size} bytes): ${error.Code || ''} ${error}`);
}
const copyParams = {
@@ -472,7 +472,7 @@ async function removeDir(apiConfig, pathPrefix, progressCallback) {
// deleteObjects does not return error if key is not found
const [error] = await safe(s3.deleteObjects(deleteParams));
if (error) {
progressCallback({ message: `Unable to remove ${deleteParams.Key} ${error.message || error.code}` });
progressCallback({ message: `Unable to remove ${deleteParams.Key} ${error.message || error.Code}` });
throw new BoxError(BoxError.EXTERNAL_ERROR, `Unable to remove ${deleteParams.Key}. error: ${error.message}`);
}
});
@@ -530,7 +530,7 @@ async function testConfig(apiConfig) {
const s3 = createS3Client(apiConfig, {});
const [putError] = await safe(s3.putObject(putParams));
if (putError) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error put object cloudron-testfile. Message: ${putError.message} HTTP Code: ${putError.code}`);
if (putError) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error put object cloudron-testfile. Message: ${putError.message} HTTP Code: ${putError.$metadata.httpStatusCode}`);
const listParams = {
Bucket: apiConfig.bucket,
@@ -539,7 +539,7 @@ async function testConfig(apiConfig) {
};
const [listError] = await safe(s3.listObjectsV2(listParams));
if (listError) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error listing objects. Message: ${listError.message} HTTP Code: ${listError.code}`);
if (listError) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error listing objects. Message: ${listError.message} HTTP Code: ${listError.$metadata.httpStatusCode}`);
const delParams = {
Bucket: apiConfig.bucket,
@@ -547,7 +547,7 @@ async function testConfig(apiConfig) {
};
const [delError] = await safe(s3.deleteObject(delParams));
if (delError) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error del object cloudron-testfile. Message: ${delError.message} HTTP Code: ${delError.code}`);
if (delError) throw new BoxError(BoxError.EXTERNAL_ERROR, `Error del object cloudron-testfile. Message: ${delError.message} HTTP Code: ${delError.$metadata.httpStatusCode}`);
}
function removePrivateFields(apiConfig) {