Skip to content

Commit 9d4230f

Browse files
committed
feat: integration of js-ipfs-repo-migrations
Integration of js-ipfs-repo-migrations brings automatic repo migrations to ipfs-repo (both in-browser and fs). It is possible to control the automatic migration using either config's setting 'repoDisableAutoMigration' or IPFSRepo's option 'disableAutoMigration'. License: MIT Signed-off-by: Adam Uhlir <[email protected]>
1 parent cfcd0b0 commit 9d4230f

12 files changed

+308
-33
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This is the implementation of the [IPFS repo spec](https://github.com/ipfs/specs
2828
- [Use in a browser Using a script tag](#use-in-a-browser-using-a-script-tag)
2929
- [Usage](#usage)
3030
- [API](#api)
31+
- [Notes](#notes)
3132
- [Contribute](#contribute)
3233
- [License](#license)
3334

@@ -318,6 +319,11 @@ Returned promise resolves to a `boolean` indicating the existence of the lock.
318319

319320
- [Explanation of how repo is structured](https://github.com/ipfs/js-ipfs-repo/pull/111#issuecomment-279948247)
320321

322+
### Migrations
323+
324+
When there is a new repo migration and the version of repo is increased, don't
325+
forget to propagate the changes into the test repo (`test/test-repo`).
326+
321327
## Contribute
322328

323329
There are some ways you can make this module better:

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"multihashes": "~0.4.14",
5252
"multihashing-async": "~0.7.0",
5353
"ncp": "^2.0.0",
54-
"rimraf": "^2.6.3"
54+
"rimraf": "^2.6.3",
55+
"sinon": "^7.3.1"
5556
},
5657
"dependencies": {
5758
"base32.js": "~0.1.0",
@@ -64,6 +65,7 @@
6465
"err-code": "^1.1.2",
6566
"interface-datastore": "~0.7.0",
6667
"ipfs-block": "~0.8.1",
68+
"ipfs-repo-migrations": "AuHau/js-ipfs-repo-migrations#dev",
6769
"just-safe-get": "^1.3.0",
6870
"just-safe-set": "^2.1.0",
6971
"lodash.has": "^4.5.2",
@@ -73,6 +75,7 @@
7375
},
7476
"license": "MIT",
7577
"contributors": [
78+
"Adam Uhlir<[email protected]>",
7679
"Alan Shaw <[email protected]>",
7780
"Alex Potsides <[email protected]>",
7881
"Brian Hoffman <[email protected]>",

src/default-options-browser.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
// Default configuration for a repo in the browser
44
module.exports = {
5+
disableAutoMigration: false,
56
lock: 'memory',
67
storageBackends: {
78
root: require('datastore-level'),

src/default-options.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
// Default configuration for a repo in node.js
44
module.exports = {
5+
disableAutoMigration: false,
56
lock: 'fs',
67
storageBackends: {
78
root: require('datastore-fs'),

src/errors/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,21 @@ class NotFoundError extends Error {
1515
NotFoundError.code = 'ERR_NOT_FOUND'
1616
exports.NotFoundError = NotFoundError
1717

18+
/**
19+
* Error raised when version of the stored repo is not compatible with version of this package.
20+
*/
21+
class InvalidRepoVersionError extends Error {
22+
constructor (message) {
23+
super(message)
24+
this.name = 'InvalidRepoVersionError'
25+
this.code = 'ERR_INVALID_REPO_VERSION'
26+
this.message = message
27+
}
28+
}
29+
30+
InvalidRepoVersionError.code = 'ERR_INVALID_REPO_VERSION'
31+
exports.InvalidRepoVersionError = InvalidRepoVersionError
32+
1833
exports.ERR_REPO_NOT_INITIALIZED = 'ERR_REPO_NOT_INITIALIZED'
1934
exports.ERR_REPO_ALREADY_OPEN = 'ERR_REPO_ALREADY_OPEN'
2035
exports.ERR_REPO_ALREADY_CLOSED = 'ERR_REPO_ALREADY_CLOSED'

src/index.js

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ const path = require('path')
66
const debug = require('debug')
77
const Big = require('bignumber.js')
88
const errcode = require('err-code')
9+
const migrator = require('ipfs-repo-migrations')
910

11+
const constants = require('./constants')
1012
const backends = require('./backends')
1113
const version = require('./version')
1214
const config = require('./config')
@@ -26,7 +28,6 @@ const lockers = {
2628
fs: require('./lock')
2729
}
2830

29-
const repoVersion = require('./constants').repoVersion
3031

3132
/**
3233
* IpfsRepo implements all required functionality to read and write to an ipfs repo.
@@ -64,7 +65,7 @@ class IpfsRepo {
6465
await this._openRoot()
6566
await this.config.set(buildConfig(config))
6667
await this.spec.set(buildDatastoreSpec(config))
67-
await this.version.set(repoVersion)
68+
await this.version.set(constants.repoVersion)
6869
}
6970

7071
/**
@@ -92,6 +93,17 @@ class IpfsRepo {
9293
this.blocks = await blockstore(blocksBaseStore, this.options.storageBackendOptions.blocks)
9394
log('creating keystore')
9495
this.keys = backends.create('keys', path.join(this.path, 'keys'), this.options)
96+
97+
if (!await this.version.check(constants.repoVersion)) {
98+
log('Something is fishy')
99+
if (!this.options.disableAutoMigration) {
100+
log('Let see what')
101+
await this._migrate(constants.repoVersion)
102+
} else {
103+
throw new ERRORS.InvalidRepoVersionError('Incompatible repo versions. Automatic migrations disabled. Please migrate the repo manually.')
104+
}
105+
}
106+
95107
this.closed = false
96108
log('all opened')
97109
} catch (err) {
@@ -176,7 +188,7 @@ class IpfsRepo {
176188
[config] = await Promise.all([
177189
this.config.exists(),
178190
this.spec.exists(),
179-
this.version.check(repoVersion)
191+
this.version.exists()
180192
])
181193
} catch (err) {
182194
if (err.code === 'ERR_NOT_FOUND') {
@@ -239,7 +251,7 @@ class IpfsRepo {
239251
* @return {Object}
240252
*/
241253
async stat (options) {
242-
options = Object.assign({}, { human: false }, options)
254+
options = Object.assign({}, {human: false}, options)
243255
let storageMax, blocks, version, datastore, keys
244256
[storageMax, blocks, version, datastore, keys] = await Promise.all([
245257
this._storageMaxStat(),
@@ -264,6 +276,40 @@ class IpfsRepo {
264276
}
265277
}
266278

279+
async _migrate (toVersion) {
280+
let disableMigrationsConfig
281+
try {
282+
disableMigrationsConfig = await this.config.get('repoDisableAutoMigration')
283+
} catch (e) {
284+
if (e.code === ERRORS.NotFoundError.code) {
285+
disableMigrationsConfig = false
286+
} else {
287+
throw e
288+
}
289+
}
290+
291+
if (disableMigrationsConfig) {
292+
throw new ERRORS.InvalidRepoVersionError('Incompatible repo versions. Automatic migrations disabled. Please migrate the repo manually.')
293+
}
294+
295+
const currentRepoVersion = await this.version.get()
296+
log(currentRepoVersion)
297+
if (currentRepoVersion >= toVersion) {
298+
if (currentRepoVersion > toVersion) {
299+
log('Your repo\'s version is higher then this version of js-ipfs-repo require! You should revert it.')
300+
}
301+
302+
log('Nothing to migrate')
303+
return
304+
}
305+
306+
if (toVersion > migrator.getLatestMigrationVersion()) {
307+
throw new Error('The ipfs-repo-migrations package does not have migration for version: ' + toVersion)
308+
}
309+
310+
return migrator.migrate(this.path, {toVersion: toVersion, ignoreLock: true, repoOptions: this.options})
311+
}
312+
267313
async _storageMaxStat () {
268314
try {
269315
const max = await this.config.get('Datastore.StorageMax')
@@ -284,7 +330,7 @@ class IpfsRepo {
284330
.plus(block.key._buf.byteLength)
285331
}
286332

287-
return { count, size }
333+
return {count, size}
288334
}
289335
}
290336

@@ -298,7 +344,7 @@ async function getSize (queryFn) {
298344
}
299345

300346
module.exports = IpfsRepo
301-
module.exports.repoVersion = repoVersion
347+
module.exports.repoVersion = constants.repoVersion
302348
module.exports.errors = ERRORS
303349

304350
function buildOptions (_options) {

src/version.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ module.exports = (store) => {
3636
return store.put(versionKey, Buffer.from(String(version)))
3737
},
3838
/**
39-
* Check the current version, and return an error on missmatch
39+
* Check the current version, and returns true if versions matches
4040
* @param {number} expected
41-
* @returns {void}
41+
* @returns {boolean}
4242
*/
4343
async check (expected) {
4444
const version = await this.get()
@@ -47,9 +47,7 @@ module.exports = (store) => {
4747
// TODO: Clean up the compatibility logic. Repo feature detection would be ideal, or a better version schema
4848
const compatibleVersion = (version === 6 && expected === 7) || (expected === 6 && version === 7)
4949

50-
if (version !== expected && !compatibleVersion) {
51-
throw errcode(new Error(`ipfs repo needs migration: expected version v${expected}, found version v${version}`), 'ERR_INVALID_REPO_VERSION')
52-
}
50+
return version === expected || compatibleVersion
5351
}
5452
}
5553
}

test/browser.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,29 @@
44

55
const IPFSRepo = require('../src')
66

7+
8+
async function createTempRepo ({dontOpen, opts}) {
9+
const date = Date.now().toString()
10+
const repoPath = 'test-repo-for-' + date
11+
12+
const repo = new IPFSRepo(repoPath, opts)
13+
await repo.init({})
14+
15+
if (!dontOpen) {
16+
await repo.open()
17+
}
18+
19+
return {
20+
path: repoPath,
21+
instance: repo,
22+
teardown: async () => {}
23+
}
24+
}
25+
726
describe('IPFS Repo Tests on the Browser', () => {
827
require('./options-test')
28+
require('./migrations-test')(createTempRepo)
29+
930
const repo = new IPFSRepo('myrepo')
1031

1132
before(async () => {

0 commit comments

Comments
 (0)