diff --git a/adapter.js b/adapter.js index c83803d..8777498 100644 --- a/adapter.js +++ b/adapter.js @@ -39,13 +39,15 @@ const { prop, map, always, identity } = R */ /** - * @param {{ minio: any, bucketPrefix: string, useNamespacedBucket: boolean }} config + * @param {{ minio: any, bucketPrefix: string, region?: string, useNamespacedBucket: boolean }} config * @returns hyper storage terminating adapter impl */ export default function (config) { - const { bucketPrefix, useNamespacedBucket, minio } = config + const { bucketPrefix, region, useNamespacedBucket, minio } = config - const lib = useNamespacedBucket ? Namespaced(bucketPrefix) : Multi(bucketPrefix) + const lib = useNamespacedBucket + ? Namespaced({ bucketPrefix, region }) + : Multi({ bucketPrefix, region }) const client = minioClientSchema.parse({ makeBucket: lib.makeBucket(minio), diff --git a/adapter.test.js b/adapter.test.js index 0caf1de..6c45a8a 100644 --- a/adapter.test.js +++ b/adapter.test.js @@ -56,6 +56,7 @@ Deno.test('adapter', async (t) => { const a = adapter({ minio: happyMinio, bucketPrefix: 'test', + region: 'us-east-2', useNamespacedBucket: false, }) diff --git a/lib/multi.js b/lib/multi.js index 9278abd..8d94c52 100644 --- a/lib/multi.js +++ b/lib/multi.js @@ -5,14 +5,28 @@ const { prop, map } = R export const createPrefix = (prefix) => (str) => `${prefix}-${str}` -export const Multi = (bucketPrefix) => { +export const Multi = ({ bucketPrefix, region }) => { if (!bucketPrefix) throw new Error('bucketPrefix is required') const bucketWithPrefix = createPrefix(bucketPrefix) const makeBucket = (minio) => { return asyncifyHandle((name) => - minio.makeBucket(bucketWithPrefix(name)).catch((err) => { + /** + * When using with AWS s3 and coupled with the fact that LocationConstraint + * is not required _only_ for us-east-1 (https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucketConfiguration.html), + * this can cause some hair-pulling gotchas when creating a bucket and not passing region -- the bucket will automatically + * be attempted to be created in us-east-1, REGARDLESS of whichever region used to instantiate the MinIO Client. + * + * So if you're using something like IAM to securely access s3, or a VPC Endpoint for s3 in a region that is _not_ us-east-1, + * you will simply get an opaque S3 AccessDenied error when creating the bucket -- your IAM Role might be constrained to only + * access, say us-east-2, or your VPC Endpoint is for s3.us-east-2.amazonaws.com, and accessing us-east-1 out of the blue + * will simply produce a seemingly incoherent "AccessDenied". -_______- + * + * SO, we MUST pass region here that is provided to the adapter, to ensure the bucket is created in the desired region, + * and any credentials imbued by IAM or the VPC are used. + */ + minio.makeBucket(bucketWithPrefix(name), region).catch((err) => { if (isBucketExistsErr(err)) throw HyperErr({ status: 409, msg: 'bucket already exists' }) throw err // some other err }) diff --git a/lib/multi.test.js b/lib/multi.test.js index e1ade66..a44e45a 100644 --- a/lib/multi.test.js +++ b/lib/multi.test.js @@ -6,7 +6,7 @@ import { Multi } from './multi.js' const { Async } = crocks Deno.test('Multi bucket minio client', async (t) => { - const multi = Multi('test') + const multi = Multi({ bucketPrefix: 'test', region: 'us-east-2' }) await t.step('makeBucket', async (t) => { await t.step('it should resolve if the bucket is created successfully', () => { @@ -25,6 +25,16 @@ Deno.test('Multi bucket minio client', async (t) => { .toPromise() }) + await t.step('it should make the bucket in the region', () => { + return multi.makeBucket({ + makeBucket: (_name, region) => { + assertEquals(region, 'us-east-2') + return Promise.resolve() + }, + })('foo') + .toPromise() + }) + await t.step('it should throw a HyperErr if the bucket exists', () => { return multi.makeBucket({ makeBucket: () => { diff --git a/lib/namespaced.js b/lib/namespaced.js index 8232d93..1695977 100644 --- a/lib/namespaced.js +++ b/lib/namespaced.js @@ -60,10 +60,10 @@ const checkNamespaceExists = (meta, name) => { )) } -export const Namespaced = (bucketPrefix) => { +export const Namespaced = ({ bucketPrefix, region }) => { if (!bucketPrefix) throw new Error('bucketPrefix is required') - const multi = Multi(bucketPrefix) + const multi = Multi({ bucketPrefix, region }) /** * The single bucket used for all objects * diff --git a/lib/namespaced.test.js b/lib/namespaced.test.js index 18a0020..29bc542 100644 --- a/lib/namespaced.test.js +++ b/lib/namespaced.test.js @@ -6,7 +6,7 @@ import { Namespaced } from './namespaced.js' const { Async } = crocks Deno.test('Namespaced bucket minio client', async (t) => { - const namespaced = Namespaced('test') + const namespaced = Namespaced({ bucketPrefix: 'test', region: 'us-east-2' }) const META = { createdAt: new Date().toJSON(), diff --git a/mod.js b/mod.js index 3ff44c9..2080d23 100644 --- a/mod.js +++ b/mod.js @@ -60,6 +60,7 @@ export default ({ url, region, bucketPrefix, useNamespacedBucket }) => { link: (config) => (_) => { return createAdapter({ minio: config.minio, + region: config.region, bucketPrefix: config.bucketPrefix, useNamespacedBucket: config.useNamespacedBucket, })