Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.

Commit 2fcea97

Browse files
author
Jacob Heun
authored
Merge pull request #2 from justinmchase/feature/auto-create-bucket
Create bucket in default region if it doesnt exist
2 parents 54443f5 + 489f0bf commit 2fcea97

File tree

3 files changed

+87
-13
lines changed

3 files changed

+87
-13
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ $ npm install datastore-s3
2626
```
2727

2828
## Usage
29-
A bucket must be created prior to using datastore-s3. Please see the AWS docs for information on how to configure the S3 instance. A bucket name is required to be set at the s3 instance level, see the below example.
29+
If the flag `createIfMissing` is not set or is false, then the bucket must be created prior to using datastore-s3. Please see the AWS docs for information on how to configure the S3 instance. A bucket name is required to be set at the s3 instance level, see the below example.
3030

3131
```js
3232
const S3 = require('aws-sdk').S3
3333
const s3Instance = new S3({ params: { Bucket: 'my-ipfs-bucket' } })
3434
const S3Store = require('datastore-s3')
3535
const store = new S3Store('.ipfs/datastore', {
3636
s3: s3Instance
37+
createIfMissing: false
3738
})
3839
```
3940

src/index.js

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
'use strict'
33

44
/* :: import type {Batch, Query, QueryResult, Callback} from 'interface-datastore' */
5+
const assert = require('assert')
56
const path = require('path')
67
const setImmediate = require('async/setImmediate')
78
const each = require('async/each')
@@ -14,7 +15,8 @@ const Deferred = require('pull-defer')
1415
const pull = require('pull-stream')
1516

1617
/* :: export type S3DSInputOptions = {
17-
s3: S3Instance
18+
s3: S3Instance,
19+
createIfMissing: ?boolean
1820
}
1921
2022
declare type S3Instance = {
@@ -42,20 +44,26 @@ class S3Datastore {
4244
/* :: path: string */
4345
/* :: opts: S3DSInputOptions */
4446
/* :: bucket: string */
47+
/* :: createIfMissing: boolean */
4548

4649
constructor (path /* : string */, opts /* : S3DSInputOptions */) {
4750
this.path = path
4851
this.opts = opts
49-
50-
try {
51-
if (typeof this.opts.s3.config.params.Bucket !== 'string') {
52-
throw new Error()
53-
}
54-
} catch (err) {
55-
throw new Error('An S3 instance with a predefined Bucket must be supplied. See the datastore-s3 README for examples')
56-
}
57-
58-
this.bucket = this.opts.s3.config.params.Bucket
52+
const {
53+
createIfMissing = false,
54+
s3: {
55+
config: {
56+
params: {
57+
Bucket
58+
} = {}
59+
} = {}
60+
} = {}
61+
} = opts
62+
63+
assert(typeof Bucket === 'string', 'An S3 instance with a predefined Bucket must be supplied. See the datastore-s3 README for examples.')
64+
assert(typeof createIfMissing === 'boolean', `createIfMissing must be a boolean but was (${typeof createIfMissing}) ${createIfMissing}`)
65+
this.bucket = Bucket
66+
this.createIfMissing = createIfMissing
5967
}
6068

6169
/**
@@ -81,7 +89,14 @@ class S3Datastore {
8189
Key: this._getFullKey(key),
8290
Body: val
8391
}, (err, data) => {
84-
callback(err)
92+
if (err && err.code === 'NoSuchBucket' && this.createIfMissing) {
93+
this.opts.s3.createBucket({}, (err) => {
94+
if (err) return callback(err)
95+
setImmediate(() => this.put(key, val, callback))
96+
})
97+
} else {
98+
callback(err)
99+
}
85100
})
86101
}
87102

test/index.spec.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ describe('S3Datastore', () => {
2222
() => new S3Store('.ipfs/datastore', { s3 })
2323
).to.throw()
2424
})
25+
it('createIfMissing defaults to false', () => {
26+
const s3 = new S3({ params: { Bucket: 'test' } })
27+
const store = new S3Store('.ipfs', { s3 })
28+
expect(store.createIfMissing).to.equal(false)
29+
})
30+
it('createIfMissing can be set to true', () => {
31+
const s3 = new S3({ params: { Bucket: 'test' } })
32+
const store = new S3Store('.ipfs', { s3, createIfMissing: true })
33+
expect(store.createIfMissing).to.equal(true)
34+
})
2535
})
2636

2737
describe('put', () => {
@@ -37,6 +47,54 @@ describe('S3Datastore', () => {
3747

3848
store.put(new Key('/z/key'), Buffer.from('test data'), done)
3949
})
50+
it('should create the bucket when missing if createIfMissing is true', (done) => {
51+
const s3 = new S3({ params: { Bucket: 'my-ipfs-bucket' } })
52+
const store = new S3Store('.ipfs/datastore', { s3, createIfMissing: true })
53+
54+
// 1. On the first call upload will fail with a NoSuchBucket error
55+
// 2. This should result in the `createBucket` standin being called
56+
// 3. upload is then called a second time and it passes
57+
58+
let bucketCreated = false
59+
standin.replace(s3, 'upload', (stand, params, callback) => {
60+
if (!bucketCreated) return callback({ code: 'NoSuchBucket' })
61+
stand.restore()
62+
return callback(null)
63+
})
64+
65+
standin.replace(s3, 'createBucket', (stand, params, callback) => {
66+
bucketCreated = true
67+
stand.restore()
68+
return callback()
69+
})
70+
71+
store.put(new Key('/z/key'), Buffer.from('test data'), done)
72+
})
73+
it('should create the bucket when missing if createIfMissing is true', (done) => {
74+
const s3 = new S3({ params: { Bucket: 'my-ipfs-bucket' } })
75+
const store = new S3Store('.ipfs/datastore', { s3, createIfMissing: false })
76+
77+
let bucketCreated = false
78+
standin.replace(s3, 'upload', (stand, params, callback) => {
79+
if (!bucketCreated) return callback({ code: 'NoSuchBucket' })
80+
stand.restore()
81+
return callback(null)
82+
})
83+
84+
standin.replace(s3, 'createBucket', (stand, params, callback) => {
85+
bucketCreated = true
86+
stand.restore()
87+
return callback()
88+
})
89+
90+
store.put(new Key('/z/key'), Buffer.from('test data'), (err) => {
91+
expect(bucketCreated).to.equal(false)
92+
expect(err).to.deep.equal({
93+
code: 'NoSuchBucket'
94+
})
95+
done()
96+
})
97+
})
4098
})
4199

42100
describe('get', () => {

0 commit comments

Comments
 (0)