diff --git a/src/storage/s3.js b/src/storage/s3.js index ebaad32b2..e7a2d6ccb 100644 --- a/src/storage/s3.js +++ b/src/storage/s3.js @@ -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) {