Skip to content

Fix non disposable daemon init/start and attach to running daemons #308

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[![Dependency Status](https://david-dm.org/ipfs/js-ipfsd-ctl.svg?style=flat-square)](https://david-dm.org/ipfs/js-ipfsd-ctl)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/feross/standard)
[![Bundle Size](https://flat.badgen.net/bundlephobia/minzip/ipfsd-ctl)](https://bundlephobia.com/result?p=ipfsd-ctl)

> Spawn IPFS daemons using JavaScript!

## Lead Maintainer
Expand Down Expand Up @@ -84,7 +85,7 @@ server.start((err) => {
`ipfsd-ctl` can spawn `disposable` and `non-disposable` daemons.

- `disposable`- Creates on a temporary repo which will be optionally initialized and started (the default), as well cleaned up on process exit. Great for tests.
- `non-disposable` - Requires the user to initialize and start the node, as well as stop and cleanup after wards. Additionally, a non-disposable will allow you to pass a custom repo using the `repoPath` option, if the `repoPath` is not defined, it will use the default repo for the node type (`$HOME/.ipfs` or `$HOME/.jsipfs`). The `repoPath` parameter is ignored for disposable nodes, as there is a risk of deleting a live repo.
- `non-disposable` - Non disposable daemons will by default attach to any nodes running on the default or the supplied repo. Requires the user to initialize and start the node, as well as stop and cleanup afterwards. Additionally, a non-disposable will allow you to pass a custom repo using the `repoPath` option, if the `repoPath` is not defined, it will use the default repo for the node type (`$HOME/.ipfs` or `$HOME/.jsipfs`). The `repoPath` parameter is ignored for disposable nodes, as there is a risk of deleting a live repo.

## Batteries not included. Bring your own IPFS executable.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"lodash.clone": "^4.5.0",
"lodash.defaults": "^4.2.0",
"lodash.defaultsdeep": "^4.6.0",
"merge-options": "^1.0.1",
"multiaddr": "^5.0.0",
"once": "^1.4.0",
"protons": "^1.0.1",
Expand Down
33 changes: 12 additions & 21 deletions src/endpoint/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,32 +61,25 @@ module.exports = (server) => {
path: '/spawn',
handler: (request, reply) => {
const payload = request.payload || {}

// TODO: use the ../src/index.js so that the right Factory is picked
const f = new FactoryDaemon({ type: payload.type })

f.spawn(payload.options, (err, ipfsd) => {
f.spawn(payload, (err, ipfsd) => {
if (err) {
return reply(boom.badRequest(err))
}
const id = hat()
const initialized = ipfsd.initialized
nodes[id] = ipfsd

let api = null

if (nodes[id].started) {
api = {
apiAddr: nodes[id].apiAddr
? nodes[id].apiAddr.toString()
: '',
gatewayAddr: nodes[id].gatewayAddr
? nodes[id].gatewayAddr.toString()
: ''
}
}

reply({ id: id, api: api, initialized: initialized })
reply({
_id: id,
apiAddr: ipfsd.apiAddr ? ipfsd.apiAddr.toString() : '',
gatewayAddr: ipfsd.gatewayAddr ? ipfsd.gatewayAddr.toString() : '',
initialized: ipfsd.initialized,
started: ipfsd.started,
_env: ipfsd._env,
path: ipfsd.path
})
})
}
})
Expand Down Expand Up @@ -131,10 +124,8 @@ module.exports = (server) => {
}

reply({
api: {
apiAddr: nodes[id].apiAddr.toString(),
gatewayAddr: nodes[id].gatewayAddr.toString()
}
apiAddr: nodes[id].apiAddr.toString(),
gatewayAddr: nodes[id].gatewayAddr ? nodes[id].gatewayAddr.toString() : null
})
})
},
Expand Down
35 changes: 20 additions & 15 deletions src/factory-client.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict'

const request = require('superagent')
const merge = require('merge-options')
const defaultConfig = require('./defaults/config.json')
const DaemonClient = require('./ipfsd-client')

/** @ignore @typedef {import("./index").SpawnOptions} SpawnOptions */
Expand Down Expand Up @@ -90,29 +92,32 @@ class FactoryClient {
options = {}
}

options = options || {}

const daemonOptions = merge({
exec: this.options.exec,
type: this.options.type,
IpfsClient: this.options.IpfsClient,
disposable: true,
start: options.disposable !== false,
init: options.disposable !== false,
config: defaultConfig
}, options)

if (options.defaultAddrs) {
delete daemonOptions.config.Addresses
}
request
.post(`${this.baseUrl}/spawn`)
.send({ options: options, type: this.options.type })
.post(`${this.baseUrl}/spawn`, daemonOptions)
.end((err, res) => {
if (err) {
return callback(new Error(err.response ? err.response.body.message : err))
}

const apiAddr = res.body.api ? res.body.api.apiAddr : ''
const gatewayAddr = res.body.api ? res.body.api.gatewayAddr : ''

const ipfsd = new DaemonClient(
const node = new DaemonClient(
this.baseUrl,
res.body.id,
res.body.initialized,
apiAddr,
gatewayAddr,
{ IpfsClient: this.options.IpfsClient }
res.body,
daemonOptions
)

callback(null, ipfsd)
callback(null, node)
})
}
}
Expand Down
67 changes: 17 additions & 50 deletions src/factory-daemon.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
'use strict'

const defaultsDeep = require('lodash.defaultsdeep')
const clone = require('lodash.clone')
const series = require('async/series')
const path = require('path')
const merge = require('merge-options')
const tmpDir = require('./utils/tmp-dir')
const Daemon = require('./ipfsd-daemon')
const defaultConfig = require('./defaults/config.json')
const defaultOptions = require('./defaults/options.json')

/** @ignore @typedef {import("./index").SpawnOptions} SpawnOptions */

Expand All @@ -23,7 +20,7 @@ class FactoryDaemon {
if (options && options.type === 'proc') {
throw new Error('This Factory does not know how to spawn in proc nodes')
}
this.options = Object.assign({}, { type: 'go' }, options)
this.options = Object.assign({ type: 'go' }, options)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: we've required merge-options above, why not use it here instead of Object.assign?

}

/**
Expand Down Expand Up @@ -52,7 +49,6 @@ class FactoryDaemon {
* - version - the ipfs version
* - repo - the repo version
* - commit - the commit hash for this version
* @returns {void}
*/
version (options, callback) {
if (typeof options === 'function') {
Expand All @@ -76,63 +72,34 @@ class FactoryDaemon {
*
* @param {SpawnOptions} [options={}] - Various config options and ipfs config parameters
* @param {function(Error, Daemon): void} callback - Callback receives Error or a Daemon instance, Daemon has a `api` property which is an `ipfs-http-client` instance.
* @returns {void}
*/
spawn (options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
}

// TODO this options parsing is daunting. Refactor and move to a separate
// func documenting what it is trying to do.
options = defaultsDeep(
{ IpfsClient: this.options.IpfsClient },
options,
defaultOptions
)

options.init = typeof options.init !== 'undefined'
? options.init
: true

if (!options.disposable) {
const nonDisposableConfig = clone(defaultConfig)
options.init = false
options.start = false

const defaultRepo = path.join(
process.env.HOME || process.env.USERPROFILE,
options.isJs
? '.jsipfs'
: '.ipfs'
)

options.repoPath = options.repoPath ||
(process.env.IPFS_PATH || defaultRepo)
options.config = defaultsDeep({}, options.config, nonDisposableConfig)
} else {
options.config = defaultsDeep({}, options.config, defaultConfig)
}
const daemonOptions = merge({
exec: this.options.exec,
type: this.options.type,
IpfsClient: this.options.IpfsClient,
disposable: true,
start: options.disposable !== false,
init: options.disposable !== false,
config: defaultConfig
}, options)

if (options.defaultAddrs) {
delete options.config.Addresses
delete daemonOptions.config.Addresses
}

options.type = this.options.type
options.exec = options.exec || this.options.exec
options.initOptions = defaultsDeep({}, this.options.initOptions, options.initOptions)

const node = new Daemon(options)
const node = new Daemon(daemonOptions)

series([
(cb) => options.init
? node.init(options.initOptions, cb)
: cb(null, node),
(cb) => options.start
? node.start(options.args, cb)
: cb()
], (err) => {
daemonOptions.init && (cb => node.init(daemonOptions.initOptions, cb)),
daemonOptions.start && (cb => node.start(daemonOptions.args, cb))
].filter(Boolean),
(err) => {
if (err) { return callback(err) }

callback(null, node)
Expand Down
89 changes: 27 additions & 62 deletions src/factory-in-proc.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
'use strict'

const defaults = require('lodash.defaultsdeep')
const clone = require('lodash.clone')
const series = require('async/series')
const path = require('path')
const once = require('once')
const merge = require('merge-options')
const tmpDir = require('./utils/tmp-dir')
const repoUtils = require('./utils/repo/nodejs')
const InProc = require('./ipfsd-in-proc')
const defaultConfig = require('./defaults/config.json')
const defaultOptions = require('./defaults/options.json')

/** @ignore @typedef {import("./index").SpawnOptions} SpawnOptions */

Expand Down Expand Up @@ -38,7 +33,6 @@ class FactoryInProc {
*
* @param {string} type - the type of the node
* @param {function(Error, string): void} callback
* @returns {void}
*/
tmpDir (type, callback) {
callback(null, tmpDir(true))
Expand All @@ -49,7 +43,6 @@ class FactoryInProc {
*
* @param {Object} [options={}]
* @param {function(Error, string): void} callback
* @returns {void}
*/
version (options, callback) {
if (typeof options === 'function') {
Expand All @@ -58,79 +51,51 @@ class FactoryInProc {
}

const node = new InProc(options)
node.once('ready', () => {
node.version(callback)
})
node.version(callback)
}

/**
* Spawn JSIPFS instances
*
* @param {SpawnOptions} [opts={}] - various config options and ipfs config parameters
* @param {SpawnOptions} [options={}] - various config options and ipfs config parameters
* @param {function(Error, InProc): void} callback - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node`
* @returns {void}
*/
spawn (opts, callback) {
if (typeof opts === 'function') {
callback = opts
opts = defaultOptions
spawn (options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
}

const options = defaults({}, opts, defaultOptions)
options.init = typeof options.init !== 'undefined'
? options.init
: true

if (options.disposable) {
options.config = defaults({}, options.config, defaultConfig)
} else {
const nonDisposableConfig = clone(defaultConfig)
options.init = false
options.start = false

const defaultRepo = path.join(
process.env.HOME || process.env.USERPROFILE || '',
options.isJs ? '.jsipfs' : '.ipfs'
)

options.repoPath = options.repoPath || (process.env.IPFS_PATH || defaultRepo)
options.config = defaults({}, options.config, nonDisposableConfig)
}
const daemonOptions = merge({
exec: this.options.exec,
type: this.options.type,
IpfsApi: this.options.IpfsApi,
disposable: true,
start: options.disposable !== false,
init: options.disposable !== false,
config: defaultConfig
}, options)

if (options.defaultAddrs) {
delete options.config.Addresses
delete daemonOptions.config.Addresses
}

options.type = this.options.type
options.exec = options.exec || this.options.exec

if (typeof options.exec !== 'function') {
if (typeof this.options.exec !== 'function') {
return callback(new Error(`'type' proc requires 'exec' to be a coderef`))
}

const node = new InProc(options)
const callbackOnce = once((err) => {
if (err) {
return callback(err)
}
callback(null, node)
})
node.once('error', callbackOnce)
const node = new InProc(daemonOptions)

series([
(cb) => node.once('ready', cb),
(cb) => repoUtils.repoExists(node.path, (err, initialized) => {
if (err) { return cb(err) }
node.initialized = initialized
cb()
}),
(cb) => options.init
? node.init(cb)
: cb(),
(cb) => options.start
? node.start(options.args, cb)
: cb()
], callbackOnce)
daemonOptions.init && (cb => node.init(daemonOptions.initOptions, cb)),
daemonOptions.start && (cb => node.start(daemonOptions.args, cb))
].filter(Boolean),
(err) => {
if (err) { return callback(err) }

callback(null, node)
})
}
}

Expand Down
Loading