Skip to content
This repository has been archived by the owner on Oct 1, 2021. It is now read-only.

Commit

Permalink
feat: initial implementation (#1)
Browse files Browse the repository at this point in the history
* Basic setup

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Basic migrator functionality

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Strict mode

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Linting

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Working CLI

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Improving CLI usablity

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Dropping migration class interface

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Improving error handling and other tweaks of migrations

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Refactoring to drop ipfs-repo in favor of datastore

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Refactor migrator to pass only repo's path into migration and not datastore instance.

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* 'add' command for CLI

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Linting

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Writing proper README

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Revert tests

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Migrate tests

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* More tests

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Tests for lock.js and version.js

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Description of tests

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Update interface-datastore dependency

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Fixing package-lock with correct interface-datastore version

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Update wording of CLI reporter

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Correct doctype for optional parameters

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Exposing repo.getVersion

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Improving verification of reversibility

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Adding check if repo is initialized

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Renaming logging key

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Adding parameter ignoreLock

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Adding parameter for options

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Adding documentation for ignoreLock and options parameters

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Instructions on integration with js-ipfs

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Updating dependencies

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Implementing Error's codes

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Refactoring into options object

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Merging revert and migrate CLI commands

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Extanding README with usage and other details

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Integration test

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Removing error message assertions

License: MIT
Signed-off-by: Adam Uhlir <uhlir.a@gmail.com>

* Removing package.json for migrations

* Adding basic sharness tests

* Removing 'reversible' flag from migration's API

* Renaming progressCb parameter to onProgress

* Removing package.json mentions and discussing migration's dependencies

* Linting

* Sharness tests

* Non-throw error handling for getLatestMigrationVersion

* Check if migration exist for migrate()

* Fixing integration tests

* Grammar check

Co-Authored-By: dirkmc <dirkmdev@gmail.com>

* Info about versioning

* Grammar fixes

Co-Authored-By: dirkmc <dirkmdev@gmail.com>

* Messages tweak

Co-Authored-By: dirkmc <dirkmdev@gmail.com>

* Default paramaters

Co-Authored-By: dirkmc <dirkmdev@gmail.com>

* Tweaks

* fix: update functions signature in templates

* doc: CI/codecov/dependencies badges

* style: lint fix

* Message wordings

Co-Authored-By: dirkmc <dirkmdev@gmail.com>

* fix: tweaks

* style: package json lint fix

* fix: update add command

* feat: utils for properly getting Datastore and its options

* feat: when error occures save last succesfull migration version

* style: lint

* fix: apply review suggestions

Co-Authored-By: David Dias <daviddias.p@gmail.com>
  • Loading branch information
AuHau and daviddias authored Oct 17, 2019
1 parent f6a7e24 commit aae9aec
Show file tree
Hide file tree
Showing 38 changed files with 21,002 additions and 1 deletion.
6 changes: 6 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[*]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* text=auto
test/test-repo/** text eol=lf
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
docs

.eslintrc

# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# node-waf configuration
.lock-wscript

build
dist

# Dependency directory
node_modules

# Tests
test/test-repo-for*
test/sharness/tmp
41 changes: 41 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
language: node_js
cache: npm
stages:
- check
- test
- cov

node_js:
- '10'

os:
- linux
- osx
- windows

script: npx nyc -s npm run test:node -- --bail
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov

jobs:
include:
- stage: check
script:
- npx aegir commitlint --travis
- npx aegir dep-check
- npm run lint

- stage: test
name: chrome
addons:
chrome: stable
script: npx aegir test -t browser

- stage: test
name: firefox
addons:
firefox: latest
script: npx aegir test -t browser -- --browsers FirefoxHeadless

notifications:
email: false

278 changes: 277 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,277 @@
# js-ipfs-migrator
# Migration tool for JS IPFS Repo

[![Travis CI](https://flat.badgen.net/travis/ipfs/js-ipfs-repo-migrations)](https://travis-ci.com/ipfs/js-ipfs-repo-migrations)
[![codecov](https://codecov.io/gh/ipfs/js-ipfs-repo/branch/master/graph/badge.svg)](https://codecov.io/gh/ipfs/js-ipfs-repo-migrations)
[![Dependency Status](https://david-dm.org/ipfs/js-ipfs-repo-migrations.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfs-repo-migrations)
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](http://ipn.io)
[![](https://img.shields.io/badge/project-IPFS-blue.svg?style=flat-square)](http://ipfs.io/)
[![](https://img.shields.io/badge/freenode-%23ipfs-blue.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23ipfs)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
![](https://img.shields.io/badge/npm-%3E%3D3.0.0-orange.svg?style=flat-square)
![](https://img.shields.io/badge/Node.js-%3E%3D10.0.0-orange.svg?style=flat-square)

> Migration framework for versioning of JS IPFS Repo
This package is inspired by the [go-ipfs repo migration tool](https://github.com/ipfs/fs-repo-migrations/)

## Lead Maintainer

[Adam Uhlíř](https://github.com/auhau/)

## Table of Contents

- [Background](#background)
- [Install](#install)
- [npm](#npm)
- [Use in Node.js](#use-in-nodejs)
- [Use in a browser with browserify, webpack or any other bundler](#use-in-a-browser-with-browserify-webpack-or-any-other-bundler)
- [Use in a browser Using a script tag](#use-in-a-browser-using-a-script-tag)
- [Usage](#usage)
- [Writing migration](#writing-migration)
- [Migrations matrix](#migrations-matrix)
- [API](#api)
- [CLI](#cli)
- [Versioning](#versioning)
- [Contribute](#contribute)
- [License](#license)

## Background


As js-ipfs evolves and new technologies, algorithms and data structures are incorporated it is necessary to
enable users to transition between versions. Different versions of js-ipfs may expect a different IPFS repo structure or content (see: [IPFS repo spec](https://github.com/ipfs/specs/tree/master/repo), [JS implementation](https://github.com/ipfs/js-ipfs-repo) ).
So the IPFS repo is versioned, and this package provides a framework to create migrations to transition
from one version of IPFS repo to the next/previous version.

This framework:
* Handles locking/unlocking of repository
* Defines migrations API
* Executes and reports migrations in both directions: forward and backward
* Simplifies creation of new migrations
* Works on the browser too!

## Install

### npm

```sh
> npm install ipfs-repo-migrations
```

### Use in Node.js

```js
const migrations = require('ipfs-repo-migrations')
```

### Use in a browser with browserify, webpack or any other bundler

```js
const migrations = require('ipfs-repo-migrations')
```

## Usage

Example:

```js
const migrations = require('ipfs-repo-migrations')
const getVersion = require('ipfs-repo-migrations/repo/version')

const repoPath = 'some/repo/path'
const repoVersion = await getVersion(repoPath)

if(repoVersion < migrations.getLatestMigrationVersion()){
// Old repo! Lets migrate to latest version!
await migrations.migrate(repoPath)
}
```

To migrate your repository using the CLI, see the [how to run migrations](./run.md) tutorial.

## API

### `.migrate(path, {toVersion, ignoreLock, repoOptions, onProgress, isDryRun}) -> Promise<void>`

Executes a forward migration to a specific version, or to the latest version if a specific version is not specified.

**Arguments:**

* `path` (string, mandatory) - path to the repo to be migrated
* `options` (object, optional) - options for the migration
* `options.toVersion` (int, optional) - version to which the repo should be migrated. Defaults to the latest migration version.
* `options.ignoreLock` (bool, optional) - if true will not lock the repo when applying migrations. Use with caution.
* `options.repoOptions` (object, optional) - options that are passed to migrations, that use them to construct the datastore. (options are the same as for IPFSRepo).
* `options.onProgress` (function, optional) - callback that is called after finishing execution of each migration to report progress.
* `options.isDryRun` (bool, optional) - flag that indicates if it is a dry run that should give the same output as running a migration but without making any actual changes.

#### `onProgress(migration, counter, totalMigrations)`

Signature of the progress callback.

**Arguments:**
* `migration` (object) - object of migration that just successfully finished running. See [Architecture of migrations](#architecture-of-migrations) for details.
* `counter` (int) - index of current migration.
* `totalMigrations` (int) - total count of migrations that will be run.

### `.revert(path, toVersion, {ignoreLock, options, onProgress, isDryRun}) -> Promise<void>`

Executes backward migration to a specific version.

**Arguments:**

* `path` (string, mandatory) - path to the repo to be reverted
* `toVersion` (int, mandatory) - version to which the repo should be reverted to.
* `options` (object, optional) - options for the reversion
* `options.ignoreLock` (bool, optional) - if true will not lock the repo when applying migrations. Use with caution.
* `options.options` (object, optional) - options that are passed to migrations, that use them to construct the datastore. (options are the same as for IPFSRepo).
* `options.onProgress` (function, optional) - callback that is called after finishing execution of each migration to report progress.
* `options.isDryRun` (bool, optional) - flag that indicates if it is a dry run that should give the same output as running a migration but without making any actual changes.

### `getLatestMigrationVersion() -> int`

Return the version of the latest migration.

## CLI

The CLI is a NodeJS binary named `jsipfs-repo-migrations`.
It has several commands:

* `migrate` - performs forward/backward migration to specific or latest version.
* `status` - check repo for migrations that should be run.
* `add` - bootstraps new migration.

For further details see the `--help` pages.

## Creating a new migration

Migrations are one of those things that can be extremely painful on users. At the end of the day, we want users never to have to think about it. The process should be:

- SAFE. No data lost. Ever.
- Revertible. Tools must implement forward and backward (if possible) migrations.
- Tests. Migrations have to be well tested.
- To Spec. The tools must conform to the spec.

If your migration has several parts, it should be fail-proof enough that if one part of migration fails the previous changes
are reverted before propagating the error. If possible then the outcome should be consistent repo so it migration could
be run again.

### Architecture of a migration

All migrations are placed in the `/migrations` folder. Each folder there represents one migration that follows the migration
API.

All migrations are collected in `/migrations/index.js`, which should not be edited manually. It is regenerated on
every run of `jsipfs-migrations add` (manual changes should follow the same style of modifications).
**The order of migrations is important and migrations must be sorted in ascending order**.

Each migration must follow this API. It must export an object in its `index.js` that has following properties:

* `version` (int) - Number that represents the version which the repo will migrate to (eg. `migration-8` will move the repo to version 8).
* `description` (string) - Brief description of what the migrations does.
* `migrate` (function) - Function that performs the migration (see signature of this function below)
* `revert` (function) - If defined then this function will revert the migration to the previous version. Otherwise it is assumed that it is not possible to revert this migration.

#### `.migrate(repoPath, isBrowser)`

_Do not confuse this function with the `require('ipfs-repo-migrations').migrate()` function that drives the whole migration process!_

Arguments:
* `repoPath` (string) - absolute path to the root of the repo
* `options` (object, optional) - object containing `IPFSRepo` options, that should be used to construct a datastore instance.
* `isBrowser` (bool) - indicates if the migration is run in a browser environment (as opposed to NodeJS)

#### `.revert(repoPath, isBrowser)`

_Do not confuse this function with the `require('ipfs-repo-migrations').revert()` function that drives the whole backward migration process!_

Arguments:
* `repoPath` (string) - path to the root of the repo
* `options` (object, optional) - object containing `IPFSRepo` options, that should be used to construct the datastore instance.
* `isBrowser` (bool) - indicates if the migration is run in a browser environment (as opposed to NodeJS)

### Browser vs. NodeJS environments

The migration might need to distinguish in which environment it runs (browser vs. NodeJS). For this reason there is an argument
`isBrowser` passed to migrations functions. But with simple migrations it should not be necessary to distinguish between
these environments as the datastore implementation will handle the main differences.

There are currently two main datastore implementations:
1. [`datastore-fs`](https://github.com/ipfs/js-datastore-fs) that is backed by file system and is used mainly in the NodeJS environment
2. [`datastore-level`](https://github.com/ipfs/js-datastore-level) that is backed by LevelDB and is used mainly in the browser environment

Both implementations share the same API and hence are interchangeable.

When the migration is run in a browser environment, `datastore-fs` is automatically replaced with `datastore-level` even
when it is directly imported (`require('datastore-fs')` will return `datastore-level` in a browser).
So with simple migrations you shouldn't worry about the difference between `datastore-fs` and `datastore-level`
and by default use the `datastore-fs` package (as the replace mechanism does not work vice versa).

### Guidelines

The recommended way to write a new migration is to first bootstrap a dummy migration using the CLI:

```sh
> npm run new-migration
```

A new folder is created with the bootstrapped migration. You can then simply fill in the required fields and
write the rest of the migration!

### Integration with js-ipfs

When a new migration is created, the repo version in [`js-ipfs-repo`](https://github.com/ipfs/js-ipfs-repo) should be updated with the new version,
together with updated version of this package. Then the updated version should be propagated to `js-ipfs`.

### Tests

If a migration affects any of the following functionality, it must provide tests for the following functions
to work under the version of the repo that it migrates to:

* `/src/repo/version.js`:`getVersion()` - retrieving repository's version
* `/src/repo/lock.js`:`lock()` - locking repository that uses file system
* `/src/repo/lock-memory.js`:`lock()` - locking repository that uses memory

Every migration must have test coverage. Tests for migrations should be placed in the `/test/migrations/` folder. Most probably
you will have to plug the tests into `browser.js`/`node.js` if they require specific bootstrapping on each platform.

### Empty migrations

For interop with go-ipfs it might be necessary just to bump a version of a repo without any actual
modification as there might not be any changes needed in the JS implementation. For that purpose you can create an "empty migration".

The easiest way to do so is with the CLI:

```sh
> npm run new-migration -- --empty
```

This will create an empty migration with the next version.

### Migrations matrix

| IPFS repo version | JS IPFS version |
| -----------------: |:----------------:|
| 7 | v0.0.0 - latest |

## Developer

### Module versioning notes

In order to have good overview of what version of package contains what kind of migrations, this package follows this versioning schema: `0.<versionOfLastMigration>.<patches>`.

## Contribute

There are some ways you can make this module better:

- Consult our [open issues](https://github.com/ipfs/js-ipfs-repo/issues) and take on one of them
- Help our tests reach 100% coverage!

This repository falls under the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).

[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md)

## License

[MIT](LICENSE)
14 changes: 14 additions & 0 deletions migrations/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict'

// Do not modify this file manually as it will be overridden when running 'add' CLI command.
// Modify migration-templates.js file

const emptyMigration = {
description: 'Empty migration.',
migrate: () => {},
revert: () => {},
empty: true,
}

module.exports = [
]
Loading

0 comments on commit aae9aec

Please sign in to comment.