Files
speckle-server/packages/server/modules/blobstorage/objectStorage.js
T
Gergő Jedlicska a54fa8c28f Test blob storage (#814)
* feat(server): add server authz pipeline rework first sketch

* feat(server authz): add new server authz middleware poc implementation

* test(server authz): add unittests for the new server authz workflow

* feat(wip rework of fileuploads vs blob storage): add basim impl of separate blob storage service

* feat(fileimport service): refactored file import service to utilize the new asssetstorage service

* refactor(server errors): refactor server errors to use the shared module definitions

Now all the errors inherit from BaseError

* refactor(fileimport service): cleanup after refactor

* feat(frontend fileimports): use the new blob storage for downloading the original file

* refactor(server fileimports): clean up the remnants of S3 storage from file imports

* refactor(server authz): centralize generic authz pipeline configs

* refactor(server blob storage): refactor / rename everything to use the `blob-storage` name

* ci(circleci): add s3 objectstorage environment variables

* ci(circleci): fix missing env variables

* ci(circleci): add minio test container

* ci(circleci): fix minio app startup

* ci(circleci): enable circleci remote docker

* ci(circleci): fix minio startup

* ci(cirleci): detach and wait properly for minio to start

* ci(circleci): revert to additional minio img config, it only fails when the container is stopped ?!

* ci(circleci): disable file uploads

* fix(fileimports): update with blob storage refactor leftovers

* feat(server blob storage): add blob storage graphql api

* refactor(server errors): merge new errors to shared module

* fix(server comments rte): fix import for RTE error

* chore(fileimports): remove node-fetch from dependency

* chore(server): remove body parser dependency

* fix(server blob storage): fix gql api

* fix(frontend): fix fileupload item not loading the new upload status, cause of premature event fire

* feat(server blob storage): fix file size limit and allow for public streams

* Update packages/server/modules/blobstorage/graph/schemas/blobstorage.graphql

Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>

* chore(blobstorage): fix PR review issues

* fix(server): fix import bugs

* test(server): blob storage first test

* test(server blob services): add tests for blob storage services

* test(server blob storage): add service and rest api tests

* test(server blob storage): add server blob storage graphql api tests

* feat(server blob storage): store and make available blob fileHash attribute

* feat(server authz): add fatal failure option to server authz pipeline

Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
2022-06-23 16:35:42 +02:00

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: '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
}