72d27b9a7c
* Allow save object to S3 in different region * feat(helm & docker-compose): adds S3_REGION to helm chart & docker-compose Explicitly adding the environment variable to deployment configuration files provides system operators with documentation of its existence. Set to empty by default, which will result in the default value being used. Co-authored-by: Iain Sproat <68657+iainsproat@users.noreply.github.com>
132 lines
3.6 KiB
JavaScript
132 lines
3.6 KiB
JavaScript
const { NotFoundError } = require('@/modules/shared/errors')
|
|
const {
|
|
S3Client,
|
|
GetObjectCommand,
|
|
HeadBucketCommand,
|
|
DeleteObjectCommand,
|
|
CreateBucketCommand,
|
|
S3ServiceException
|
|
} = require('@aws-sdk/client-s3')
|
|
const { Upload } = require('@aws-sdk/lib-storage')
|
|
|
|
let s3Config = null
|
|
|
|
const getS3Config = () => {
|
|
if (!s3Config) {
|
|
if (!process.env.S3_ACCESS_KEY)
|
|
throw new Error('Config value S3_ACCESS_KEY is missing')
|
|
if (!process.env.S3_SECRET_KEY)
|
|
throw new Error('Config value S3_SECRET_KEY is missing')
|
|
if (!process.env.S3_ENDPOINT) throw new Error('Config value S3_ENDPOINT is missing')
|
|
s3Config = {
|
|
credentials: {
|
|
accessKeyId: process.env.S3_ACCESS_KEY,
|
|
secretAccessKey: process.env.S3_SECRET_KEY
|
|
},
|
|
endpoint: process.env.S3_ENDPOINT,
|
|
forcePathStyle: true,
|
|
// s3ForcePathStyle: true,
|
|
// signatureVersion: 'v4',
|
|
region: process.env.S3_REGION || 'us-east-1'
|
|
}
|
|
}
|
|
return s3Config
|
|
}
|
|
|
|
let storageBucket = null
|
|
|
|
const getStorageBucket = () => {
|
|
if (!storageBucket) {
|
|
if (!process.env.S3_BUCKET) throw new Error('Config value S3_BUCKET is missing')
|
|
storageBucket = process.env.S3_BUCKET
|
|
}
|
|
return storageBucket
|
|
}
|
|
|
|
const getObjectStorage = () => ({
|
|
client: new S3Client(getS3Config()),
|
|
Bucket: getStorageBucket(),
|
|
createBucket: process.env.S3_CREATE_BUCKET || false
|
|
})
|
|
|
|
const sendCommand = async (command) => {
|
|
const { client, Bucket } = getObjectStorage()
|
|
try {
|
|
return await client.send(command(Bucket))
|
|
} catch (err) {
|
|
if (err instanceof S3ServiceException && err.Code === 'NoSuchKey')
|
|
throw new NotFoundError(err.message)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
const getObjectStream = async ({ objectKey }) => {
|
|
const data = await sendCommand(
|
|
(Bucket) => new GetObjectCommand({ Bucket, Key: objectKey })
|
|
)
|
|
return data.Body
|
|
}
|
|
|
|
const getObjectAttributes = async ({ objectKey }) => {
|
|
const data = await sendCommand(
|
|
(Bucket) => new GetObjectCommand({ Bucket, Key: objectKey })
|
|
)
|
|
return { fileSize: data.ContentLength }
|
|
}
|
|
|
|
const storeFileStream = async ({ objectKey, fileStream }) => {
|
|
const { client, Bucket } = getObjectStorage()
|
|
const parallelUploads3 = new Upload({
|
|
client,
|
|
params: { Bucket, Key: objectKey, Body: fileStream },
|
|
tags: [
|
|
/*...*/
|
|
], // optional tags
|
|
queueSize: 4, // optional concurrency configuration
|
|
partSize: 1024 * 1024 * 5, // optional size of each part, in bytes, at least 5MB
|
|
leavePartsOnError: false // optional manually handle dropped parts
|
|
})
|
|
|
|
// parallelUploads3.on('httpUploadProgress', (progress) => {
|
|
// console.log(progress)
|
|
// })
|
|
|
|
const data = await parallelUploads3.done()
|
|
// the ETag is a hash of the object. Could be used to dedupe stuff...
|
|
const fileHash = data.ETag.replaceAll('"', '')
|
|
return { fileHash }
|
|
}
|
|
|
|
const deleteObject = async ({ objectKey }) => {
|
|
await sendCommand((Bucket) => new DeleteObjectCommand({ Bucket, Key: objectKey }))
|
|
}
|
|
const ensureStorageAccess = async () => {
|
|
const { client, Bucket, createBucket } = getObjectStorage()
|
|
try {
|
|
// await this._client.send(new HeadBucketCommand({ Bucket: this._bucket }))
|
|
await client.send(new HeadBucketCommand({ Bucket }))
|
|
return
|
|
} catch (err) {
|
|
if (err.statusCode === 403) {
|
|
throw new Error('Access denied to S3 bucket ')
|
|
}
|
|
if (createBucket) {
|
|
try {
|
|
await client.send(new CreateBucketCommand({ Bucket }))
|
|
} catch (err) {
|
|
console.log(err)
|
|
}
|
|
} else {
|
|
throw new Error(`Can't open S3 bucket '${Bucket}': ${err.toString()}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
ensureStorageAccess,
|
|
deleteObject,
|
|
getObjectAttributes,
|
|
storeFileStream,
|
|
getObjectStream
|
|
}
|