From fb0c80e52ac0613783f6f970a40a1f059c4b47b1 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 10 Nov 2017 19:53:32 -0800 Subject: [PATCH 01/85] feat: add ability to start js-ipfs nodes --- package.json | 2 +- src/daemon.js | 26 +- ...ns.spec.js => spawning-go-daemons.spec.js} | 0 test/spawning-js-daemons.spec.js | 462 ++++++++++++++++++ 4 files changed, 477 insertions(+), 13 deletions(-) rename test/{spawning-daemons.spec.js => spawning-go-daemons.spec.js} (100%) create mode 100644 test/spawning-js-daemons.spec.js diff --git a/package.json b/package.json index bd1f783e..9b076899 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "lint": "aegir lint", "coverage": "aegir coverage", - "test": "aegir test -t node", + "test": "aegir test --target node", "docs": "aegir docs", "release": "aegir release -t node", "release-minor": "aegir release --type minor -t node", diff --git a/src/daemon.js b/src/daemon.js index e5cf42ab..6da11b99 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -14,13 +14,9 @@ const isWindows = os.platform() === 'win32' const exec = require('./exec') -const ipfsDefaultPath = findIpfsExecutable() - const GRACE_PERIOD = 7500 // amount of ms to wait before sigkill -function findIpfsExecutable () { - const rootPath = process.env.testpath ? process.env.testpath : __dirname - +function findIpfsExecutable (isJs, rootPath) { let appRoot = path.join(rootPath, '..') // If inside .asar try to load from .asar.unpacked // this only works if asar was built with @@ -31,7 +27,9 @@ function findIpfsExecutable () { appRoot = appRoot.replace(`.asar${path.sep}`, `.asar.unpacked${path.sep}`) } const appName = isWindows ? 'ipfs.exe' : 'ipfs' - const depPath = path.join('go-ipfs-dep', 'go-ipfs', appName) + const depPath = isJs + ? path.join('ipfs', 'src', 'cli', 'bin.js') + : path.join('go-ipfs-dep', 'go-ipfs', appName) const npm3Path = path.join(appRoot, '../', depPath) const npm2Path = path.join(appRoot, 'node_modules', depPath) @@ -92,13 +90,17 @@ class Node { * @returns {Node} */ constructor (path, opts, disposable) { - this.path = path - this.opts = opts || {} - this.exec = process.env.IPFS_EXEC || ipfsDefaultPath + const rootPath = process.env.testpath ? process.env.testpath : __dirname + const isJS = process.env.IPFS_JS && process.env.IPFS_JS === 'true' + + this.path = path || null + this.opts = opts || { isJs: false } + this.isJs = this.opts.isJs || isJS || false + this.exec = process.env.IPFS_EXEC || findIpfsExecutable(this.isJs, rootPath) this.subprocess = null this.initialized = fs.existsSync(path) this.clean = true - this.env = Object.assign({}, process.env, { IPFS_PATH: path }) + this.env = path ? Object.assign({}, process.env, { IPFS_PATH: path }) : process.env this.disposable = disposable this._apiAddr = null this._gatewayAddr = null @@ -251,11 +253,11 @@ class Node { output += String(data) const apiMatch = want.api - ? output.trim().match(/API server listening on (.*)/) + ? output.trim().match(/API (?:server|is) listening on[:]? (.*)/) : true const gwMatch = want.gateway - ? output.trim().match(/Gateway (.*) listening on (.*)/) + ? output.trim().match(/Gateway (?:.*)? 27listening on[:]?(.*)/) : true if (apiMatch && gwMatch && !returned) { diff --git a/test/spawning-daemons.spec.js b/test/spawning-go-daemons.spec.js similarity index 100% rename from test/spawning-daemons.spec.js rename to test/spawning-go-daemons.spec.js diff --git a/test/spawning-js-daemons.spec.js b/test/spawning-js-daemons.spec.js new file mode 100644 index 00000000..c63c528a --- /dev/null +++ b/test/spawning-js-daemons.spec.js @@ -0,0 +1,462 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ +'use strict' + +const async = require('async') +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) +const ipfsApi = require('ipfs-api') +const multiaddr = require('multiaddr') +const fs = require('fs') +const rimraf = require('rimraf') +const path = require('path') +const once = require('once') +const os = require('os') + +const exec = require('../src/exec') +const ipfsd = require('../src') + +const isWindows = os.platform() === 'win32' + +describe('daemon spawning', () => { + describe('local daemon', () => { + const repoPath = '/tmp/ipfsd-ctl-test' + const addr = '/ip4/127.0.0.1/tcp/5678' + const config = { + Addresses: { + API: addr + } + } + + it('allows passing flags to init', (done) => { + async.waterfall([ + (cb) => ipfsd.local(repoPath, config, cb), + (node, cb) => { + async.series([ + (cb) => node.init(cb), + (cb) => node.getConfig('Addresses.API', cb) + ], (err, res) => { + expect(err).to.not.exist() + expect(res[1]).to.be.eql(addr) + rimraf(repoPath, cb) + }) + } + ], done) + }) + }) + + describe('disposable daemon', () => { + const blorb = Buffer.from('blorb') + let ipfs + let store + let retrieve + + beforeEach((done) => { + async.waterfall([ + (cb) => ipfs.block.put(blorb, cb), + (block, cb) => { + store = block.cid.toBaseEncodedString() + ipfs.block.get(store, cb) + }, + (_block, cb) => { + retrieve = _block.data + cb() + } + ], done) + }) + + describe('without api instance (.disposable)', () => { + before((done) => { + async.waterfall([ + (cb) => ipfsd.disposable(cb), + (node, cb) => { + node.startDaemon((err) => { + console.log(`WHERE HERE!!`) + expect(err).to.not.exist() + ipfs = ipfsApi(node.apiAddr) + cb() + }) + } + ], done) + }) + + it('should have started the daemon and returned an api', () => { + expect(ipfs).to.exist() + expect(ipfs.id).to.exist() + }) + + it('should be able to store objects', () => { + expect(store) + .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.be.eql('blorb') + }) + }) + + describe('with api instance (.disposableApi)', () => { + before((done) => { + ipfsd.disposableApi((err, api) => { + expect(err).to.not.exist() + + ipfs = api + done() + }) + }) + + it('should have started the daemon and returned an api with host/port', () => { + expect(ipfs).to.have.property('id') + expect(ipfs).to.have.property('apiHost') + expect(ipfs).to.have.property('apiPort') + }) + + it('should be able to store objects', () => { + expect(store) + .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.equal('blorb') + }) + }) + }) + + describe('starting and stopping', () => { + let node + + describe('init', () => { + before((done) => { + ipfsd.disposable((err, res) => { + if (err) { + done(err) + } + node = res + done() + }) + }) + + it('should returned a node', () => { + expect(node).to.exist() + }) + + it('daemon should not be running', () => { + expect(node.daemonPid()).to.not.exist() + }) + }) + + let pid + + describe('starting', () => { + let ipfs + + before((done) => { + node.startDaemon((err, res) => { + expect(err).to.not.exist() + + pid = node.daemonPid() + ipfs = res + + // actually running? + done = once(done) + exec('kill', ['-0', pid], { cleanup: true }, () => done()) + }) + }) + + it('should be running', () => { + expect(ipfs.id).to.exist() + }) + }) + + describe('stopping', () => { + let stopped = false + + before((done) => { + node.stopDaemon((err) => { + expect(err).to.not.exist() + stopped = true + }) + + // make sure it's not still running + const poll = setInterval(() => { + exec('kill', ['-0', pid], { cleanup: true }, { + error () { + clearInterval(poll) + done() + // so it does not get called again + done = () => {} + } + }) + }, 100) + }) + + it('should be stopped', () => { + expect(node.daemonPid()).to.not.exist() + expect(stopped).to.equal(true) + }) + }) + }) + + describe('setting up and init a local node', () => { + const testpath1 = '/tmp/ipfstestpath1' + + describe('cleanup', () => { + before((done) => { + rimraf(testpath1, done) + }) + + it('should not have a directory', () => { + expect(fs.existsSync('/tmp/ipfstestpath1')).to.be.eql(false) + }) + }) + + describe('setup', () => { + let node + before((done) => { + ipfsd.local(testpath1, (err, res) => { + if (err) { + return done(err) + } + node = res + done() + }) + }) + + it('should have returned a node', () => { + expect(node).to.exist() + }) + + it('should not be initialized', () => { + expect(node.initialized).to.be.eql(false) + }) + + describe('initialize', () => { + before((done) => { + node.init(done) + }) + + it('should have made a directory', () => { + expect(fs.existsSync(testpath1)).to.be.eql(true) + }) + + it('should be initialized', () => { + expect(node.initialized).to.be.eql(true) + }) + + it('should be initialized', () => { + expect(node.initialized).to.be.eql(true) + }) + }) + }) + }) + + describe('change config of a disposable node', () => { + let ipfsNode + + before((done) => { + ipfsd.disposable((err, node) => { + if (err) { + return done(err) + } + ipfsNode = node + done() + }) + }) + + it('Should return a config value', (done) => { + ipfsNode.getConfig('Bootstrap', (err, config) => { + expect(err).to.not.exist() + expect(config).to.exist() + done() + }) + }) + + it('Should return the whole config', (done) => { + ipfsNode.getConfig((err, config) => { + expect(err).to.not.exist() + expect(config).to.exist() + done() + }) + }) + + it('Should set a config value', (done) => { + async.series([ + (cb) => ipfsNode.setConfig('Bootstrap', 'null', cb), + (cb) => ipfsNode.getConfig('Bootstrap', cb) + ], (err, res) => { + expect(err).to.not.exist() + expect(res[1]).to.be.eql('null') + done() + }) + }) + + it('should give an error if setting an invalid config value', (done) => { + ipfsNode.setConfig('Bootstrap', 'true', (err) => { + expect(err.message).to.match(/failed to set config value/) + done() + }) + }) + }) + + it('allows passing via $IPFS_EXEC', (done) => { + process.env.IPFS_EXEC = '/some/path' + ipfsd.local((err, node) => { + expect(err).to.not.exist() + expect(node.exec).to.be.eql('/some/path') + + process.env.IPFS_EXEC = '' + done() + }) + }) + + it('prints the version', (done) => { + ipfsd.version((err, version) => { + expect(err).to.not.exist() + expect(version).to.be.eql('js-ipfs version: 0.26.0') + done() + }) + }) + + describe('ipfs-api version', () => { + let ipfs + + before((done) => { + ipfsd.disposable((err, node) => { + expect(err).to.not.exist() + node.startDaemon((err, ignore) => { + expect(err).to.not.exist() + ipfs = ipfsApi(node.apiAddr) + done() + }) + }) + }) + + // skip on windows for now + // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 + // fails on windows see https://github.com/ipfs/js-ipfs-api/issues/408 + if (isWindows) { + return it.skip('uses the correct ipfs-api') + } + + it('uses the correct ipfs-api', (done) => { + ipfs.util.addFromFs(path.join(__dirname, 'fixtures/'), { + recursive: true + }, (err, res) => { + expect(err).to.not.exist() + + const added = res[res.length - 1] + + // Temporary: Need to see what is going on on windows + expect(res).to.deep.equal([ + { + path: 'fixtures/test.txt', + hash: 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD', + size: 19 + }, + { + path: 'fixtures', + hash: 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU', + size: 73 + } + ]) + + expect(res.length).to.equal(2) + expect(added).to.have.property('path', 'fixtures') + expect(added).to.have.property( + 'hash', + 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU' + ) + expect(res[0]).to.have.property('path', 'fixtures/test.txt') + expect(res[0]).to.have.property( + 'hash', + 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD' + ) + done() + }) + }) + }) + + describe('startDaemon', () => { + it('start and stop', (done) => { + const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` + + const check = (cb) => { + // skip on windows + // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326983530 + if (!isWindows) { + if (fs.existsSync(path.join(dir, 'repo.lock'))) { + cb(new Error('repo.lock not removed')) + } + if (fs.existsSync(path.join(dir, 'api'))) { + cb(new Error('api file not removed')) + } + } + cb() + } + + async.waterfall([ + (cb) => ipfsd.local(dir, cb), + (node, cb) => node.init((err) => cb(err, node)), + (node, cb) => node.startDaemon((err) => cb(err, node)), + (node, cb) => node.stopDaemon(cb), + check, + (cb) => ipfsd.local(dir, cb), + (node, cb) => node.startDaemon((err) => cb(err, node)), + (node, cb) => node.stopDaemon(cb), + check, + (cb) => ipfsd.local(dir, cb), + (node, cb) => node.startDaemon((err) => cb(err, node)), + (node, cb) => node.stopDaemon(cb), + check + ], done) + }) + + it('starts the daemon and returns valid API and gateway addresses', (done) => { + const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` + + async.waterfall([ + (cb) => ipfsd.local(dir, cb), + (daemon, cb) => daemon.init((err) => cb(err, daemon)), + (daemon, cb) => daemon.startDaemon((err, api) => cb(err, daemon, api)) + ], (err, daemon, api) => { + expect(err).to.not.exist() + + // Check for props in daemon + expect(daemon).to.have.property('apiAddr') + expect(daemon).to.have.property('gatewayAddr') + expect(daemon.apiAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(daemon.apiAddr)).to.equal(true) + expect(daemon.gatewayAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(daemon.gatewayAddr)).to.equal(true) + + // Check for props in ipfs-api instance + expect(api).to.have.property('apiHost') + expect(api).to.have.property('apiPort') + expect(api).to.have.property('gatewayHost') + expect(api).to.have.property('gatewayPort') + expect(api.apiHost).to.equal('127.0.0.1') + expect(api.apiPort).to.equal('5001') + expect(api.gatewayHost).to.equal('127.0.0.1') + expect(api.gatewayPort).to.equal('8080') + + daemon.stopDaemon(done) + }) + }) + + it('allows passing flags', (done) => { + ipfsd.disposable((err, node) => { + expect(err).to.not.exist() + + node.startDaemon(['--should-not-exist'], (err) => { + expect(err).to.exist() + expect(err.message) + .to.match(/Unrecognized option 'should-not-exist'/) + + done() + }) + }) + }) + }) +}) From 04476b024d9fb74d7d03b5e6080147896aa3d71c Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Mon, 13 Nov 2017 02:33:25 -0800 Subject: [PATCH 02/85] fix: test & cleanup --- src/daemon.js | 15 +- ...emons.spec.js => spawning-daemons.spec.js} | 56 ++- test/spawning-js-daemons.spec.js | 462 ------------------ 3 files changed, 43 insertions(+), 490 deletions(-) rename test/{spawning-go-daemons.spec.js => spawning-daemons.spec.js} (90%) delete mode 100644 test/spawning-js-daemons.spec.js diff --git a/src/daemon.js b/src/daemon.js index 6da11b99..329b0bfa 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -14,7 +14,7 @@ const isWindows = os.platform() === 'win32' const exec = require('./exec') -const GRACE_PERIOD = 7500 // amount of ms to wait before sigkill +const GRACE_PERIOD = 10500 // amount of ms to wait before sigkill function findIpfsExecutable (isJs, rootPath) { let appRoot = path.join(rootPath, '..') @@ -91,11 +91,12 @@ class Node { */ constructor (path, opts, disposable) { const rootPath = process.env.testpath ? process.env.testpath : __dirname - const isJS = process.env.IPFS_JS && process.env.IPFS_JS === 'true' + const isJS = process.env.IPFS_JS && process.env.IPFS_JS === 'true' // TODO: handle proper truthy/falsy + + this.opts = opts || { isJs: isJS || false } + process.env.IPFS_JS = this.opts.isJs this.path = path || null - this.opts = opts || { isJs: false } - this.isJs = this.opts.isJs || isJS || false this.exec = process.env.IPFS_EXEC || findIpfsExecutable(this.isJs, rootPath) this.subprocess = null this.initialized = fs.existsSync(path) @@ -240,7 +241,7 @@ class Node { .filter(Boolean) .slice(-1)[0] || '' - if (input.match('daemon is running')) { + if (input.match(/(?:daemon is running|Daemon is ready)/)) { // we're good return callback(null, this.api) } @@ -257,7 +258,7 @@ class Node { : true const gwMatch = want.gateway - ? output.trim().match(/Gateway (?:.*)? 27listening on[:]?(.*)/) + ? output.trim().match(/Gateway (?:.*) listening on[:]?(.*)/) : true if (apiMatch && gwMatch && !returned) { @@ -271,7 +272,7 @@ class Node { } if (want.gateway) { - this._gatewayAddr = multiaddr(gwMatch[2]) + this._gatewayAddr = multiaddr(gwMatch[1]) this.api.gatewayHost = this.gatewayAddr.nodeAddress().address this.api.gatewayPort = this.gatewayAddr.nodeAddress().port } diff --git a/test/spawning-go-daemons.spec.js b/test/spawning-daemons.spec.js similarity index 90% rename from test/spawning-go-daemons.spec.js rename to test/spawning-daemons.spec.js index 3d8e8d8f..5a67d2c0 100644 --- a/test/spawning-go-daemons.spec.js +++ b/test/spawning-daemons.spec.js @@ -18,10 +18,13 @@ const os = require('os') const exec = require('../src/exec') const ipfsd = require('../src') +const VERSION_STRING = process.env.IPFS_JS ? 'js-ipfs version: 0.26.0' : 'ipfs version 0.4.11' +const API_PORT = process.env.IPFS_JS ? '5002' : '5001' +const GW_PORT = process.env.IPFS_JS ? '9090' : '8080' + const isWindows = os.platform() === 'win32' describe('daemon spawning', function () { this.timeout(60 * 1000) - describe('local daemon', () => { const repoPath = path.join(os.tmpdir(), 'ipfsd-ctl-test') const addr = '/ip4/127.0.0.1/tcp/5678' @@ -292,11 +295,15 @@ describe('daemon spawning', function () { }) }) - it('should give an error if setting an invalid config value', (done) => { - ipfsNode.setConfig('Bootstrap', 'true', (err) => { - expect(err.message).to.match(/failed to set config value/) - done() - }) + it('should give an error if setting an invalid config value', function (done) { + if (!process.env.IPFS_JS) { // TODO: handle proper truthy/falsy + ipfsNode.setConfig('Bootstrap', 'true', (err) => { + expect(err.message).to.match(/failed to set config value/) + done() + }) + } else { + this.skip() + } }) }) @@ -314,7 +321,7 @@ describe('daemon spawning', function () { it('prints the version', (done) => { ipfsd.version((err, version) => { expect(err).to.not.exist() - expect(version).to.equal('ipfs version 0.4.13') + expect(version).to.be.eql(VERSION_STRING) done() }) }) @@ -372,7 +379,8 @@ describe('daemon spawning', function () { }) describe('startDaemon', () => { - it('start and stop', (done) => { + it('start and stop', function (done) { + this.timeout(20000) const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` const check = (cb) => { @@ -406,7 +414,9 @@ describe('daemon spawning', function () { ], done) }) - it('starts the daemon and returns valid API and gateway addresses', (done) => { + it('starts the daemon and returns valid API and gateway addresses', function (done) { + this.timeout(20000) + const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` async.waterfall([ @@ -430,26 +440,30 @@ describe('daemon spawning', function () { expect(api).to.have.property('gatewayHost') expect(api).to.have.property('gatewayPort') expect(api.apiHost).to.equal('127.0.0.1') - expect(api.apiPort).to.equal('5001') + console.log(API_PORT) + expect(api.apiPort).to.equal(API_PORT) expect(api.gatewayHost).to.equal('127.0.0.1') - expect(api.gatewayPort).to.equal('8080') + expect(api.gatewayPort).to.equal(GW_PORT) daemon.stopDaemon(done) }) }) - it('allows passing flags', (done) => { - ipfsd.disposable((err, node) => { - expect(err).to.not.exist() - - node.startDaemon(['--should-not-exist'], (err) => { - expect(err).to.exist() - expect(err.message) - .to.match(/Unrecognized option 'should-not-exist'/) + it('allows passing flags', function (done) { + if (!process.env.IPFS_JS) { + ipfsd.disposable((err, node) => { + expect(err).to.not.exist() + node.startDaemon(['--should-not-exist'], (err) => { + expect(err).to.exist() + expect(err.message) + .to.match(/Unrecognized option 'should-not-exist'/) - done() + done() + }) }) - }) + } else { + this.skip() + } }) }) }) diff --git a/test/spawning-js-daemons.spec.js b/test/spawning-js-daemons.spec.js deleted file mode 100644 index c63c528a..00000000 --- a/test/spawning-js-daemons.spec.js +++ /dev/null @@ -1,462 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ -'use strict' - -const async = require('async') -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const ipfsApi = require('ipfs-api') -const multiaddr = require('multiaddr') -const fs = require('fs') -const rimraf = require('rimraf') -const path = require('path') -const once = require('once') -const os = require('os') - -const exec = require('../src/exec') -const ipfsd = require('../src') - -const isWindows = os.platform() === 'win32' - -describe('daemon spawning', () => { - describe('local daemon', () => { - const repoPath = '/tmp/ipfsd-ctl-test' - const addr = '/ip4/127.0.0.1/tcp/5678' - const config = { - Addresses: { - API: addr - } - } - - it('allows passing flags to init', (done) => { - async.waterfall([ - (cb) => ipfsd.local(repoPath, config, cb), - (node, cb) => { - async.series([ - (cb) => node.init(cb), - (cb) => node.getConfig('Addresses.API', cb) - ], (err, res) => { - expect(err).to.not.exist() - expect(res[1]).to.be.eql(addr) - rimraf(repoPath, cb) - }) - } - ], done) - }) - }) - - describe('disposable daemon', () => { - const blorb = Buffer.from('blorb') - let ipfs - let store - let retrieve - - beforeEach((done) => { - async.waterfall([ - (cb) => ipfs.block.put(blorb, cb), - (block, cb) => { - store = block.cid.toBaseEncodedString() - ipfs.block.get(store, cb) - }, - (_block, cb) => { - retrieve = _block.data - cb() - } - ], done) - }) - - describe('without api instance (.disposable)', () => { - before((done) => { - async.waterfall([ - (cb) => ipfsd.disposable(cb), - (node, cb) => { - node.startDaemon((err) => { - console.log(`WHERE HERE!!`) - expect(err).to.not.exist() - ipfs = ipfsApi(node.apiAddr) - cb() - }) - } - ], done) - }) - - it('should have started the daemon and returned an api', () => { - expect(ipfs).to.exist() - expect(ipfs.id).to.exist() - }) - - it('should be able to store objects', () => { - expect(store) - .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.be.eql('blorb') - }) - }) - - describe('with api instance (.disposableApi)', () => { - before((done) => { - ipfsd.disposableApi((err, api) => { - expect(err).to.not.exist() - - ipfs = api - done() - }) - }) - - it('should have started the daemon and returned an api with host/port', () => { - expect(ipfs).to.have.property('id') - expect(ipfs).to.have.property('apiHost') - expect(ipfs).to.have.property('apiPort') - }) - - it('should be able to store objects', () => { - expect(store) - .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.equal('blorb') - }) - }) - }) - - describe('starting and stopping', () => { - let node - - describe('init', () => { - before((done) => { - ipfsd.disposable((err, res) => { - if (err) { - done(err) - } - node = res - done() - }) - }) - - it('should returned a node', () => { - expect(node).to.exist() - }) - - it('daemon should not be running', () => { - expect(node.daemonPid()).to.not.exist() - }) - }) - - let pid - - describe('starting', () => { - let ipfs - - before((done) => { - node.startDaemon((err, res) => { - expect(err).to.not.exist() - - pid = node.daemonPid() - ipfs = res - - // actually running? - done = once(done) - exec('kill', ['-0', pid], { cleanup: true }, () => done()) - }) - }) - - it('should be running', () => { - expect(ipfs.id).to.exist() - }) - }) - - describe('stopping', () => { - let stopped = false - - before((done) => { - node.stopDaemon((err) => { - expect(err).to.not.exist() - stopped = true - }) - - // make sure it's not still running - const poll = setInterval(() => { - exec('kill', ['-0', pid], { cleanup: true }, { - error () { - clearInterval(poll) - done() - // so it does not get called again - done = () => {} - } - }) - }, 100) - }) - - it('should be stopped', () => { - expect(node.daemonPid()).to.not.exist() - expect(stopped).to.equal(true) - }) - }) - }) - - describe('setting up and init a local node', () => { - const testpath1 = '/tmp/ipfstestpath1' - - describe('cleanup', () => { - before((done) => { - rimraf(testpath1, done) - }) - - it('should not have a directory', () => { - expect(fs.existsSync('/tmp/ipfstestpath1')).to.be.eql(false) - }) - }) - - describe('setup', () => { - let node - before((done) => { - ipfsd.local(testpath1, (err, res) => { - if (err) { - return done(err) - } - node = res - done() - }) - }) - - it('should have returned a node', () => { - expect(node).to.exist() - }) - - it('should not be initialized', () => { - expect(node.initialized).to.be.eql(false) - }) - - describe('initialize', () => { - before((done) => { - node.init(done) - }) - - it('should have made a directory', () => { - expect(fs.existsSync(testpath1)).to.be.eql(true) - }) - - it('should be initialized', () => { - expect(node.initialized).to.be.eql(true) - }) - - it('should be initialized', () => { - expect(node.initialized).to.be.eql(true) - }) - }) - }) - }) - - describe('change config of a disposable node', () => { - let ipfsNode - - before((done) => { - ipfsd.disposable((err, node) => { - if (err) { - return done(err) - } - ipfsNode = node - done() - }) - }) - - it('Should return a config value', (done) => { - ipfsNode.getConfig('Bootstrap', (err, config) => { - expect(err).to.not.exist() - expect(config).to.exist() - done() - }) - }) - - it('Should return the whole config', (done) => { - ipfsNode.getConfig((err, config) => { - expect(err).to.not.exist() - expect(config).to.exist() - done() - }) - }) - - it('Should set a config value', (done) => { - async.series([ - (cb) => ipfsNode.setConfig('Bootstrap', 'null', cb), - (cb) => ipfsNode.getConfig('Bootstrap', cb) - ], (err, res) => { - expect(err).to.not.exist() - expect(res[1]).to.be.eql('null') - done() - }) - }) - - it('should give an error if setting an invalid config value', (done) => { - ipfsNode.setConfig('Bootstrap', 'true', (err) => { - expect(err.message).to.match(/failed to set config value/) - done() - }) - }) - }) - - it('allows passing via $IPFS_EXEC', (done) => { - process.env.IPFS_EXEC = '/some/path' - ipfsd.local((err, node) => { - expect(err).to.not.exist() - expect(node.exec).to.be.eql('/some/path') - - process.env.IPFS_EXEC = '' - done() - }) - }) - - it('prints the version', (done) => { - ipfsd.version((err, version) => { - expect(err).to.not.exist() - expect(version).to.be.eql('js-ipfs version: 0.26.0') - done() - }) - }) - - describe('ipfs-api version', () => { - let ipfs - - before((done) => { - ipfsd.disposable((err, node) => { - expect(err).to.not.exist() - node.startDaemon((err, ignore) => { - expect(err).to.not.exist() - ipfs = ipfsApi(node.apiAddr) - done() - }) - }) - }) - - // skip on windows for now - // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 - // fails on windows see https://github.com/ipfs/js-ipfs-api/issues/408 - if (isWindows) { - return it.skip('uses the correct ipfs-api') - } - - it('uses the correct ipfs-api', (done) => { - ipfs.util.addFromFs(path.join(__dirname, 'fixtures/'), { - recursive: true - }, (err, res) => { - expect(err).to.not.exist() - - const added = res[res.length - 1] - - // Temporary: Need to see what is going on on windows - expect(res).to.deep.equal([ - { - path: 'fixtures/test.txt', - hash: 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD', - size: 19 - }, - { - path: 'fixtures', - hash: 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU', - size: 73 - } - ]) - - expect(res.length).to.equal(2) - expect(added).to.have.property('path', 'fixtures') - expect(added).to.have.property( - 'hash', - 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU' - ) - expect(res[0]).to.have.property('path', 'fixtures/test.txt') - expect(res[0]).to.have.property( - 'hash', - 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD' - ) - done() - }) - }) - }) - - describe('startDaemon', () => { - it('start and stop', (done) => { - const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` - - const check = (cb) => { - // skip on windows - // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326983530 - if (!isWindows) { - if (fs.existsSync(path.join(dir, 'repo.lock'))) { - cb(new Error('repo.lock not removed')) - } - if (fs.existsSync(path.join(dir, 'api'))) { - cb(new Error('api file not removed')) - } - } - cb() - } - - async.waterfall([ - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.init((err) => cb(err, node)), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check, - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check, - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check - ], done) - }) - - it('starts the daemon and returns valid API and gateway addresses', (done) => { - const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` - - async.waterfall([ - (cb) => ipfsd.local(dir, cb), - (daemon, cb) => daemon.init((err) => cb(err, daemon)), - (daemon, cb) => daemon.startDaemon((err, api) => cb(err, daemon, api)) - ], (err, daemon, api) => { - expect(err).to.not.exist() - - // Check for props in daemon - expect(daemon).to.have.property('apiAddr') - expect(daemon).to.have.property('gatewayAddr') - expect(daemon.apiAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(daemon.apiAddr)).to.equal(true) - expect(daemon.gatewayAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(daemon.gatewayAddr)).to.equal(true) - - // Check for props in ipfs-api instance - expect(api).to.have.property('apiHost') - expect(api).to.have.property('apiPort') - expect(api).to.have.property('gatewayHost') - expect(api).to.have.property('gatewayPort') - expect(api.apiHost).to.equal('127.0.0.1') - expect(api.apiPort).to.equal('5001') - expect(api.gatewayHost).to.equal('127.0.0.1') - expect(api.gatewayPort).to.equal('8080') - - daemon.stopDaemon(done) - }) - }) - - it('allows passing flags', (done) => { - ipfsd.disposable((err, node) => { - expect(err).to.not.exist() - - node.startDaemon(['--should-not-exist'], (err) => { - expect(err).to.exist() - expect(err.message) - .to.match(/Unrecognized option 'should-not-exist'/) - - done() - }) - }) - }) - }) -}) From 8a306c8b657068880caa54a1a0678eaf6815738a Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Mon, 13 Nov 2017 03:36:43 -0800 Subject: [PATCH 03/85] feat: clean up and cosolidate tests --- package.json | 2 +- src/daemon.js | 4 +- test/daemon.spec.js | 14 + test/npm-installs.spec.js | 7 +- test/spawning-daemons.js | 478 ++++++++++++++++++++++++++++++++++ test/spawning-daemons.spec.js | 469 --------------------------------- 6 files changed, 500 insertions(+), 474 deletions(-) create mode 100644 test/daemon.spec.js create mode 100644 test/spawning-daemons.js delete mode 100644 test/spawning-daemons.spec.js diff --git a/package.json b/package.json index 9b076899..fc9a0a21 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "lint": "aegir lint", "coverage": "aegir coverage", - "test": "aegir test --target node", + "test": "aegir test -t node --timeout 50000", "docs": "aegir docs", "release": "aegir release -t node", "release-minor": "aegir release --type minor -t node", diff --git a/src/daemon.js b/src/daemon.js index 329b0bfa..7031d152 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -10,6 +10,8 @@ const path = require('path') const join = path.join const once = require('once') const os = require('os') +const truthy = require('truthy') + const isWindows = os.platform() === 'win32' const exec = require('./exec') @@ -91,7 +93,7 @@ class Node { */ constructor (path, opts, disposable) { const rootPath = process.env.testpath ? process.env.testpath : __dirname - const isJS = process.env.IPFS_JS && process.env.IPFS_JS === 'true' // TODO: handle proper truthy/falsy + const isJS = truthy(process.env.IPFS_JS) this.opts = opts || { isJs: isJS || false } process.env.IPFS_JS = this.opts.isJs diff --git a/test/daemon.spec.js b/test/daemon.spec.js new file mode 100644 index 00000000..ec236046 --- /dev/null +++ b/test/daemon.spec.js @@ -0,0 +1,14 @@ +/* eslint-env mocha */ +'use strict' + +const daemon = require('./spawning-daemons.js') + +describe('ipfsd-ctl', () => { + describe('Go daemon', () => { + daemon(false)() + }) + + describe('Js daemon', () => { + daemon(true)() + }) +}) diff --git a/test/npm-installs.spec.js b/test/npm-installs.spec.js index c7c2b183..b9c353b6 100644 --- a/test/npm-installs.spec.js +++ b/test/npm-installs.spec.js @@ -17,8 +17,11 @@ describe('ipfs executable path', () => { const tmp = os.tmpdir() const appName = isWindows ? 'ipfs.exe' : 'ipfs' + const oldPath = process.env.testpath + before(() => { process.env.testpath = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/lib') }) // fake __dirname + after(() => { process.env.testpath = oldPath }) + it('has the correct path when installed with npm3', (done) => { - process.env.testpath = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/lib') // fake __dirname let npm3Path = path.join(tmp, 'ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs') mkdirp(npm3Path, (err) => { @@ -36,8 +39,6 @@ describe('ipfs executable path', () => { }) it('has the correct path when installed with npm2', (done) => { - process.env.testpath = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/lib') // fake __dirname - let npm2Path = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs') mkdirp(npm2Path, (err) => { diff --git a/test/spawning-daemons.js b/test/spawning-daemons.js new file mode 100644 index 00000000..4c007dc2 --- /dev/null +++ b/test/spawning-daemons.js @@ -0,0 +1,478 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ +'use strict' + +const async = require('async') +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) +const ipfsApi = require('ipfs-api') +const multiaddr = require('multiaddr') +const fs = require('fs') +const rimraf = require('rimraf') +const path = require('path') +const once = require('once') +const os = require('os') + +const exec = require('../src/exec') +const ipfsd = require('../src') + +const isWindows = os.platform() === 'win32' + +module.exports = () => { + return (isJs) => { + const VERSION_STRING = isJs ? 'js-ipfs version: 0.26.0' : 'ipfs version 0.4.11' + + const API_PORT = isJs ? '5002' : '5001' + const GW_PORT = isJs ? '9090' : '8080' + + describe('daemon spawning', () => { + describe('local daemon', () => { + const repoPath = '/tmp/ipfsd-ctl-test' + const addr = '/ip4/127.0.0.1/tcp/5678' + const config = { + Addresses: { + API: addr + } + } + + it('allows passing flags to init', (done) => { + async.waterfall([ + (cb) => ipfsd.local(repoPath, config, cb), + (node, cb) => { + async.series([ + (cb) => node.init(cb), + (cb) => node.getConfig('Addresses.API', cb) + ], (err, res) => { + expect(err).to.not.exist() + expect(res[1]).to.be.eql(addr) + rimraf(repoPath, cb) + }) + } + ], done) + }) + }) + + describe('disposable daemon', () => { + const blorb = Buffer.from('blorb') + let ipfs + let store + let retrieve + + beforeEach((done) => { + async.waterfall([ + (cb) => ipfs.block.put(blorb, cb), + (block, cb) => { + store = block.cid.toBaseEncodedString() + ipfs.block.get(store, cb) + }, + (_block, cb) => { + retrieve = _block.data + cb() + } + ], done) + }) + + describe('without api instance (.disposable)', () => { + before((done) => { + async.waterfall([ + (cb) => ipfsd.disposable(cb), + (node, cb) => { + node.startDaemon((err) => { + expect(err).to.not.exist() + ipfs = ipfsApi(node.apiAddr) + cb() + }) + } + ], done) + }) + + it('should have started the daemon and returned an api', () => { + expect(ipfs).to.exist() + expect(ipfs.id).to.exist() + }) + + it('should be able to store objects', () => { + expect(store) + .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.be.eql('blorb') + }) + }) + + describe('with api instance (.disposableApi)', () => { + before((done) => { + ipfsd.disposableApi((err, api) => { + expect(err).to.not.exist() + + ipfs = api + done() + }) + }) + + it('should have started the daemon and returned an api with host/port', () => { + expect(ipfs).to.have.property('id') + expect(ipfs).to.have.property('apiHost') + expect(ipfs).to.have.property('apiPort') + }) + + it('should be able to store objects', () => { + expect(store) + .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.equal('blorb') + }) + }) + }) + + describe('starting and stopping', () => { + let node + + describe('init', () => { + before((done) => { + ipfsd.disposable((err, res) => { + if (err) { + done(err) + } + node = res + done() + }) + }) + + it('should returned a node', () => { + expect(node).to.exist() + }) + + it('daemon should not be running', () => { + expect(node.daemonPid()).to.not.exist() + }) + }) + + let pid + + describe('starting', () => { + let ipfs + + before((done) => { + node.startDaemon((err, res) => { + expect(err).to.not.exist() + + pid = node.daemonPid() + ipfs = res + + // actually running? + done = once(done) + exec('kill', ['-0', pid], { cleanup: true }, () => done()) + }) + }) + + it('should be running', () => { + expect(ipfs.id).to.exist() + }) + }) + + describe('stopping', () => { + let stopped = false + + before((done) => { + node.stopDaemon((err) => { + expect(err).to.not.exist() + stopped = true + }) + + // make sure it's not still running + const poll = setInterval(() => { + exec('kill', ['-0', pid], { cleanup: true }, { + error () { + clearInterval(poll) + done() + // so it does not get called again + done = () => {} + } + }) + }, 100) + }) + + it('should be stopped', () => { + expect(node.daemonPid()).to.not.exist() + expect(stopped).to.equal(true) + }) + }) + }) + + describe('setting up and init a local node', () => { + const testpath1 = '/tmp/ipfstestpath1' + + describe('cleanup', () => { + before((done) => { + rimraf(testpath1, done) + }) + + it('should not have a directory', () => { + expect(fs.existsSync('/tmp/ipfstestpath1')).to.be.eql(false) + }) + }) + + describe('setup', () => { + let node + before((done) => { + ipfsd.local(testpath1, (err, res) => { + if (err) { + return done(err) + } + node = res + done() + }) + }) + + it('should have returned a node', () => { + expect(node).to.exist() + }) + + it('should not be initialized', () => { + expect(node.initialized).to.be.eql(false) + }) + + describe('initialize', () => { + before((done) => { + node.init(done) + }) + + it('should have made a directory', () => { + expect(fs.existsSync(testpath1)).to.be.eql(true) + }) + + it('should be initialized', () => { + expect(node.initialized).to.be.eql(true) + }) + + it('should be initialized', () => { + expect(node.initialized).to.be.eql(true) + }) + }) + }) + }) + + describe('change config of a disposable node', () => { + let ipfsNode + + before((done) => { + ipfsd.disposable((err, node) => { + if (err) { + return done(err) + } + ipfsNode = node + done() + }) + }) + + it('Should return a config value', (done) => { + ipfsNode.getConfig('Bootstrap', (err, config) => { + expect(err).to.not.exist() + expect(config).to.exist() + done() + }) + }) + + it('Should return the whole config', (done) => { + ipfsNode.getConfig((err, config) => { + expect(err).to.not.exist() + expect(config).to.exist() + done() + }) + }) + + it('Should set a config value', (done) => { + async.series([ + (cb) => ipfsNode.setConfig('Bootstrap', 'null', cb), + (cb) => ipfsNode.getConfig('Bootstrap', cb) + ], (err, res) => { + expect(err).to.not.exist() + expect(res[1]).to.be.eql('null') + done() + }) + }) + + it('should give an error if setting an invalid config value', function (done) { + if (isJs) { + this.skip() + } else { + ipfsNode.setConfig('Bootstrap', 'true', (err) => { + expect(err.message).to.match(/failed to set config value/) + done() + }) + } + }) + }) + + it('allows passing via $IPFS_EXEC', (done) => { + process.env.IPFS_EXEC = '/some/path' + ipfsd.local((err, node) => { + expect(err).to.not.exist() + expect(node.exec).to.be.eql('/some/path') + + process.env.IPFS_EXEC = '' + done() + }) + }) + + it('prints the version', (done) => { + ipfsd.version((err, version) => { + expect(err).to.not.exist() + expect(version).to.be.eql(VERSION_STRING) + done() + }) + }) + + describe('ipfs-api version', () => { + let ipfs + + before((done) => { + ipfsd.disposable((err, node) => { + expect(err).to.not.exist() + node.startDaemon((err, ignore) => { + expect(err).to.not.exist() + ipfs = ipfsApi(node.apiAddr) + done() + }) + }) + }) + + // skip on windows for now + // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 + // fails on windows see https://github.com/ipfs/js-ipfs-api/issues/408 + if (isWindows) { + return it.skip('uses the correct ipfs-api') + } + + it('uses the correct ipfs-api', (done) => { + ipfs.util.addFromFs(path.join(__dirname, 'fixtures/'), { + recursive: true + }, (err, res) => { + expect(err).to.not.exist() + + const added = res[res.length - 1] + + // Temporary: Need to see what is going on on windows + expect(res).to.deep.equal([ + { + path: 'fixtures/test.txt', + hash: 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD', + size: 19 + }, + { + path: 'fixtures', + hash: 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU', + size: 73 + } + ]) + + expect(res.length).to.equal(2) + expect(added).to.have.property('path', 'fixtures') + expect(added).to.have.property( + 'hash', + 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU' + ) + expect(res[0]).to.have.property('path', 'fixtures/test.txt') + expect(res[0]).to.have.property( + 'hash', + 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD' + ) + done() + }) + }) + }) + + describe('startDaemon', () => { + it('start and stop', (done) => { + const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` + + const check = (cb) => { + // skip on windows + // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326983530 + if (!isWindows) { + if (fs.existsSync(path.join(dir, 'repo.lock'))) { + cb(new Error('repo.lock not removed')) + } + if (fs.existsSync(path.join(dir, 'api'))) { + cb(new Error('api file not removed')) + } + } + cb() + } + + async.waterfall([ + (cb) => ipfsd.local(dir, cb), + (node, cb) => node.init((err) => cb(err, node)), + (node, cb) => node.startDaemon((err) => cb(err, node)), + (node, cb) => node.stopDaemon(cb), + check, + (cb) => ipfsd.local(dir, cb), + (node, cb) => node.startDaemon((err) => cb(err, node)), + (node, cb) => node.stopDaemon(cb), + check, + (cb) => ipfsd.local(dir, cb), + (node, cb) => node.startDaemon((err) => cb(err, node)), + (node, cb) => node.stopDaemon(cb), + check + ], done) + }) + + it('starts the daemon and returns valid API and gateway addresses', (done) => { + const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` + + async.waterfall([ + (cb) => ipfsd.local(dir, cb), + (daemon, cb) => daemon.init((err) => cb(err, daemon)), + (daemon, cb) => daemon.startDaemon((err, api) => cb(err, daemon, api)) + ], (err, daemon, api) => { + expect(err).to.not.exist() + + // Check for props in daemon + expect(daemon).to.have.property('apiAddr') + expect(daemon).to.have.property('gatewayAddr') + expect(daemon.apiAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(daemon.apiAddr)).to.equal(true) + expect(daemon.gatewayAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(daemon.gatewayAddr)).to.equal(true) + + // Check for props in ipfs-api instance + expect(api).to.have.property('apiHost') + expect(api).to.have.property('apiPort') + expect(api).to.have.property('gatewayHost') + expect(api).to.have.property('gatewayPort') + expect(api.apiHost).to.equal('127.0.0.1') + console.log(API_PORT) + expect(api.apiPort).to.equal(API_PORT) + expect(api.gatewayHost).to.equal('127.0.0.1') + expect(api.gatewayPort).to.equal(GW_PORT) + + daemon.stopDaemon(done) + }) + }) + + it('allows passing flags', function (done) { + if (isJs) { + this.skip() + } else { + ipfsd.disposable((err, node) => { + expect(err).to.not.exist() + node.startDaemon(['--should-not-exist'], (err) => { + expect(err).to.exist() + expect(err.message) + .to.match(/Unrecognized option 'should-not-exist'/) + + done() + }) + }) + } + }) + }) + }) + } +} diff --git a/test/spawning-daemons.spec.js b/test/spawning-daemons.spec.js deleted file mode 100644 index 5a67d2c0..00000000 --- a/test/spawning-daemons.spec.js +++ /dev/null @@ -1,469 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ -'use strict' - -const async = require('async') -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const ipfsApi = require('ipfs-api') -const multiaddr = require('multiaddr') -const fs = require('fs') -const rimraf = require('rimraf') -const path = require('path') -const once = require('once') -const os = require('os') - -const exec = require('../src/exec') -const ipfsd = require('../src') - -const VERSION_STRING = process.env.IPFS_JS ? 'js-ipfs version: 0.26.0' : 'ipfs version 0.4.11' -const API_PORT = process.env.IPFS_JS ? '5002' : '5001' -const GW_PORT = process.env.IPFS_JS ? '9090' : '8080' - -const isWindows = os.platform() === 'win32' -describe('daemon spawning', function () { - this.timeout(60 * 1000) - describe('local daemon', () => { - const repoPath = path.join(os.tmpdir(), 'ipfsd-ctl-test') - const addr = '/ip4/127.0.0.1/tcp/5678' - const config = { - Addresses: { - API: addr - } - } - - it('allows passing flags to init', (done) => { - async.waterfall([ - (cb) => ipfsd.local(repoPath, config, cb), - (node, cb) => { - async.series([ - (cb) => node.init(cb), - (cb) => node.getConfig('Addresses.API', cb) - ], (err, res) => { - expect(err).to.not.exist() - expect(res[1]).to.be.eql(addr) - rimraf(repoPath, cb) - }) - } - ], done) - }) - }) - - describe('disposable daemon', () => { - const blorb = Buffer.from('blorb') - let ipfs - let store - let retrieve - - beforeEach((done) => { - async.waterfall([ - (cb) => ipfs.block.put(blorb, cb), - (block, cb) => { - store = block.cid.toBaseEncodedString() - ipfs.block.get(store, cb) - }, - (_block, cb) => { - retrieve = _block.data - cb() - } - ], done) - }) - - describe('without api instance (.disposable)', () => { - before((done) => { - async.waterfall([ - (cb) => ipfsd.disposable(cb), - (node, cb) => { - node.startDaemon((err) => { - expect(err).to.not.exist() - ipfs = ipfsApi(node.apiAddr) - cb() - }) - } - ], done) - }) - - it('should have started the daemon and returned an api', () => { - expect(ipfs).to.exist() - expect(ipfs.id).to.exist() - }) - - it('should be able to store objects', () => { - expect(store) - .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.be.eql('blorb') - }) - }) - - describe('with api instance (.disposableApi)', () => { - before((done) => { - ipfsd.disposableApi((err, api) => { - expect(err).to.not.exist() - - ipfs = api - done() - }) - }) - - it('should have started the daemon and returned an api with host/port', () => { - expect(ipfs).to.have.property('id') - expect(ipfs).to.have.property('apiHost') - expect(ipfs).to.have.property('apiPort') - }) - - it('should be able to store objects', () => { - expect(store) - .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.equal('blorb') - }) - }) - }) - - describe('starting and stopping', () => { - let node - - describe('init', () => { - before((done) => { - ipfsd.disposable((err, res) => { - if (err) { - done(err) - } - node = res - done() - }) - }) - - it('should returned a node', () => { - expect(node).to.exist() - }) - - it('daemon should not be running', () => { - expect(node.daemonPid()).to.not.exist() - }) - }) - - let pid - - describe('starting', () => { - let ipfs - - before((done) => { - node.startDaemon((err, res) => { - expect(err).to.not.exist() - - pid = node.daemonPid() - ipfs = res - - // actually running? - done = once(done) - exec('kill', ['-0', pid], { cleanup: true }, () => done()) - }) - }) - - it('should be running', () => { - expect(ipfs.id).to.exist() - }) - }) - - describe('stopping', () => { - let stopped = false - - before((done) => { - node.stopDaemon((err) => { - expect(err).to.not.exist() - stopped = true - }) - - // make sure it's not still running - const poll = setInterval(() => { - exec('kill', ['-0', pid], { cleanup: true }, { - error () { - clearInterval(poll) - done() - // so it does not get called again - done = () => {} - } - }) - }, 100) - }) - - it('should be stopped', () => { - expect(node.daemonPid()).to.not.exist() - expect(stopped).to.equal(true) - }) - }) - }) - - describe('setting up and init a local node', () => { - const testpath1 = path.join(os.tmpdir(), 'ipfstestpath1') - - describe('cleanup', () => { - before((done) => { - rimraf(testpath1, done) - }) - - it('should not have a directory', () => { - expect(fs.existsSync(testpath1)).to.be.eql(false) - }) - }) - - describe('setup', () => { - let node - before((done) => { - ipfsd.local(testpath1, (err, res) => { - if (err) { - return done(err) - } - node = res - done() - }) - }) - - it('should have returned a node', () => { - expect(node).to.exist() - }) - - it('should not be initialized', () => { - expect(node.initialized).to.be.eql(false) - }) - - describe('initialize', () => { - before((done) => { - node.init(done) - }) - - it('should have made a directory', () => { - expect(fs.existsSync(testpath1)).to.be.eql(true) - }) - - it('should be initialized', () => { - expect(node.initialized).to.be.eql(true) - }) - - it('should be initialized', () => { - expect(node.initialized).to.be.eql(true) - }) - }) - }) - }) - - describe('change config of a disposable node', () => { - let ipfsNode - - before((done) => { - ipfsd.disposable((err, node) => { - if (err) { - return done(err) - } - ipfsNode = node - done() - }) - }) - - it('Should return a config value', (done) => { - ipfsNode.getConfig('Bootstrap', (err, config) => { - expect(err).to.not.exist() - expect(config).to.exist() - done() - }) - }) - - it('Should return the whole config', (done) => { - ipfsNode.getConfig((err, config) => { - expect(err).to.not.exist() - expect(config).to.exist() - done() - }) - }) - - it('Should set a config value', (done) => { - async.series([ - (cb) => ipfsNode.setConfig('Bootstrap', 'null', cb), - (cb) => ipfsNode.getConfig('Bootstrap', cb) - ], (err, res) => { - expect(err).to.not.exist() - expect(res[1]).to.be.eql('null') - done() - }) - }) - - it('should give an error if setting an invalid config value', function (done) { - if (!process.env.IPFS_JS) { // TODO: handle proper truthy/falsy - ipfsNode.setConfig('Bootstrap', 'true', (err) => { - expect(err.message).to.match(/failed to set config value/) - done() - }) - } else { - this.skip() - } - }) - }) - - it('allows passing via $IPFS_EXEC', (done) => { - process.env.IPFS_EXEC = '/some/path' - ipfsd.local((err, node) => { - expect(err).to.not.exist() - expect(node.exec).to.be.eql('/some/path') - - process.env.IPFS_EXEC = '' - done() - }) - }) - - it('prints the version', (done) => { - ipfsd.version((err, version) => { - expect(err).to.not.exist() - expect(version).to.be.eql(VERSION_STRING) - done() - }) - }) - - describe('ipfs-api version', () => { - let ipfs - - before((done) => { - ipfsd.disposable((err, node) => { - expect(err).to.not.exist() - node.startDaemon((err, ignore) => { - expect(err).to.not.exist() - ipfs = ipfsApi(node.apiAddr) - done() - }) - }) - }) - - it('uses the correct ipfs-api', (done) => { - ipfs.util.addFromFs(path.join(__dirname, 'fixtures/'), { - recursive: true - }, (err, res) => { - expect(err).to.not.exist() - - const added = res[res.length - 1] - - // Temporary: Need to see what is going on on windows - expect(res).to.deep.equal([ - { - path: 'fixtures/test.txt', - hash: 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD', - size: 19 - }, - { - path: 'fixtures', - hash: 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU', - size: 73 - } - ]) - - expect(res.length).to.equal(2) - expect(added).to.have.property('path', 'fixtures') - expect(added).to.have.property( - 'hash', - 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU' - ) - expect(res[0]).to.have.property('path', 'fixtures/test.txt') - expect(res[0]).to.have.property( - 'hash', - 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD' - ) - done() - }) - }) - }) - - describe('startDaemon', () => { - it('start and stop', function (done) { - this.timeout(20000) - const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` - - const check = (cb) => { - // skip on windows - // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326983530 - if (!isWindows) { - if (fs.existsSync(path.join(dir, 'repo.lock'))) { - cb(new Error('repo.lock not removed')) - } - if (fs.existsSync(path.join(dir, 'api'))) { - cb(new Error('api file not removed')) - } - } - cb() - } - - async.waterfall([ - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.init((err) => cb(err, node)), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check, - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check, - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check - ], done) - }) - - it('starts the daemon and returns valid API and gateway addresses', function (done) { - this.timeout(20000) - - const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` - - async.waterfall([ - (cb) => ipfsd.local(dir, cb), - (daemon, cb) => daemon.init((err) => cb(err, daemon)), - (daemon, cb) => daemon.startDaemon((err, api) => cb(err, daemon, api)) - ], (err, daemon, api) => { - expect(err).to.not.exist() - - // Check for props in daemon - expect(daemon).to.have.property('apiAddr') - expect(daemon).to.have.property('gatewayAddr') - expect(daemon.apiAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(daemon.apiAddr)).to.equal(true) - expect(daemon.gatewayAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(daemon.gatewayAddr)).to.equal(true) - - // Check for props in ipfs-api instance - expect(api).to.have.property('apiHost') - expect(api).to.have.property('apiPort') - expect(api).to.have.property('gatewayHost') - expect(api).to.have.property('gatewayPort') - expect(api.apiHost).to.equal('127.0.0.1') - console.log(API_PORT) - expect(api.apiPort).to.equal(API_PORT) - expect(api.gatewayHost).to.equal('127.0.0.1') - expect(api.gatewayPort).to.equal(GW_PORT) - - daemon.stopDaemon(done) - }) - }) - - it('allows passing flags', function (done) { - if (!process.env.IPFS_JS) { - ipfsd.disposable((err, node) => { - expect(err).to.not.exist() - node.startDaemon(['--should-not-exist'], (err) => { - expect(err).to.exist() - expect(err.message) - .to.match(/Unrecognized option 'should-not-exist'/) - - done() - }) - }) - } else { - this.skip() - } - }) - }) -}) From e5103ee2127cd061b0b1ecfe5c756fd979128218 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 22 Nov 2017 15:40:12 -0600 Subject: [PATCH 04/85] feat: add a factory to start/stop nodes from node and browser --- .aegir.js | 12 ++++ .gitignore | 2 + src/factory/index.js | 21 +++++++ src/factory/local-factory/index.js | 39 +++++++++++++ src/factory/remote-factory/client.js | 68 +++++++++++++++++++++++ src/factory/remote-factory/index.js | 49 +++++++++++++++++ src/factory/remote-factory/routes.js | 82 ++++++++++++++++++++++++++++ src/factory/remote-factory/server.js | 31 +++++++++++ src/factory/remote-factory/tasks.js | 19 +++++++ src/factory/remote-factory/utils.js | 21 +++++++ test/factory.spec.js | 49 +++++++++++++++++ test/spawning-daemons.js | 5 +- 12 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 .aegir.js create mode 100644 src/factory/index.js create mode 100644 src/factory/local-factory/index.js create mode 100644 src/factory/remote-factory/client.js create mode 100644 src/factory/remote-factory/index.js create mode 100644 src/factory/remote-factory/routes.js create mode 100644 src/factory/remote-factory/server.js create mode 100644 src/factory/remote-factory/tasks.js create mode 100644 src/factory/remote-factory/utils.js create mode 100644 test/factory.spec.js diff --git a/.aegir.js b/.aegir.js new file mode 100644 index 00000000..4e3f0f91 --- /dev/null +++ b/.aegir.js @@ -0,0 +1,12 @@ +'use strict' + +const tasks = require('./src/remote-factory/tasks') + +module.exports = { + hooks: { + browser: { + pre: tasks.start, + post: tasks.stop + } + } +} diff --git a/.gitignore b/.gitignore index 4cdb1c97..011edeae 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ node_modules dist docs + +.idea diff --git a/src/factory/index.js b/src/factory/index.js new file mode 100644 index 00000000..a8dfd053 --- /dev/null +++ b/src/factory/index.js @@ -0,0 +1,21 @@ +'use strict' + +const isNode = require('detect-node') +const Local = require('./local-factory') +const Remote = require('./remote-factory') + +class Factory { + constructor (opts) { + this.factory = isNode ? new Local() : new Remote(opts) + } + + spawnNode (repoPath, opts, callback) { + this.factory.spawnNode(repoPath, opts, callback) + } + + dismantle (callback) { + this.factory.dismantle(callback) + } +} + +module.exports = Factory diff --git a/src/factory/local-factory/index.js b/src/factory/local-factory/index.js new file mode 100644 index 00000000..8257f1df --- /dev/null +++ b/src/factory/local-factory/index.js @@ -0,0 +1,39 @@ +'use strict' + +const IpfsdCtl = require('../..') +const each = require('async/each') +const waterfall = require('async/waterfall') + +class Factory { + constructor () { + this.nodes = [] + } + + /* yields a new started node */ + spawnNode (repoPath, opts, callback) { + if (typeof repoPath === 'function') { + callback = repoPath + repoPath = null + } + if (typeof opts === 'function') { + callback = opts + opts = {} + } + + opts = Object.assign({}, opts, { repoPath }) + + waterfall([ + (cb) => IpfsdCtl.disposable(opts, cb), + (node, cb) => { + this.nodes.push(node) + node.startDaemon(['--enable-pubsub-experiment'], cb) + } + ], callback) + } + + dismantle (callback) { + each(this.nodes, (node, cb) => node.stopDaemon(cb), callback) + } +} + +module.exports = Factory diff --git a/src/factory/remote-factory/client.js b/src/factory/remote-factory/client.js new file mode 100644 index 00000000..84772fce --- /dev/null +++ b/src/factory/remote-factory/client.js @@ -0,0 +1,68 @@ +'use strict' + +const io = require('socket.io-client') +const IpfsApi = require('ipfs-api') + +const { toPayload, toRes } = require('./utils') + +class Client { + constructor (sio) { + this._sio = sio + } + + _request () { + const action = Array.from(arguments).shift() + const args = Array.from(arguments).splice(1, arguments.length - 2) + const cb = Array.from(arguments).splice(arguments.length - 1, 1).shift() + this._sio.on(action, (data) => toRes(data, cb)) + this._sio.emit(action, toPayload(args)) + } + + start (opts, cb) { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + this._request('start', opts, (err, api) => { + if (err) { + return cb(err) + } + + cb(null, new IpfsApi(api.apiAddr)) + }) + } + + stop (nodes, cb) { + if (typeof nodes === 'function') { + cb = nodes + nodes = undefined + } + + cb = cb || (() => {}) + this._request('stop', nodes, cb) + } +} + +function createClient (options, callback) { + if (typeof options === 'function') { + callback = options + options = {} + } + + callback = callback || (() => {}) + options = options || {} + const url = options.url ? (delete options.url) : 'http://localhost:55155' + options = Object.assign({}, options, { + transports: ['websocket'], + 'force new connection': true + }) + + const sio = io.connect(url, options) + sio.once('connect_error', (err) => { throw err }) + sio.once('connect', () => { + callback(null, new Client(sio)) + }) +} + +module.exports = createClient diff --git a/src/factory/remote-factory/index.js b/src/factory/remote-factory/index.js new file mode 100644 index 00000000..4bab0a1a --- /dev/null +++ b/src/factory/remote-factory/index.js @@ -0,0 +1,49 @@ +'use strict' + +const createClient = require('./client') + +class Factory { + constructor (options) { + this._options = options || {} + this._client = null + } + + _spawn (repoPath, opts, callback) { + if (typeof repoPath === 'function') { + callback = repoPath + repoPath = null + } + if (typeof opts === 'function') { + callback = opts + opts = {} + } + + opts = Object.assign({}, opts, { repoPath }) + this._client.start(opts, callback) + } + + spawnNode (repoPath, opts, callback) { + if (!this._client) { + return createClient(this._options, (err, cl) => { + if (err) { + return callback(err) + } + + this._client = cl + return this._spawn(repoPath, opts, callback) + }) + } + + return this._spawn(repoPath, opts, callback) + } + + dismantle (callback) { + if (!this._client) { + return callback() + } + + this._client.stop(callback) + } +} + +module.exports = Factory diff --git a/src/factory/remote-factory/routes.js b/src/factory/remote-factory/routes.js new file mode 100644 index 00000000..544aa320 --- /dev/null +++ b/src/factory/remote-factory/routes.js @@ -0,0 +1,82 @@ +'use strict' + +const IpfsdCtl = require('..') +const SocketIO = require('socket.io') +const each = require('async/each') +const waterfall = require('async/waterfall') +const eachSeries = require('async/eachSeries') +const series = require('async/series') +const parsePayload = require('./utils').parsePayload + +const nodes = new Map() + +function start (opts, callback) { + if (typeof opts === 'function') { + callback = opts + opts = null + } + + let node = null + waterfall([ + (cb) => IpfsdCtl.disposable(opts, cb), + (n, cb) => { + node = n + series([ + (pCb) => { + const configValues = { + Bootstrap: [], + Discovery: {}, + 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], + 'API.HTTPHeaders.Access-Control-Allow-Methods': [ + 'PUT', + 'POST', + 'GET' + ] + } + eachSeries(Object.keys(configValues), (configKey, cb) => { + const configVal = JSON.stringify(configValues[configKey]) + node.setConfig(configKey, configVal, cb) + }, pCb) + }, + (pCb) => node.startDaemon(['--enable-pubsub-experiment'], cb) + ], cb) + }, + (api, cb) => api.id(cb), + (id, cb) => cb(null, nodes.set(id.id, node)) + ], (err) => { + callback(err, { + apiAddr: node.apiAddr.toString() + }) + }) +} + +function stop (node, callback) { + if (typeof node === 'function') { + callback = node + node = undefined + } + + if (node) { + return nodes.get(node).stopDaemon(callback) + } + + each(nodes, (node, cb) => node[1].stopDaemon(cb), callback) +} + +module.exports = (http) => { + const io = new SocketIO(http.listener) + io.on('connection', handle) + + function handle (socket) { + const response = (action) => (err, data) => { + if (err) { + return socket.emit(action, JSON.stringify({ err })) + } + + socket.emit(action, JSON.stringify({ data })) + } + + socket.on('start', (data) => start.apply(null, parsePayload(data).concat(response('start')))) + socket.on('stop', (data) => stop.apply(null, parsePayload(data).concat(response('stop')))) + } +} diff --git a/src/factory/remote-factory/server.js b/src/factory/remote-factory/server.js new file mode 100644 index 00000000..e3a14a8c --- /dev/null +++ b/src/factory/remote-factory/server.js @@ -0,0 +1,31 @@ +'use strict' + +const Hapi = require('hapi') +const routes = require('./routes') + +const port = Number(process.env.PORT) || 55155 +const options = { + connections: { + routes: { + cors: true + } + } +} + +function server (callback) { + const http = new Hapi.Server(options) + + http.connection({ port: port }) + + http.start((err) => { + if (err) { + return callback(err) + } + + routes(http) + + callback(null, http) + }) +} + +module.exports = server diff --git a/src/factory/remote-factory/tasks.js b/src/factory/remote-factory/tasks.js new file mode 100644 index 00000000..fd8402da --- /dev/null +++ b/src/factory/remote-factory/tasks.js @@ -0,0 +1,19 @@ +'use strict' + +const factoryServer = require('./server') + +let factory +module.exports = { + start (done) { + factoryServer((err, http) => { + if (err) { + return done(err) + } + factory = http + done() + }) + }, + stop (done) { + factory.stop(done) + } +} diff --git a/src/factory/remote-factory/utils.js b/src/factory/remote-factory/utils.js new file mode 100644 index 00000000..ab9bf51e --- /dev/null +++ b/src/factory/remote-factory/utils.js @@ -0,0 +1,21 @@ +'use strict' + +exports.toPayload = (args) => JSON.stringify({ args }) + +exports.toRes = (payload, cb) => { + payload = JSON.parse(payload) + if (payload.err) { + return cb(payload.err) + } + + return cb(null, payload.data) +} + +exports.parsePayload = (data) => { + const args = JSON.parse(data).args + if (!Array.isArray(args)) { + throw new Error('args field should be an array') + } + + return args +} diff --git a/test/factory.spec.js b/test/factory.spec.js new file mode 100644 index 00000000..e8898836 --- /dev/null +++ b/test/factory.spec.js @@ -0,0 +1,49 @@ +/* eslint-env mocha */ + +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const Factory = require('../src/factory') + +const factory = new Factory() +describe('Factory', () => { + let node = null + let hash = null + + it('should start node', function (done) { + factory.spawnNode((err, n) => { + expect(err).to.not.exist() + node = n + done() + }) + }) + + it('should add to running node', (done) => { + node.files.add(Buffer.from('Hello!'), (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + hash = res[0].hash + done() + }) + }) + + it('should cat from running node', (done) => { + node.files.cat(hash, (err, buffer) => { + expect(err).to.not.exist() + expect(buffer).to.exist() + expect(buffer.toString()).to.equal('Hello!') + done() + }) + }) + + it('should stop running nodes', function (done) { + factory.dismantle((err) => { + expect(err).to.not.exist() + done() + }) + }) +}) diff --git a/test/spawning-daemons.js b/test/spawning-daemons.js index 4c007dc2..15bcba00 100644 --- a/test/spawning-daemons.js +++ b/test/spawning-daemons.js @@ -250,10 +250,6 @@ module.exports = () => { it('should be initialized', () => { expect(node.initialized).to.be.eql(true) }) - - it('should be initialized', () => { - expect(node.initialized).to.be.eql(true) - }) }) }) }) @@ -457,6 +453,7 @@ module.exports = () => { }) it('allows passing flags', function (done) { + // skip in js, since js-ipfs doesn't fail on unrecognized args, it prints the help instead if (isJs) { this.skip() } else { From c75389e5c4fe1da493267d5b366cbe8e3416c060 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 28 Nov 2017 20:34:03 -0600 Subject: [PATCH 05/85] feat: reworking with new interface --- package.json | 2 + src/daemon.js | 73 ++--- src/factory/index.js | 21 -- src/factory/local-factory/index.js | 39 --- src/factory/remote-factory/client.js | 68 ---- src/factory/remote-factory/index.js | 49 --- src/factory/remote-factory/routes.js | 82 ----- src/factory/remote-factory/server.js | 31 -- src/factory/remote-factory/tasks.js | 19 -- src/factory/remote-factory/utils.js | 21 -- src/index.js | 87 +---- test/factory.spec.js | 49 --- test/spawning-daemons.js | 457 +++++++++++++++------------ 13 files changed, 303 insertions(+), 695 deletions(-) delete mode 100644 src/factory/index.js delete mode 100644 src/factory/local-factory/index.js delete mode 100644 src/factory/remote-factory/client.js delete mode 100644 src/factory/remote-factory/index.js delete mode 100644 src/factory/remote-factory/routes.js delete mode 100644 src/factory/remote-factory/server.js delete mode 100644 src/factory/remote-factory/tasks.js delete mode 100644 src/factory/remote-factory/utils.js delete mode 100644 test/factory.spec.js diff --git a/package.json b/package.json index fc9a0a21..63644d98 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,8 @@ "eslint-config-standard-jsx": "^4.0.2", "go-ipfs-dep": "0.4.13", "ipfs-api": "^17.1.3", + "lodash.get": "^4.4.2", + "lodash.merge": "^4.6.0", "multiaddr": "^3.0.1", "once": "^1.4.0", "rimraf": "^2.6.2", diff --git a/src/daemon.js b/src/daemon.js index 7031d152..a1e4134d 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -78,6 +78,10 @@ function parseConfig (path, callback) { ], callback) } +function tempDir (isJs) { + return join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) +} + /** * Controll a go-ipfs node. */ @@ -85,26 +89,24 @@ class Node { /** * Create a new node. * - * @param {string} path * @param {Object} [opts] * @param {Object} [opts.env={}] - Additional environment settings, passed to executing shell. - * @param {boolean} [disposable=false] - Should this be a temporary node. * @returns {Node} */ - constructor (path, opts, disposable) { + constructor (opts) { const rootPath = process.env.testpath ? process.env.testpath : __dirname - const isJS = truthy(process.env.IPFS_JS) + const isJs = truthy(process.env.IPFS_JS) - this.opts = opts || { isJs: isJS || false } + this.opts = opts || { isJs: isJs || false } process.env.IPFS_JS = this.opts.isJs - this.path = path || null - this.exec = process.env.IPFS_EXEC || findIpfsExecutable(this.isJs, rootPath) + this.path = this.opts.disposable ? tempDir(isJs) : (this.opts.repoPath || tempDir(isJs)) + this.disposable = this.opts.disposable + this.exec = process.env.IPFS_EXEC || findIpfsExecutable(this.opts.isJs, rootPath) this.subprocess = null this.initialized = fs.existsSync(path) this.clean = true - this.env = path ? Object.assign({}, process.env, { IPFS_PATH: path }) : process.env - this.disposable = disposable + this.env = this.path ? Object.assign({}, process.env, { IPFS_PATH: this.path }) : process.env this._apiAddr = null this._gatewayAddr = null @@ -162,7 +164,7 @@ class Node { return callback(err) } - configureNode(this, this.opts, (err) => { + configureNode(this, this.opts.config, (err) => { if (err) { return callback(err) } @@ -214,25 +216,12 @@ class Node { callback = once(callback) - // Check if there were explicit options to want or not want. Otherwise, - // assume values will be in the local daemon config - // TODO: This should check the local daemon config - const want = { - gateway: typeof this.opts['Addresses.Gateway'] === 'string' - ? this.opts['Addresses.Gateway'].length > 0 - : true, - api: typeof this.opts['Addresses.API'] === 'string' - ? this.opts['Addresses.API'].length > 0 - : true - } - parseConfig(this.path, (err, conf) => { if (err) { return callback(err) } let output = '' - let returned = false this.subprocess = this._run(args, { env: this.env }, { error: (err) => { @@ -255,31 +244,25 @@ class Node { data: (data) => { output += String(data) - const apiMatch = want.api - ? output.trim().match(/API (?:server|is) listening on[:]? (.*)/) - : true - - const gwMatch = want.gateway - ? output.trim().match(/Gateway (?:.*) listening on[:]?(.*)/) - : true + const apiMatch = output.trim().match(/API (?:server|is) listening on[:]? (.*)/) + const gwMatch = output.trim().match(/Gateway (?:.*) listening on[:]?(.*)/) - if (apiMatch && gwMatch && !returned) { - returned = true - - if (want.api) { - this._apiAddr = multiaddr(apiMatch[1]) - this.api = ipfs(apiMatch[1]) - this.api.apiHost = this.apiAddr.nodeAddress().address - this.api.apiPort = this.apiAddr.nodeAddress().port - } + if (apiMatch && apiMatch.length > 0) { + this._apiAddr = multiaddr(apiMatch[1]) + this.api = ipfs(apiMatch[1]) + this.api.apiHost = this.apiAddr.nodeAddress().address + this.api.apiPort = this.apiAddr.nodeAddress().port + } - if (want.gateway) { - this._gatewayAddr = multiaddr(gwMatch[1]) - this.api.gatewayHost = this.gatewayAddr.nodeAddress().address - this.api.gatewayPort = this.gatewayAddr.nodeAddress().port - } + if (gwMatch && gwMatch.length > 0) { + this._gatewayAddr = multiaddr(gwMatch[1]) + this.api.gatewayHost = this.gatewayAddr.nodeAddress().address + this.api.gatewayPort = this.gatewayAddr.nodeAddress().port + } - callback(null, this.api) + if (output.match(/(?:daemon is running|Daemon is ready)/)) { + // we're good + return callback(null, this.api) } } }) diff --git a/src/factory/index.js b/src/factory/index.js deleted file mode 100644 index a8dfd053..00000000 --- a/src/factory/index.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -const isNode = require('detect-node') -const Local = require('./local-factory') -const Remote = require('./remote-factory') - -class Factory { - constructor (opts) { - this.factory = isNode ? new Local() : new Remote(opts) - } - - spawnNode (repoPath, opts, callback) { - this.factory.spawnNode(repoPath, opts, callback) - } - - dismantle (callback) { - this.factory.dismantle(callback) - } -} - -module.exports = Factory diff --git a/src/factory/local-factory/index.js b/src/factory/local-factory/index.js deleted file mode 100644 index 8257f1df..00000000 --- a/src/factory/local-factory/index.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict' - -const IpfsdCtl = require('../..') -const each = require('async/each') -const waterfall = require('async/waterfall') - -class Factory { - constructor () { - this.nodes = [] - } - - /* yields a new started node */ - spawnNode (repoPath, opts, callback) { - if (typeof repoPath === 'function') { - callback = repoPath - repoPath = null - } - if (typeof opts === 'function') { - callback = opts - opts = {} - } - - opts = Object.assign({}, opts, { repoPath }) - - waterfall([ - (cb) => IpfsdCtl.disposable(opts, cb), - (node, cb) => { - this.nodes.push(node) - node.startDaemon(['--enable-pubsub-experiment'], cb) - } - ], callback) - } - - dismantle (callback) { - each(this.nodes, (node, cb) => node.stopDaemon(cb), callback) - } -} - -module.exports = Factory diff --git a/src/factory/remote-factory/client.js b/src/factory/remote-factory/client.js deleted file mode 100644 index 84772fce..00000000 --- a/src/factory/remote-factory/client.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict' - -const io = require('socket.io-client') -const IpfsApi = require('ipfs-api') - -const { toPayload, toRes } = require('./utils') - -class Client { - constructor (sio) { - this._sio = sio - } - - _request () { - const action = Array.from(arguments).shift() - const args = Array.from(arguments).splice(1, arguments.length - 2) - const cb = Array.from(arguments).splice(arguments.length - 1, 1).shift() - this._sio.on(action, (data) => toRes(data, cb)) - this._sio.emit(action, toPayload(args)) - } - - start (opts, cb) { - if (typeof opts === 'function') { - cb = opts - opts = {} - } - - this._request('start', opts, (err, api) => { - if (err) { - return cb(err) - } - - cb(null, new IpfsApi(api.apiAddr)) - }) - } - - stop (nodes, cb) { - if (typeof nodes === 'function') { - cb = nodes - nodes = undefined - } - - cb = cb || (() => {}) - this._request('stop', nodes, cb) - } -} - -function createClient (options, callback) { - if (typeof options === 'function') { - callback = options - options = {} - } - - callback = callback || (() => {}) - options = options || {} - const url = options.url ? (delete options.url) : 'http://localhost:55155' - options = Object.assign({}, options, { - transports: ['websocket'], - 'force new connection': true - }) - - const sio = io.connect(url, options) - sio.once('connect_error', (err) => { throw err }) - sio.once('connect', () => { - callback(null, new Client(sio)) - }) -} - -module.exports = createClient diff --git a/src/factory/remote-factory/index.js b/src/factory/remote-factory/index.js deleted file mode 100644 index 4bab0a1a..00000000 --- a/src/factory/remote-factory/index.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict' - -const createClient = require('./client') - -class Factory { - constructor (options) { - this._options = options || {} - this._client = null - } - - _spawn (repoPath, opts, callback) { - if (typeof repoPath === 'function') { - callback = repoPath - repoPath = null - } - if (typeof opts === 'function') { - callback = opts - opts = {} - } - - opts = Object.assign({}, opts, { repoPath }) - this._client.start(opts, callback) - } - - spawnNode (repoPath, opts, callback) { - if (!this._client) { - return createClient(this._options, (err, cl) => { - if (err) { - return callback(err) - } - - this._client = cl - return this._spawn(repoPath, opts, callback) - }) - } - - return this._spawn(repoPath, opts, callback) - } - - dismantle (callback) { - if (!this._client) { - return callback() - } - - this._client.stop(callback) - } -} - -module.exports = Factory diff --git a/src/factory/remote-factory/routes.js b/src/factory/remote-factory/routes.js deleted file mode 100644 index 544aa320..00000000 --- a/src/factory/remote-factory/routes.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict' - -const IpfsdCtl = require('..') -const SocketIO = require('socket.io') -const each = require('async/each') -const waterfall = require('async/waterfall') -const eachSeries = require('async/eachSeries') -const series = require('async/series') -const parsePayload = require('./utils').parsePayload - -const nodes = new Map() - -function start (opts, callback) { - if (typeof opts === 'function') { - callback = opts - opts = null - } - - let node = null - waterfall([ - (cb) => IpfsdCtl.disposable(opts, cb), - (n, cb) => { - node = n - series([ - (pCb) => { - const configValues = { - Bootstrap: [], - Discovery: {}, - 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], - 'API.HTTPHeaders.Access-Control-Allow-Methods': [ - 'PUT', - 'POST', - 'GET' - ] - } - eachSeries(Object.keys(configValues), (configKey, cb) => { - const configVal = JSON.stringify(configValues[configKey]) - node.setConfig(configKey, configVal, cb) - }, pCb) - }, - (pCb) => node.startDaemon(['--enable-pubsub-experiment'], cb) - ], cb) - }, - (api, cb) => api.id(cb), - (id, cb) => cb(null, nodes.set(id.id, node)) - ], (err) => { - callback(err, { - apiAddr: node.apiAddr.toString() - }) - }) -} - -function stop (node, callback) { - if (typeof node === 'function') { - callback = node - node = undefined - } - - if (node) { - return nodes.get(node).stopDaemon(callback) - } - - each(nodes, (node, cb) => node[1].stopDaemon(cb), callback) -} - -module.exports = (http) => { - const io = new SocketIO(http.listener) - io.on('connection', handle) - - function handle (socket) { - const response = (action) => (err, data) => { - if (err) { - return socket.emit(action, JSON.stringify({ err })) - } - - socket.emit(action, JSON.stringify({ data })) - } - - socket.on('start', (data) => start.apply(null, parsePayload(data).concat(response('start')))) - socket.on('stop', (data) => stop.apply(null, parsePayload(data).concat(response('stop')))) - } -} diff --git a/src/factory/remote-factory/server.js b/src/factory/remote-factory/server.js deleted file mode 100644 index e3a14a8c..00000000 --- a/src/factory/remote-factory/server.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict' - -const Hapi = require('hapi') -const routes = require('./routes') - -const port = Number(process.env.PORT) || 55155 -const options = { - connections: { - routes: { - cors: true - } - } -} - -function server (callback) { - const http = new Hapi.Server(options) - - http.connection({ port: port }) - - http.start((err) => { - if (err) { - return callback(err) - } - - routes(http) - - callback(null, http) - }) -} - -module.exports = server diff --git a/src/factory/remote-factory/tasks.js b/src/factory/remote-factory/tasks.js deleted file mode 100644 index fd8402da..00000000 --- a/src/factory/remote-factory/tasks.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict' - -const factoryServer = require('./server') - -let factory -module.exports = { - start (done) { - factoryServer((err, http) => { - if (err) { - return done(err) - } - factory = http - done() - }) - }, - stop (done) { - factory.stop(done) - } -} diff --git a/src/factory/remote-factory/utils.js b/src/factory/remote-factory/utils.js deleted file mode 100644 index ab9bf51e..00000000 --- a/src/factory/remote-factory/utils.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -exports.toPayload = (args) => JSON.stringify({ args }) - -exports.toRes = (payload, cb) => { - payload = JSON.parse(payload) - if (payload.err) { - return cb(payload.err) - } - - return cb(null, payload.data) -} - -exports.parsePayload = (data) => { - const args = JSON.parse(data).args - if (!Array.isArray(args)) { - throw new Error('args field should be an array') - } - - return args -} diff --git a/src/index.js b/src/index.js index 38dc0bbe..1a953365 100644 --- a/src/index.js +++ b/src/index.js @@ -1,23 +1,15 @@ 'use strict' -const os = require('os') -const join = require('path').join +const merge = require('lodash.merge') +const waterfall = require('async/waterfall') const Node = require('./daemon') -// Note how defaultOptions are Addresses.Swarm and not Addresses: { Swarm : <> } const defaultOptions = { - 'Addresses.Swarm': ['/ip4/0.0.0.0/tcp/0'], - 'Addresses.Gateway': '', - 'Addresses.API': '/ip4/127.0.0.1/tcp/0', disposable: true, + start: true, init: true } - -function tempDir () { - return join(os.tmpdir(), `ipfs_${String(Math.random()).substr(2)}`) -} - /** * Control go-ipfs nodes directly from JavaScript. * @@ -36,83 +28,36 @@ const IpfsDaemonController = { }, /** - * Create a new local node. - * - * @memberof IpfsDaemonController - * @param {string} [path] - Location of the repo. Defaults to `$IPFS_PATH`, or `$HOME/.ipfs`, or `$USER_PROFILE/.ipfs`. - * @param {Object} [opts={}] - * @param {function(Error, Node)} callback - * @returns {undefined} - */ - local (path, opts, callback) { - if (typeof opts === 'function') { - callback = opts - opts = {} - } - - if (!callback) { - callback = path - path = process.env.IPFS_PATH || - join(process.env.HOME || - process.env.USERPROFILE, '.ipfs') - } - - process.nextTick(() => callback(null, new Node(path, opts))) - }, - - /** - * Create a new disposable node. - * This means the repo is created in a temporary location and cleaned up on process exit. + * Spawn an IPFS node + * The repo is created in a temporary location and cleaned up on process exit. * * @memberof IpfsDaemonController * @param {Object} [opts={}] - * @param {function(Error, Node)} callback + * @param {function(Error, {ctl: IpfsApi, ctrl: Node})} callback * @returns {undefined} */ - disposable (opts, callback) { + spawn (opts, callback) { if (typeof opts === 'function') { callback = opts opts = defaultOptions } let options = {} - Object.assign(options, defaultOptions, opts || {}) - - const repoPath = options.repoPath || tempDir() - const disposable = options.disposable - delete options.disposable - delete options.repoPath - - const node = new Node(repoPath, options, disposable) - - if (typeof options.init === 'boolean' && - options.init === false) { - process.nextTick(() => callback(null, node)) - } else { - node.init((err) => callback(err, node)) - } - }, + merge(options, defaultOptions, opts || {}) + options.init = (typeof options.init !== 'undefined' ? options.init : true) + options.start = options.init && options.start // don't start if not initialized - /** - * Create a new disposable node and already started the daemon. - * - * @memberof IpfsDaemonController - * @param {Object} [opts={}] - * @param {function(Error, Node)} callback - * @returns {undefined} - */ - disposableApi (opts, callback) { - if (typeof opts === 'function') { - callback = opts - opts = defaultOptions - } + const node = new Node(options) - this.disposable(opts, (err, node) => { + waterfall([ + (cb) => options.init ? node.init(cb) : cb(null, node), + (node, cb) => options.start ? node.startDaemon(cb) : cb(null, null) + ], (err, api) => { if (err) { return callback(err) } - node.startDaemon(callback) + callback(null, { ctl: api, ctrl: node }) }) } } diff --git a/test/factory.spec.js b/test/factory.spec.js deleted file mode 100644 index e8898836..00000000 --- a/test/factory.spec.js +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-env mocha */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) - -const Factory = require('../src/factory') - -const factory = new Factory() -describe('Factory', () => { - let node = null - let hash = null - - it('should start node', function (done) { - factory.spawnNode((err, n) => { - expect(err).to.not.exist() - node = n - done() - }) - }) - - it('should add to running node', (done) => { - node.files.add(Buffer.from('Hello!'), (err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - hash = res[0].hash - done() - }) - }) - - it('should cat from running node', (done) => { - node.files.cat(hash, (err, buffer) => { - expect(err).to.not.exist() - expect(buffer).to.exist() - expect(buffer.toString()).to.equal('Hello!') - done() - }) - }) - - it('should stop running nodes', function (done) { - factory.dismantle((err) => { - expect(err).to.not.exist() - done() - }) - }) -}) diff --git a/test/spawning-daemons.js b/test/spawning-daemons.js index 15bcba00..121318d9 100644 --- a/test/spawning-daemons.js +++ b/test/spawning-daemons.js @@ -10,87 +10,157 @@ chai.use(dirtyChai) const ipfsApi = require('ipfs-api') const multiaddr = require('multiaddr') const fs = require('fs') -const rimraf = require('rimraf') const path = require('path') const once = require('once') const os = require('os') const exec = require('../src/exec') -const ipfsd = require('../src') +const ipfsdFactory = require('../src') const isWindows = os.platform() === 'win32' -module.exports = () => { - return (isJs) => { +function tempDir (isJs) { + return path.join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) +} + +module.exports = (isJs) => { + return () => { const VERSION_STRING = isJs ? 'js-ipfs version: 0.26.0' : 'ipfs version 0.4.11' const API_PORT = isJs ? '5002' : '5001' const GW_PORT = isJs ? '9090' : '8080' + it.skip('prints the version', (done) => { + ipfsdFactory.version((err, version) => { + expect(err).to.not.exist() + expect(version).to.be.eql(VERSION_STRING) + done() + }) + }) + describe('daemon spawning', () => { - describe('local daemon', () => { - const repoPath = '/tmp/ipfsd-ctl-test' - const addr = '/ip4/127.0.0.1/tcp/5678' - const config = { - Addresses: { - API: addr - } - } + describe('spawn a bare node', () => { + let node = null + let api = null - it('allows passing flags to init', (done) => { - async.waterfall([ - (cb) => ipfsd.local(repoPath, config, cb), - (node, cb) => { - async.series([ - (cb) => node.init(cb), - (cb) => node.getConfig('Addresses.API', cb) - ], (err, res) => { - expect(err).to.not.exist() - expect(res[1]).to.be.eql(addr) - rimraf(repoPath, cb) - }) - } - ], done) + after((done) => node.stopDaemon(done)) + + it('create node', (done) => { + ipfsdFactory.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { + expect(err).to.not.exist() + expect(ipfsd.ctrl).to.exist() + expect(ipfsd.ctl).to.not.exist() + node = ipfsd.ctrl + done() + }) }) - }) - describe('disposable daemon', () => { - const blorb = Buffer.from('blorb') - let ipfs - let store - let retrieve + it('init node', (done) => { + node.init((err) => { + expect(err).to.not.exist() + expect(node.initialized).to.be.ok() + done() + }) + }) - beforeEach((done) => { - async.waterfall([ - (cb) => ipfs.block.put(blorb, cb), - (block, cb) => { - store = block.cid.toBaseEncodedString() - ipfs.block.get(store, cb) - }, - (_block, cb) => { - retrieve = _block.data - cb() - } - ], done) + it('start node', (done) => { + node.startDaemon((err, a) => { + api = a + expect(err).to.not.exist() + expect(api).to.exist() + expect(api.id).to.exist() + done() + }) }) - describe('without api instance (.disposable)', () => { + describe('should add and retrieve content', () => { + const blorb = Buffer.from('blorb') + let store + let retrieve + before((done) => { async.waterfall([ - (cb) => ipfsd.disposable(cb), - (node, cb) => { - node.startDaemon((err) => { - expect(err).to.not.exist() - ipfs = ipfsApi(node.apiAddr) - cb() - }) + (cb) => api.block.put(blorb, cb), + (block, cb) => { + store = block.cid.toBaseEncodedString() + api.block.get(store, cb) + }, + (_block, cb) => { + retrieve = _block.data + cb() } ], done) }) - it('should have started the daemon and returned an api', () => { - expect(ipfs).to.exist() - expect(ipfs.id).to.exist() + it('should be able to store objects', () => { + expect(store) + .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.be.eql('blorb') + }) + + it('should have started the daemon and returned an api with host/port', () => { + expect(api).to.have.property('id') + expect(api).to.have.property('apiHost') + expect(api).to.have.property('apiPort') + }) + + it('should be able to store objects', () => { + expect(store) + .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.equal('blorb') + }) + }) + }) + + describe('spawn an initialized node', () => { + let node = null + let api = null + + after((done) => node.stopDaemon(done)) + + it('create node and init', (done) => { + ipfsdFactory.spawn({ isJs, start: false, disposable: true }, (err, ipfsd) => { + expect(err).to.not.exist() + expect(ipfsd.ctrl).to.exist() + expect(ipfsd.ctl).to.not.exist() + node = ipfsd.ctrl + done() + }) + }) + + it('start node', (done) => { + node.startDaemon((err, a) => { + api = a + expect(err).to.not.exist() + expect(api).to.exist() + expect(api.id).to.exist() + done() + }) + }) + + describe('should add and retrieve content', () => { + const blorb = Buffer.from('blorb') + let store + let retrieve + + before((done) => { + async.waterfall([ + (cb) => api.block.put(blorb, cb), + (block, cb) => { + store = block.cid.toBaseEncodedString() + api.block.get(store, cb) + }, + (_block, cb) => { + retrieve = _block.data + cb() + } + ], done) }) it('should be able to store objects', () => { @@ -101,22 +171,74 @@ module.exports = () => { it('should be able to retrieve objects', () => { expect(retrieve.toString()).to.be.eql('blorb') }) + + it('should have started the daemon and returned an api with host/port', () => { + expect(api).to.have.property('id') + expect(api).to.have.property('apiHost') + expect(api).to.have.property('apiPort') + }) + + it('should be able to store objects', () => { + expect(store) + .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.equal('blorb') + }) + }) + }) + + describe('spawn a node and attach api', () => { + let node = null + let api = null + + after((done) => node.stopDaemon(done)) + + it('create init and start node', (done) => { + ipfsdFactory.spawn({ isJs }, (err, ipfsd) => { + expect(err).to.not.exist() + expect(ipfsd.ctrl).to.exist() + expect(ipfsd.ctl).to.exist() + expect(ipfsd.ctl.id).to.exist() + node = ipfsd.ctrl + api = ipfsd.ctl + done() + }) }) - describe('with api instance (.disposableApi)', () => { + describe('should add and retrieve content', () => { + const blorb = Buffer.from('blorb') + let store + let retrieve + before((done) => { - ipfsd.disposableApi((err, api) => { - expect(err).to.not.exist() + async.waterfall([ + (cb) => api.block.put(blorb, cb), + (block, cb) => { + store = block.cid.toBaseEncodedString() + api.block.get(store, cb) + }, + (_block, cb) => { + retrieve = _block.data + cb() + } + ], done) + }) - ipfs = api - done() - }) + it('should be able to store objects', () => { + expect(store) + .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.be.eql('blorb') }) it('should have started the daemon and returned an api with host/port', () => { - expect(ipfs).to.have.property('id') - expect(ipfs).to.have.property('apiHost') - expect(ipfs).to.have.property('apiPort') + expect(api).to.have.property('id') + expect(api).to.have.property('apiHost') + expect(api).to.have.property('apiPort') }) it('should be able to store objects', () => { @@ -130,16 +252,48 @@ module.exports = () => { }) }) + describe('spawn a node and pass init options', () => { + const repoPath = tempDir(isJs) + const addr = '/ip4/127.0.0.1/tcp/5678' + const config = { + 'Addresses.API': addr + } + + it('allows passing ipfs config options to spawn', (done) => { + const options = { + config, + repoPath, + init: true, + isJs + } + + let node + async.waterfall([ + (cb) => ipfsdFactory.spawn(options, cb), + (ipfsd, cb) => { + node = ipfsd.ctrl + node.getConfig('Addresses.API', cb) + } + ], (err, res) => { + expect(err).to.not.exist() + expect(res).to.be.eql(addr) + async.series([ + (cb) => node.stopDaemon(cb) + ], done) + }) + }) + }) + describe('starting and stopping', () => { let node - describe('init', () => { + describe(`create and init a node (ctlr)`, () => { before((done) => { - ipfsd.disposable((err, res) => { - if (err) { - done(err) - } - node = res + ipfsdFactory.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { + expect(err).to.not.exist() + expect(ipfsd.ctrl).to.exist() + + node = ipfsd.ctrl done() }) }) @@ -201,74 +355,31 @@ module.exports = () => { it('should be stopped', () => { expect(node.daemonPid()).to.not.exist() expect(stopped).to.equal(true) - }) - }) - }) - - describe('setting up and init a local node', () => { - const testpath1 = '/tmp/ipfstestpath1' - - describe('cleanup', () => { - before((done) => { - rimraf(testpath1, done) - }) - - it('should not have a directory', () => { - expect(fs.existsSync('/tmp/ipfstestpath1')).to.be.eql(false) - }) - }) - - describe('setup', () => { - let node - before((done) => { - ipfsd.local(testpath1, (err, res) => { - if (err) { - return done(err) - } - node = res - done() - }) - }) - - it('should have returned a node', () => { - expect(node).to.exist() - }) - - it('should not be initialized', () => { - expect(node.initialized).to.be.eql(false) - }) - - describe('initialize', () => { - before((done) => { - node.init(done) - }) - - it('should have made a directory', () => { - expect(fs.existsSync(testpath1)).to.be.eql(true) - }) - - it('should be initialized', () => { - expect(node.initialized).to.be.eql(true) - }) + expect(fs.existsSync(path.join(node.path, 'repo.lock'))).to.not.be.ok() + expect(fs.existsSync(path.join(node.path, 'api'))).to.not.be.ok() }) }) }) describe('change config of a disposable node', () => { - let ipfsNode + let node + // let ipfs before((done) => { - ipfsd.disposable((err, node) => { + ipfsdFactory.spawn({ isJs }, (err, res) => { if (err) { return done(err) } - ipfsNode = node + node = res.ctrl + // ipfs = res.ctl done() }) }) + after((done) => node.stopDaemon(done)) + it('Should return a config value', (done) => { - ipfsNode.getConfig('Bootstrap', (err, config) => { + node.getConfig('Bootstrap', (err, config) => { expect(err).to.not.exist() expect(config).to.exist() done() @@ -276,17 +387,17 @@ module.exports = () => { }) it('Should return the whole config', (done) => { - ipfsNode.getConfig((err, config) => { + node.getConfig((err, config) => { expect(err).to.not.exist() expect(config).to.exist() done() }) }) - it('Should set a config value', (done) => { + it.skip('Should set a config value', (done) => { async.series([ - (cb) => ipfsNode.setConfig('Bootstrap', 'null', cb), - (cb) => ipfsNode.getConfig('Bootstrap', cb) + (cb) => node.setConfig('Bootstrap', 'null', cb), + (cb) => node.getConfig('Bootstrap', cb) ], (err, res) => { expect(err).to.not.exist() expect(res[1]).to.be.eql('null') @@ -296,9 +407,9 @@ module.exports = () => { it('should give an error if setting an invalid config value', function (done) { if (isJs) { - this.skip() + this.skip() // js doesn't fail on invalid config } else { - ipfsNode.setConfig('Bootstrap', 'true', (err) => { + node.setConfig('Bootstrap', 'true', (err) => { expect(err.message).to.match(/failed to set config value/) done() }) @@ -306,31 +417,14 @@ module.exports = () => { }) }) - it('allows passing via $IPFS_EXEC', (done) => { - process.env.IPFS_EXEC = '/some/path' - ipfsd.local((err, node) => { - expect(err).to.not.exist() - expect(node.exec).to.be.eql('/some/path') - - process.env.IPFS_EXEC = '' - done() - }) - }) - - it('prints the version', (done) => { - ipfsd.version((err, version) => { - expect(err).to.not.exist() - expect(version).to.be.eql(VERSION_STRING) - done() - }) - }) - describe('ipfs-api version', () => { let ipfs + let node before((done) => { - ipfsd.disposable((err, node) => { + ipfsdFactory.spawn({ start: false }, (err, ret) => { expect(err).to.not.exist() + node = ret.ctrl node.startDaemon((err, ignore) => { expect(err).to.not.exist() ipfs = ipfsApi(node.apiAddr) @@ -339,6 +433,8 @@ module.exports = () => { }) }) + after((done) => node.stopDaemon(done)) + // skip on windows for now // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 // fails on windows see https://github.com/ipfs/js-ipfs-api/issues/408 @@ -385,57 +481,19 @@ module.exports = () => { }) describe('startDaemon', () => { - it('start and stop', (done) => { - const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` - - const check = (cb) => { - // skip on windows - // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326983530 - if (!isWindows) { - if (fs.existsSync(path.join(dir, 'repo.lock'))) { - cb(new Error('repo.lock not removed')) - } - if (fs.existsSync(path.join(dir, 'api'))) { - cb(new Error('api file not removed')) - } - } - cb() - } - - async.waterfall([ - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.init((err) => cb(err, node)), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check, - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check, - (cb) => ipfsd.local(dir, cb), - (node, cb) => node.startDaemon((err) => cb(err, node)), - (node, cb) => node.stopDaemon(cb), - check - ], done) - }) - it('starts the daemon and returns valid API and gateway addresses', (done) => { - const dir = `${os.tmpdir()}/tmp-${Date.now() + '-' + Math.random().toString(36)}` - - async.waterfall([ - (cb) => ipfsd.local(dir, cb), - (daemon, cb) => daemon.init((err) => cb(err, daemon)), - (daemon, cb) => daemon.startDaemon((err, api) => cb(err, daemon, api)) - ], (err, daemon, api) => { + ipfsdFactory.spawn({ isJs, config: null }, (err, ipfsd) => { expect(err).to.not.exist() + const api = ipfsd.ctl + const node = ipfsd.ctrl // Check for props in daemon - expect(daemon).to.have.property('apiAddr') - expect(daemon).to.have.property('gatewayAddr') - expect(daemon.apiAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(daemon.apiAddr)).to.equal(true) - expect(daemon.gatewayAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(daemon.gatewayAddr)).to.equal(true) + expect(node).to.have.property('apiAddr') + expect(node).to.have.property('gatewayAddr') + expect(node.apiAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(node.apiAddr)).to.equal(true) + expect(node.gatewayAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(node.gatewayAddr)).to.equal(true) // Check for props in ipfs-api instance expect(api).to.have.property('apiHost') @@ -443,12 +501,11 @@ module.exports = () => { expect(api).to.have.property('gatewayHost') expect(api).to.have.property('gatewayPort') expect(api.apiHost).to.equal('127.0.0.1') - console.log(API_PORT) expect(api.apiPort).to.equal(API_PORT) expect(api.gatewayHost).to.equal('127.0.0.1') expect(api.gatewayPort).to.equal(GW_PORT) - daemon.stopDaemon(done) + node.stopDaemon(done) }) }) @@ -457,9 +514,9 @@ module.exports = () => { if (isJs) { this.skip() } else { - ipfsd.disposable((err, node) => { + ipfsdFactory.spawn({ start: false }, (err, ipfsd) => { expect(err).to.not.exist() - node.startDaemon(['--should-not-exist'], (err) => { + ipfsd.ctrl.startDaemon(['--should-not-exist'], (err) => { expect(err).to.exist() expect(err.message) .to.match(/Unrecognized option 'should-not-exist'/) From aafcae61021e8961956b3e1435a5827405997b52 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 29 Nov 2017 11:38:53 -0600 Subject: [PATCH 06/85] more tests reworking --- src/index.js | 5 +-- test/daemon.spec.js | 14 -------- test/node.js | 23 +++++++++++++ test/npm-installs.js | 71 +++++++++++++++++++++++++++++++++++++++ test/npm-installs.spec.js | 58 -------------------------------- test/spawning-daemons.js | 4 +-- 6 files changed, 99 insertions(+), 76 deletions(-) delete mode 100644 test/daemon.spec.js create mode 100644 test/node.js create mode 100644 test/npm-installs.js delete mode 100644 test/npm-installs.spec.js diff --git a/src/index.js b/src/index.js index 1a953365..19a7c74e 100644 --- a/src/index.js +++ b/src/index.js @@ -20,11 +20,12 @@ const IpfsDaemonController = { * Get the version of the currently used go-ipfs binary. * * @memberof IpfsDaemonController + * @param {Object} [opts={}] * @param {function(Error, string)} callback * @returns {undefined} */ - version (callback) { - (new Node()).version(callback) + version (opts, callback) { + (new Node(opts)).version(callback) }, /** diff --git a/test/daemon.spec.js b/test/daemon.spec.js deleted file mode 100644 index ec236046..00000000 --- a/test/daemon.spec.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const daemon = require('./spawning-daemons.js') - -describe('ipfsd-ctl', () => { - describe('Go daemon', () => { - daemon(false)() - }) - - describe('Js daemon', () => { - daemon(true)() - }) -}) diff --git a/test/node.js b/test/node.js new file mode 100644 index 00000000..c859ff4d --- /dev/null +++ b/test/node.js @@ -0,0 +1,23 @@ +/* eslint-env mocha */ +'use strict' + +const daemon = require('./spawning-daemons.js') +const install = require('./npm-installs') + +describe('ipfsd-ctl', () => { + afterEach(() => Object.keys(process.env).forEach((key) => { + if (key.includes('IPFS')) { + delete process.env[key] // clean up IPFS envs + } + })) + + describe('Go daemon', () => { + daemon(false)() + install(false)() + }) + + describe('Js daemon', () => { + daemon(true)() + install(true)() + }) +}) diff --git a/test/npm-installs.js b/test/npm-installs.js new file mode 100644 index 00000000..95c7b3bd --- /dev/null +++ b/test/npm-installs.js @@ -0,0 +1,71 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) +const fs = require('fs') +const rimraf = require('rimraf') +const mkdirp = require('mkdirp') +const path = require('path') +const os = require('os') +const isWindows = os.platform() === 'win32' + +module.exports = (isJs) => { + return () => { + describe('ipfs executable path', () => { + const tmp = os.tmpdir() + const appName = isJs + ? 'bin.js' + : isWindows ? 'ipfs.exe' : 'ipfs' + + const oldPath = process.env.testpath + before(() => { process.env.testpath = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/lib') }) // fake __dirname + after(() => { process.env.testpath = oldPath }) + + it('has the correct path when installed with npm3', (done) => { + let execPath = isJs + ? 'ipfsd-ctl-test/node_modules/ipfs/src/cli' + : 'ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs' + + let npm3Path = path.join(tmp, execPath) + + mkdirp(npm3Path, (err) => { + expect(err).to.not.exist() + + fs.writeFileSync(path.join(npm3Path, appName)) + delete require.cache[require.resolve('../src/daemon.js')] + const Daemon = require('../src/daemon.js') + + const node = new Daemon({ isJs }) + expect(node.exec) + .to.eql(path.join(tmp, `${execPath}/${appName}`)) + rimraf(path.join(tmp, 'ipfsd-ctl-test'), done) + }) + }) + + it('has the correct path when installed with npm2', (done) => { + let execPath = isJs + ? 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/ipfs/src/cli' + : 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs' + + let npm2Path = path.join(tmp, execPath) + + mkdirp(npm2Path, (err) => { + expect(err).to.not.exist() + + fs.writeFileSync(path.join(npm2Path, appName)) + delete require.cache[require.resolve('../src/daemon.js')] + const Daemon = require('../src/daemon.js') + + const node = new Daemon({ isJs }) + expect(node.exec) + .to.eql(path.join(tmp, `${execPath}/${appName}`)) + rimraf(path.join(tmp, 'ipfsd-ctl-test'), done) + }) + }) + }) + } +} diff --git a/test/npm-installs.spec.js b/test/npm-installs.spec.js deleted file mode 100644 index b9c353b6..00000000 --- a/test/npm-installs.spec.js +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const fs = require('fs') -const rimraf = require('rimraf') -const mkdirp = require('mkdirp') -const path = require('path') -const os = require('os') -const isWindows = os.platform() === 'win32' - -describe('ipfs executable path', () => { - const tmp = os.tmpdir() - const appName = isWindows ? 'ipfs.exe' : 'ipfs' - - const oldPath = process.env.testpath - before(() => { process.env.testpath = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/lib') }) // fake __dirname - after(() => { process.env.testpath = oldPath }) - - it('has the correct path when installed with npm3', (done) => { - let npm3Path = path.join(tmp, 'ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs') - - mkdirp(npm3Path, (err) => { - expect(err).to.not.exist() - - fs.writeFileSync(path.join(npm3Path, appName)) - delete require.cache[require.resolve('../src/daemon.js')] - const Daemon = require('../src/daemon.js') - - const node = new Daemon() - expect(node.exec) - .to.eql(path.join(tmp, `ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs/${appName}`)) - rimraf(path.join(tmp, 'ipfsd-ctl-test'), done) - }) - }) - - it('has the correct path when installed with npm2', (done) => { - let npm2Path = path.join(tmp, 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs') - - mkdirp(npm2Path, (err) => { - expect(err).to.not.exist() - - fs.writeFileSync(path.join(npm2Path, appName)) - delete require.cache[require.resolve('../src/daemon.js')] - const Daemon = require('../src/daemon.js') - - const node = new Daemon() - - expect(node.exec) - .to.eql(path.join(tmp, `ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs/${appName}`)) - rimraf(path.join(tmp, 'ipfsd-ctl-test'), done) - }) - }) -}) diff --git a/test/spawning-daemons.js b/test/spawning-daemons.js index 121318d9..9f51fb23 100644 --- a/test/spawning-daemons.js +++ b/test/spawning-daemons.js @@ -30,8 +30,8 @@ module.exports = (isJs) => { const API_PORT = isJs ? '5002' : '5001' const GW_PORT = isJs ? '9090' : '8080' - it.skip('prints the version', (done) => { - ipfsdFactory.version((err, version) => { + it('prints the version', (done) => { + ipfsdFactory.version({ isJs }, (err, version) => { expect(err).to.not.exist() expect(version).to.be.eql(VERSION_STRING) done() From 8e0b9a5c76e8f2621b8fb263bebb5ed176a4745e Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 29 Nov 2017 12:01:22 -0600 Subject: [PATCH 07/85] fix: timeouts --- test/spawning-daemons.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/spawning-daemons.js b/test/spawning-daemons.js index 9f51fb23..eba8404c 100644 --- a/test/spawning-daemons.js +++ b/test/spawning-daemons.js @@ -330,7 +330,8 @@ module.exports = (isJs) => { }) }) - describe('stopping', () => { + describe('stopping', function () { + this.timeout(20 * 1000) // shutdown grace period is already 10500 let stopped = false before((done) => { From 38488120b371384f0aad2d83a5d8bab3ecd4ba2d Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 29 Nov 2017 13:53:29 -0600 Subject: [PATCH 08/85] fix: timeouts --- test/spawning-daemons.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/spawning-daemons.js b/test/spawning-daemons.js index eba8404c..689556c0 100644 --- a/test/spawning-daemons.js +++ b/test/spawning-daemons.js @@ -330,8 +330,7 @@ module.exports = (isJs) => { }) }) - describe('stopping', function () { - this.timeout(20 * 1000) // shutdown grace period is already 10500 + describe('stopping', () => { let stopped = false before((done) => { @@ -353,7 +352,9 @@ module.exports = (isJs) => { }, 100) }) - it('should be stopped', () => { + it('should be stopped', function () { + this.timeout(20 * 1000) // shutdown grace period is already 10500 + expect(node.daemonPid()).to.not.exist() expect(stopped).to.equal(true) expect(fs.existsSync(path.join(node.path, 'repo.lock'))).to.not.be.ok() From a2917990fb9f3d9534fb0c549c856e4956c8e754 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 30 Nov 2017 17:45:03 -0600 Subject: [PATCH 09/85] feat: making tests run in browser and node --- .aegir.js | 15 +- package.json | 21 +- src/daemon.js | 14 + src/index.js | 8 + src/remote/index.js | 302 ++++++++++++++++++++++ src/remote/routes.js | 245 ++++++++++++++++++ src/remote/server.js | 30 +++ src/remote/utils.js | 51 ++++ test/browser.js | 4 + test/daemon.js | 30 +++ test/{exec.spec.js => exec.js} | 0 test/node.js | 24 +- test/{spawning-daemons.js => spawning.js} | 101 +------- test/start-stop.js | 98 +++++++ 14 files changed, 835 insertions(+), 108 deletions(-) create mode 100644 src/remote/index.js create mode 100644 src/remote/routes.js create mode 100644 src/remote/server.js create mode 100644 src/remote/utils.js create mode 100644 test/browser.js create mode 100644 test/daemon.js rename test/{exec.spec.js => exec.js} (100%) rename test/{spawning-daemons.js => spawning.js} (84%) create mode 100644 test/start-stop.js diff --git a/.aegir.js b/.aegir.js index 4e3f0f91..6a5ea8f3 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,12 +1,21 @@ 'use strict' -const tasks = require('./src/remote-factory/tasks') +const server = require('./src/remote/server') module.exports = { + karma: { + files: [{ + pattern: 'test/fixtures/**/*', + watched: false, + served: true, + included: false + }], + singleRun: true + }, hooks: { browser: { - pre: tasks.start, - post: tasks.stop + pre: server.start, + post: server.stop } } } diff --git a/package.json b/package.json index 63644d98..e573c43b 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,22 @@ "scripts": { "lint": "aegir lint", "coverage": "aegir coverage", - "test": "aegir test -t node --timeout 50000", + "test": "aegir test -t node -t browser --no-cors --timeout 50000", + "test:node": "aegir test -t node --timeout 50000", + "test:browser": "aegir test -t browser --no-cors --timeout 50000", "docs": "aegir docs", "release": "aegir release -t node", "release-minor": "aegir release --type minor -t node", "release-major": "aegir release --type major -t node", "coverage-publish": "aegir coverage -u" }, + "browser": { + "./src/daemon.js": false, + "glob": false, + "fs": false, + "stream": "readable-stream", + "http": "stream-http" + }, "engines": { "node": ">=6.0.0", "npm": ">=3.0.0" @@ -51,16 +60,26 @@ "license": "MIT", "dependencies": { "async": "^2.6.0", + "debug": "^3.1.0", "detect-node": "^2.0.3", "eslint-config-standard-jsx": "^4.0.2", "go-ipfs-dep": "0.4.13", + "guid": "0.0.12", + "hapi": "^16.6.2", + "http-browserify": "^1.7.0", "ipfs-api": "^17.1.3", + "lodash.clonewith": "^4.5.0", "lodash.get": "^4.4.2", + "lodash.mapvalues": "^4.6.0", "lodash.merge": "^4.6.0", "multiaddr": "^3.0.1", "once": "^1.4.0", + "qs": "^6.5.1", + "readable-stream": "^2.3.3", + "request": "^2.83.0", "rimraf": "^2.6.2", "shutdown": "^0.3.0", + "stream-http": "^2.7.2", "subcomandante": "^1.0.5", "truthy": "0.0.1" }, diff --git a/src/daemon.js b/src/daemon.js index a1e4134d..4d94d259 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -109,6 +109,7 @@ class Node { this.env = this.path ? Object.assign({}, process.env, { IPFS_PATH: this.path }) : process.env this._apiAddr = null this._gatewayAddr = null + this._started = false if (this.opts.env) { Object.assign(this.env, this.opts.env) @@ -133,6 +134,15 @@ class Node { return this._gatewayAddr } + /** + * Is the node started + * + * @return {boolean} + */ + get started () { + return this._started + } + _run (args, opts, callback) { return exec(this.exec, args, opts, callback) } @@ -262,6 +272,7 @@ class Node { if (output.match(/(?:daemon is running|Daemon is ready)/)) { // we're good + this._started = true return callback(null, this.api) } } @@ -335,6 +346,9 @@ class Node { callback = key key = 'show' } + if (!key) { + key = 'show' + } async.waterfall([ (cb) => this._run( diff --git a/src/index.js b/src/index.js index 19a7c74e..04cc08aa 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,14 @@ const waterfall = require('async/waterfall') const Node = require('./daemon') const defaultOptions = { + config: { + 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], + 'API.HTTPHeaders.Access-Control-Allow-Methods': [ + 'PUT', + 'POST', + 'GET' + ] + }, disposable: true, start: true, init: true diff --git a/src/remote/index.js b/src/remote/index.js new file mode 100644 index 00000000..4fdcf7f0 --- /dev/null +++ b/src/remote/index.js @@ -0,0 +1,302 @@ +'use strict' + +const request = require('http') +const IpfsApi = require('ipfs-api') +const multiaddr = require('multiaddr') +const utils = require('./utils') + +const encodeParams = utils.encodeParams +const getResponse = utils.getResponse + +const createRemoteFactory = (host, port) => { + // create a client + + if (!host) { + host = 'localhost' + } + + if (!port) { + port = 9999 + } + + class Node { + constructor (id, apiAddr, gwAddrs) { + this._id = id + this._apiAddr = multiaddr(apiAddr) + this._gwAddr = multiaddr(gwAddrs) + this.initialized = false + this.started = false + } + + /** + * Get the address of connected IPFS API. + * + * @returns {Multiaddr} + */ + get apiAddr () { + return this._apiAddr + } + + /** + * Set the address of connected IPFS API. + * + * @param {Multiaddr} addr + * @returns {void} + */ + set apiAddr (addr) { + this._apiAddr = addr + } + + /** + * Get the address of connected IPFS HTTP Gateway. + * + * @returns {Multiaddr} + */ + get gatewayAddr () { + return this._gwAddr + } + + /** + * Set the address of connected IPFS Gateway. + * + * @param {Multiaddr} addr + * @returns {void} + */ + set gatewayAddr (addr) { + this._gwAddr = addr + } + + /** + * Initialize a repo. + * + * @param {Object} [initOpts={}] + * @param {number} [initOpts.keysize=2048] - The bit size of the identiy key. + * @param {string} [initOpts.directory=IPFS_PATH] - The location of the repo. + * @param {function (Error, Node)} cb + * @returns {undefined} + */ + init (initOpts, cb) { + if (typeof initOpts === 'function') { + cb = initOpts + initOpts = {} + } + request.get({ host, port, path: `/init?${encodeParams(this._id, { initOpts })}` }, + (res) => getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + this.initialized = true + cb(null, res) + })) + } + + /** + * Delete the repo that was being used. + * If the node was marked as `disposable` this will be called + * automatically when the process is exited. + * + * @param {function(Error)} cb + * @returns {undefined} + */ + shutdown (cb) { + request.get({ host, port, path: `/shutdown` }, (res) => { getResponse(res, cb) }) + } + + /** + * Start the daemon. + * + * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. + * @param {function(Error, IpfsApi)} cb + * @returns {undefined} + */ + startDaemon (flags, cb) { + if (typeof flags === 'function') { + cb = flags + flags = {} + } + request.get({ host, port, path: `/startDaemon?${encodeParams(this._id, { flags })}` }, (res) => { + getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + this.started = true + + const apiAddr = res.data ? res.data.apiAddr : '' + const gatewayAddr = res.data ? res.data.gatewayAddr : '' + + const api = IpfsApi(apiAddr) + api.apiHost = multiaddr(apiAddr).nodeAddress().address + api.apiPort = multiaddr(apiAddr).nodeAddress().port + api.gatewayHost = multiaddr(gatewayAddr).nodeAddress().address + api.gatewayPort = multiaddr(gatewayAddr).nodeAddress().port + + return cb(null, api) + }) + }) + } + + /** + * Stop the daemon. + * + * @param {function(Error)} cb + * @returns {undefined} + */ + stopDaemon (cb) { + request.get({ host, port, path: `/stopDaemon?${encodeParams(this._id)}` }, + (res) => { + getResponse(res, (err) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null) + }) + }) + } + + /** + * Kill the `ipfs daemon` process. + * + * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent + * if the process hasn't exited yet. + * + * @param {function()} cb - Called when the process was killed. + * @returns {undefined} + */ + killProcess (cb) { + request.get({ host, port, path: `/killProcess` }, (res) => { + getResponse(res, (err) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null) + }) + }) + } + + /** + * Get the pid of the `ipfs daemon` process. + * + * @param {Function} cb + * @returns {number} + */ + daemonPid (cb) { + request.get({ host, port, path: `/daemonPid` }, (res) => { + getResponse(res, (err, data) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null, data) + }) + }) + } + + /** + * Call `ipfs config` + * + * If no `key` is passed, the whole config is returned as an object. + * + * @param {string} [key] - A specific config to retrieve. + * @param {function(Error, (Object|string))} cb + * @returns {undefined} + */ + getConfig (key, cb) { + if (typeof key === 'function') { + cb = key + key = null + } + + request.get({ host, port, path: `/getConfig?${encodeParams(this._id, { key })}` }, (res) => { + getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null, res.data) + }) + }) + } + + /** + * Set a config value. + * + * @param {string} key + * @param {string} value + * @param {function(Error)} cb + * @returns {undefined} + */ + setConfig (key, value, cb) { + request.get({ host, port, path: `/setConfig?${encodeParams(this._id, { key, value })}` }, (res) => { + getResponse(res, (err, data) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null, data) + }) + }) + } + + /** + * Replace the configuration with a given file + * + * @param {string} file - path to the new config file + * @param {function(Error)} cb + * @returns {undefined} + */ + + replaceConf (file, cb) { + cb(new Error('not implemented')) + } + } + + return { + spawn: (opts, cb) => { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + request.get({ host, port, path: `/spawn?${encodeParams(this._id, { opts })}` }, (res) => { + getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + const apiAddr = res.data ? res.data.apiAddr : '' + const gatewayAddr = res.data ? res.data.gatewayAddr : '' + + const node = new Node( + res._id, + apiAddr, + gatewayAddr) + + let api + if (apiAddr) { + api = IpfsApi(apiAddr) + api.apiHost = multiaddr(apiAddr).nodeAddress().address + api.apiPort = multiaddr(apiAddr).nodeAddress().port + } + + if (gatewayAddr) { + api.gatewayHost = multiaddr(gatewayAddr).nodeAddress().address + api.gatewayPort = multiaddr(gatewayAddr).nodeAddress().port + } + + cb(null, { ctl: api, ctrl: node }) + }) + }) + } + } +} + +module.exports = createRemoteFactory diff --git a/src/remote/routes.js b/src/remote/routes.js new file mode 100644 index 00000000..78180dee --- /dev/null +++ b/src/remote/routes.js @@ -0,0 +1,245 @@ +'use strict' + +const ipfsFactory = require('../') +const guid = require('guid') +const boom = require('boom') +const utils = require('./utils') + +const parseQuery = utils.parseQuery +const makeResponse = utils.makeResponse + +let nodes = {} +module.exports = (server) => { + /** + * Spawn an IPFS node + * The repo is created in a temporary location and cleaned up on process exit. + **/ + server.route({ + method: 'GET', + path: '/spawn', + handler: (request, reply) => { + const qr = parseQuery(request.query) + const opts = qr.params.opts || {} + ipfsFactory.spawn(opts, (err, ipfsd) => { + if (err) { + return reply(boom.badRequest(err)) + } + + const id = guid.raw() + nodes[id] = ipfsd.ctrl + + 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(makeResponse(id, api)) + }) + } + }) + + /** + * Get the address of connected IPFS API. + * + * @returns {Multiaddr} + */ + server.route({ + method: 'GET', + path: '/apiAddr', + handler: (request, reply) => { + const id = parseQuery(request.query).id + reply(makeResponse(id, nodes[id].apiAddr())) + } + }) + + /** + * Get the address of connected IPFS HTTP Gateway. + */ + server.route({ + method: 'GET', + path: '/getawayAddr', + handler: (request, reply) => { + const id = parseQuery(request.query).id + reply(makeResponse(id, nodes[id].getawayAddr())) + } + }) + + /** + * Initialize a repo. + **/ + server.route({ + method: 'GET', + path: '/init', + handler: (request, reply) => { + const qr = parseQuery(request.query) + const id = qr.id + const initOpts = qr.initOpts || {} + nodes[id].init(initOpts, (err, node) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(makeResponse(id, { initialized: node.initialized })) + }) + } + }) + + /** + * Delete the repo that was being used. + * If the node was marked as `disposable` this will be called + * automatically when the process is exited. + **/ + server.route({ + method: 'GET', + path: '/shutdown', + handler: (request, reply) => { + const id = parseQuery(request.query).id + nodes[id].shutdown((err, res) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(makeResponse(id, res)) + }) + } + }) + + /** + * Start the daemon. + **/ + server.route({ + method: 'GET', + path: '/startDaemon', + handler: (request, reply) => { + const qr = parseQuery(request.query) + const id = qr.id + const flags = Object.values(qr.params ? qr.params.flags : {}) + nodes[id].startDaemon(flags, (err) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(makeResponse(id, { + apiAddr: nodes[id].apiAddr.toString(), + gatewayAddr: nodes[id].gatewayAddr.toString() + })) + }) + } + }) + + /** + * Stop the daemon. + */ + server.route({ + method: 'GET', + path: '/stopDaemon', + handler: (request, reply) => { + const id = parseQuery(request.query).id + nodes[id].stopDaemon((err, res) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(makeResponse(id, res)) + }) + } + }) + + /** + * Kill the `ipfs daemon` process. + * + * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent + * if the process hasn't exited yet. + */ + server.route({ + method: 'GET', + path: '/killProcess', + handler: (request, reply) => { + const id = parseQuery(request.query).id + nodes[id].killProcess((err, res) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(makeResponse(id, res)) + }) + } + }) + + /** + * Get the pid of the `ipfs daemon` process. + * + * @returns {number} + */ + server.route({ + method: 'GET', + path: '/daemonPid', + handler: (request, reply) => { + const id = parseQuery(request.query).id + reply(makeResponse(id, nodes[id].daemonPid(nodes[id]))) + } + }) + + /** + * Call `ipfs config` + * + * If no `key` is passed, the whole config is returned as an object. + */ + server.route({ + method: 'GET', + path: '/getConfig', + handler: (request, reply) => { + const qr = parseQuery(request.query) + const id = qr.id + const key = qr.params ? qr.params.key : null + nodes[id].getConfig(key, (err, res) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(makeResponse(id, res)) + }) + } + }) + + /** + * Set a config value. + */ + server.route({ + method: 'GET', + path: '/setConfig', + handler: (request, reply) => { + const qr = parseQuery(request.query) + const id = qr.id + const key = qr.params ? qr.params.key : undefined + const val = qr.params ? qr.params.value : undefined + nodes[id].setConfig(key, val, (err, res) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(makeResponse(id, res)) + }) + } + }) + + /** + * Replace the configuration with a given file + **/ + server.route({ + method: 'GET', + path: '/replaceConf', + handler: (request, reply) => { + const id = parseQuery(request.query).id + nodes[id].replaceConf((err, res) => { + if (err) { + return reply(boom.badRequest(err)) + } + + reply(null, makeResponse(id, res)) + }) + } + }) +} diff --git a/src/remote/server.js b/src/remote/server.js new file mode 100644 index 00000000..432f5a2b --- /dev/null +++ b/src/remote/server.js @@ -0,0 +1,30 @@ +'use strict' + +const Hapi = require('hapi') +const routes = require('./routes') + +let server = null +exports.start = function start (port, host, cb) { + if (typeof port === 'function') { + cb = port + port = 9999 + } + + if (typeof host === 'function') { + cb = host + host = 'localhost' + } + + port = port || 9999 + host = host || 'localhost' + + server = new Hapi.Server() + server.connection({ port, host }) + + routes(server) + server.start(cb) +} + +exports.stop = function stop (cb) { + server.stop(cb) +} diff --git a/src/remote/utils.js b/src/remote/utils.js new file mode 100644 index 00000000..b3cefdf0 --- /dev/null +++ b/src/remote/utils.js @@ -0,0 +1,51 @@ +'use strict' + +const Qs = require('qs') +const mapValues = require('lodash.mapValues') + +exports.getResponse = (res, cb) => { + let data = '' + res.on('data', function (buf) { + data += buf.toString() + }) + + res.on('end', () => { + const response = JSON.parse(data) + if (typeof response.statusCode !== 'undefined' && response.statusCode !== 200) { + return cb(new Error(response.message)) + } + + cb(null, response) + }) + + res.on('err', cb) +} + +exports.encodeParams = (id, params) => { + return Qs.stringify({ id, params }) +} + +exports.makeResponse = (id, data) => { + return { _id: id, data: data } +} + +exports.parseQuery = (query) => { + // sadly `parse` doesn't deal with booleans correctly + // and since HAPI gives a partially processed query + // string, the `decoder` hook never gets called, + // hence the use of mapValues + let parsed = Qs.parse(query) + const transformer = (val) => { + if (typeof val === 'object') { + return mapValues(val, transformer) + } + + if (val === 'true' || val === 'false') { + val = val === 'true' + } + + return val + } + + return mapValues(parsed, transformer) +} diff --git a/test/browser.js b/test/browser.js new file mode 100644 index 00000000..960fc61e --- /dev/null +++ b/test/browser.js @@ -0,0 +1,4 @@ +/* eslint-env mocha */ +'use strict' + +require('./daemon') diff --git a/test/daemon.js b/test/daemon.js new file mode 100644 index 00000000..5f40fdf6 --- /dev/null +++ b/test/daemon.js @@ -0,0 +1,30 @@ +/* eslint-env mocha */ +'use strict' + +const daemon = require('./spawning') +const isNode = require('detect-node') + +let ipfsdFactory + +if (isNode) { + ipfsdFactory = require('../src') +} else { + ipfsdFactory = require('../src/remote')() +} + +describe('ipfsd-ctl', () => { + // clean up IPFS env + afterEach(() => Object.keys(process.env).forEach((key) => { + if (key.includes('IPFS')) { + delete process.env[key] + } + })) + + describe('Go daemon', () => { + daemon(ipfsdFactory, false)() + }) + + describe('Js daemon', () => { + daemon(ipfsdFactory, true)() + }) +}) diff --git a/test/exec.spec.js b/test/exec.js similarity index 100% rename from test/exec.spec.js rename to test/exec.js diff --git a/test/node.js b/test/node.js index c859ff4d..6612aeb8 100644 --- a/test/node.js +++ b/test/node.js @@ -1,23 +1,21 @@ /* eslint-env mocha */ + 'use strict' -const daemon = require('./spawning-daemons.js') -const install = require('./npm-installs') +require('./daemon') +require('./exec') -describe('ipfsd-ctl', () => { - afterEach(() => Object.keys(process.env).forEach((key) => { - if (key.includes('IPFS')) { - delete process.env[key] // clean up IPFS envs - } - })) +const startStop = require('./start-stop') +const install = require('./npm-installs') - describe('Go daemon', () => { - daemon(false)() - install(false)() +describe('node', () => { + describe('cleanup', () => { + startStop(true)() + startStop(false)() }) - describe('Js daemon', () => { - daemon(true)() + describe('install', () => { install(true)() + install(false)() }) }) diff --git a/test/spawning-daemons.js b/test/spawning.js similarity index 84% rename from test/spawning-daemons.js rename to test/spawning.js index 689556c0..c84c7307 100644 --- a/test/spawning-daemons.js +++ b/test/spawning.js @@ -7,30 +7,28 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) + const ipfsApi = require('ipfs-api') const multiaddr = require('multiaddr') -const fs = require('fs') const path = require('path') -const once = require('once') const os = require('os') - -const exec = require('../src/exec') -const ipfsdFactory = require('../src') - +const isNode = require('detect-node') const isWindows = os.platform() === 'win32' function tempDir (isJs) { return path.join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) } -module.exports = (isJs) => { +module.exports = (ipfsdFactory, isJs) => { return () => { - const VERSION_STRING = isJs ? 'js-ipfs version: 0.26.0' : 'ipfs version 0.4.11' - + const VERSION_STRING = isJs ? 'js-ipfs version: 0.26.0' : 'ipfs version 0.4.13' const API_PORT = isJs ? '5002' : '5001' const GW_PORT = isJs ? '9090' : '8080' - it('prints the version', (done) => { + it('prints the version', function (done) { + if (!isNode) { + this.skip() + } ipfsdFactory.version({ isJs }, (err, version) => { expect(err).to.not.exist() expect(version).to.be.eql(VERSION_STRING) @@ -284,85 +282,6 @@ module.exports = (isJs) => { }) }) - describe('starting and stopping', () => { - let node - - describe(`create and init a node (ctlr)`, () => { - before((done) => { - ipfsdFactory.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { - expect(err).to.not.exist() - expect(ipfsd.ctrl).to.exist() - - node = ipfsd.ctrl - done() - }) - }) - - it('should returned a node', () => { - expect(node).to.exist() - }) - - it('daemon should not be running', () => { - expect(node.daemonPid()).to.not.exist() - }) - }) - - let pid - - describe('starting', () => { - let ipfs - - before((done) => { - node.startDaemon((err, res) => { - expect(err).to.not.exist() - - pid = node.daemonPid() - ipfs = res - - // actually running? - done = once(done) - exec('kill', ['-0', pid], { cleanup: true }, () => done()) - }) - }) - - it('should be running', () => { - expect(ipfs.id).to.exist() - }) - }) - - describe('stopping', () => { - let stopped = false - - before((done) => { - node.stopDaemon((err) => { - expect(err).to.not.exist() - stopped = true - }) - - // make sure it's not still running - const poll = setInterval(() => { - exec('kill', ['-0', pid], { cleanup: true }, { - error () { - clearInterval(poll) - done() - // so it does not get called again - done = () => {} - } - }) - }, 100) - }) - - it('should be stopped', function () { - this.timeout(20 * 1000) // shutdown grace period is already 10500 - - expect(node.daemonPid()).to.not.exist() - expect(stopped).to.equal(true) - expect(fs.existsSync(path.join(node.path, 'repo.lock'))).to.not.be.ok() - expect(fs.existsSync(path.join(node.path, 'api'))).to.not.be.ok() - }) - }) - }) - describe('change config of a disposable node', () => { let node // let ipfs @@ -440,7 +359,7 @@ module.exports = (isJs) => { // skip on windows for now // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 // fails on windows see https://github.com/ipfs/js-ipfs-api/issues/408 - if (isWindows) { + if (isWindows || !isNode) { return it.skip('uses the correct ipfs-api') } @@ -482,7 +401,7 @@ module.exports = (isJs) => { }) }) - describe('startDaemon', () => { + describe('validate api', () => { it('starts the daemon and returns valid API and gateway addresses', (done) => { ipfsdFactory.spawn({ isJs, config: null }, (err, ipfsd) => { expect(err).to.not.exist() diff --git a/test/start-stop.js b/test/start-stop.js new file mode 100644 index 00000000..9bb0b7ee --- /dev/null +++ b/test/start-stop.js @@ -0,0 +1,98 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const fs = require('fs') +const once = require('once') +const path = require('path') +const exec = require('../src/exec') + +const ipfsdFactory = require('../src') + +module.exports = (isJs) => { + return () => { + describe('starting and stopping', () => { + let node + + describe(`create and init a node (ctlr)`, () => { + before((done) => { + ipfsdFactory.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { + expect(err).to.not.exist() + expect(ipfsd.ctrl).to.exist() + + node = ipfsd.ctrl + done() + }) + }) + + it('should returned a node', () => { + expect(node).to.exist() + }) + + it('daemon should not be running', () => { + expect(node.daemonPid()).to.not.exist() + }) + }) + + let pid + + describe('starting', () => { + let ipfs + + before((done) => { + node.startDaemon((err, res) => { + expect(err).to.not.exist() + + pid = node.daemonPid() + ipfs = res + + // actually running? + done = once(done) + exec('kill', ['-0', pid], { cleanup: true }, () => done()) + }) + }) + + it('should be running', () => { + expect(ipfs.id).to.exist() + }) + }) + + describe('stopping', () => { + let stopped = false + + before((done) => { + node.stopDaemon((err) => { + expect(err).to.not.exist() + stopped = true + }) + + // make sure it's not still running + const poll = setInterval(() => { + exec('kill', ['-0', pid], { cleanup: true }, { + error () { + clearInterval(poll) + done() + // so it does not get called again + done = () => {} + } + }) + }, 100) + }) + + it('should be stopped', function () { + this.timeout(20 * 1000) // shutdown grace period is already 10500 + + expect(node.daemonPid()).to.not.exist() + expect(stopped).to.equal(true) + expect(fs.existsSync(path.join(node.path, 'repo.lock'))).to.not.be.ok() + expect(fs.existsSync(path.join(node.path, 'api'))).to.not.be.ok() + }) + }) + }) + } +} From 0423702eb3b80687303318e7dd868fc4956f5fac Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 1 Dec 2017 18:03:02 -0600 Subject: [PATCH 10/85] feat: expose both, local and remote factories --- src/index.js | 75 +++----------------------------------------- src/local.js | 75 ++++++++++++++++++++++++++++++++++++++++++++ src/remote/routes.js | 2 +- test/daemon.js | 5 +-- test/start-stop.js | 3 +- 5 files changed, 86 insertions(+), 74 deletions(-) create mode 100644 src/local.js diff --git a/src/index.js b/src/index.js index 04cc08aa..b1fbbdb6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,74 +1,9 @@ 'use strict' -const merge = require('lodash.merge') -const waterfall = require('async/waterfall') +const localFactory = require('./local') +const remoteFactory = require('./remote') -const Node = require('./daemon') - -const defaultOptions = { - config: { - 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], - 'API.HTTPHeaders.Access-Control-Allow-Methods': [ - 'PUT', - 'POST', - 'GET' - ] - }, - disposable: true, - start: true, - init: true -} -/** - * Control go-ipfs nodes directly from JavaScript. - * - * @namespace IpfsDaemonController - */ -const IpfsDaemonController = { - /** - * Get the version of the currently used go-ipfs binary. - * - * @memberof IpfsDaemonController - * @param {Object} [opts={}] - * @param {function(Error, string)} callback - * @returns {undefined} - */ - version (opts, callback) { - (new Node(opts)).version(callback) - }, - - /** - * Spawn an IPFS node - * The repo is created in a temporary location and cleaned up on process exit. - * - * @memberof IpfsDaemonController - * @param {Object} [opts={}] - * @param {function(Error, {ctl: IpfsApi, ctrl: Node})} callback - * @returns {undefined} - */ - spawn (opts, callback) { - if (typeof opts === 'function') { - callback = opts - opts = defaultOptions - } - - let options = {} - merge(options, defaultOptions, opts || {}) - options.init = (typeof options.init !== 'undefined' ? options.init : true) - options.start = options.init && options.start // don't start if not initialized - - const node = new Node(options) - - waterfall([ - (cb) => options.init ? node.init(cb) : cb(null, node), - (node, cb) => options.start ? node.startDaemon(cb) : cb(null, null) - ], (err, api) => { - if (err) { - return callback(err) - } - - callback(null, { ctl: api, ctrl: node }) - }) - } +module.exports = { + localFactory, + remoteFactory } - -module.exports = IpfsDaemonController diff --git a/src/local.js b/src/local.js new file mode 100644 index 00000000..fe13bf75 --- /dev/null +++ b/src/local.js @@ -0,0 +1,75 @@ +'use strict' + +const merge = require('lodash.merge') +const waterfall = require('async/waterfall') + +const Node = require('./daemon') + +const defaultOptions = { + config: { + 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], + 'API.HTTPHeaders.Access-Control-Allow-Methods': [ + 'PUT', + 'POST', + 'GET' + ] + }, + disposable: true, + start: true, + init: true +} + +/** + * Control go-ipfs nodes directly from JavaScript. + * + * @namespace IpfsDaemonController + */ +const IpfsDaemonController = { + /** + * Get the version of the currently used go-ipfs binary. + * + * @memberof IpfsDaemonController + * @param {Object} [opts={}] + * @param {function(Error, string)} callback + * @returns {undefined} + */ + version (opts, callback) { + (new Node(opts)).version(callback) + }, + + /** + * Spawn an IPFS node + * The repo is created in a temporary location and cleaned up on process exit. + * + * @memberof IpfsDaemonController + * @param {Object} [opts={}] + * @param {function(Error, {ctl: IpfsApi, ctrl: Node})} callback + * @returns {undefined} + */ + spawn (opts, callback) { + if (typeof opts === 'function') { + callback = opts + opts = defaultOptions + } + + let options = {} + merge(options, defaultOptions, opts || {}) + options.init = (typeof options.init !== 'undefined' ? options.init : true) + options.start = options.init && options.start // don't start if not initialized + + const node = new Node(options) + + waterfall([ + (cb) => options.init ? node.init(cb) : cb(null, node), + (node, cb) => options.start ? node.startDaemon(cb) : cb(null, null) + ], (err, api) => { + if (err) { + return callback(err) + } + + callback(null, { ctl: api, ctrl: node }) + }) + } +} + +module.exports = IpfsDaemonController diff --git a/src/remote/routes.js b/src/remote/routes.js index 78180dee..56588908 100644 --- a/src/remote/routes.js +++ b/src/remote/routes.js @@ -1,6 +1,6 @@ 'use strict' -const ipfsFactory = require('../') +const ipfsFactory = require('../local') const guid = require('guid') const boom = require('boom') const utils = require('./utils') diff --git a/test/daemon.js b/test/daemon.js index 5f40fdf6..62f2678d 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -3,13 +3,14 @@ const daemon = require('./spawning') const isNode = require('detect-node') +const factory = require('../src') let ipfsdFactory if (isNode) { - ipfsdFactory = require('../src') + ipfsdFactory = factory.localFactory } else { - ipfsdFactory = require('../src/remote')() + ipfsdFactory = factory.remoteFactory() } describe('ipfsd-ctl', () => { diff --git a/test/start-stop.js b/test/start-stop.js index 9bb0b7ee..9407024a 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -12,7 +12,8 @@ const once = require('once') const path = require('path') const exec = require('../src/exec') -const ipfsdFactory = require('../src') +const factory = require('../src') +const ipfsdFactory = factory.localFactory module.exports = (isJs) => { return () => { From 0ef841372850a4bea694a81f1b396b4c18ba18ca Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Mon, 4 Dec 2017 11:24:16 -0600 Subject: [PATCH 11/85] feat: reworking exports --- package.json | 2 +- src/index.js | 9 +- src/remote/client.js | 300 ++++++++++++++++++++++++++++++++++++++++++ src/remote/index.js | 305 +------------------------------------------ src/remote/routes.js | 2 - test/daemon.js | 10 +- test/spawning.js | 22 ++-- test/start-stop.js | 2 +- 8 files changed, 329 insertions(+), 323 deletions(-) create mode 100644 src/remote/client.js diff --git a/package.json b/package.json index e573c43b..52b7c84d 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "aegir": "^12.2.0", "chai": "^4.1.2", "dirty-chai": "^2.0.1", - "ipfs": "^0.26.0", + "ipfs": "^0.27.0", "is-running": "1.0.5", "mkdirp": "^0.5.1", "multihashes": "~0.4.12", diff --git a/src/index.js b/src/index.js index b1fbbdb6..853c0aa8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,10 @@ 'use strict' -const localFactory = require('./local') -const remoteFactory = require('./remote') +const localController = require('./local') +const remote = require('./remote') module.exports = { - localFactory, - remoteFactory + localController, + remoteController: remote.remoteController, + server: remote.server } diff --git a/src/remote/client.js b/src/remote/client.js new file mode 100644 index 00000000..85aa07d9 --- /dev/null +++ b/src/remote/client.js @@ -0,0 +1,300 @@ +'use strict' + +const request = require('http') +const IpfsApi = require('ipfs-api') +const multiaddr = require('multiaddr') +const utils = require('./utils') + +const encodeParams = utils.encodeParams +const getResponse = utils.getResponse + +function _createApi (apiAddr, gwAddr) { + let api + if (apiAddr) { + api = IpfsApi(apiAddr) + api.apiHost = multiaddr(apiAddr).nodeAddress().address + api.apiPort = multiaddr(apiAddr).nodeAddress().port + } + + if (api && gwAddr) { + api.gatewayHost = multiaddr(gwAddr).nodeAddress().address + api.gatewayPort = multiaddr(gwAddr).nodeAddress().port + } + + return api +} + +const createRemoteFactory = (host, port) => { + // create a client + + if (!host) { + host = 'localhost' + } + + if (!port) { + port = 9999 + } + + class Node { + constructor (id, apiAddr, gwAddrs) { + this._id = id + this._apiAddr = multiaddr(apiAddr) + this._gwAddr = multiaddr(gwAddrs) + this.initialized = false + this.started = false + } + + /** + * Get the address of connected IPFS API. + * + * @returns {Multiaddr} + */ + get apiAddr () { + return this._apiAddr + } + + /** + * Set the address of connected IPFS API. + * + * @param {Multiaddr} addr + * @returns {void} + */ + set apiAddr (addr) { + this._apiAddr = addr + } + + /** + * Get the address of connected IPFS HTTP Gateway. + * + * @returns {Multiaddr} + */ + get gatewayAddr () { + return this._gwAddr + } + + /** + * Set the address of connected IPFS Gateway. + * + * @param {Multiaddr} addr + * @returns {void} + */ + set gatewayAddr (addr) { + this._gwAddr = addr + } + + /** + * Initialize a repo. + * + * @param {Object} [initOpts={}] + * @param {number} [initOpts.keysize=2048] - The bit size of the identiy key. + * @param {string} [initOpts.directory=IPFS_PATH] - The location of the repo. + * @param {function (Error, Node)} cb + * @returns {undefined} + */ + init (initOpts, cb) { + if (typeof initOpts === 'function') { + cb = initOpts + initOpts = {} + } + request.get({ host, port, path: `/init?${encodeParams(this._id, { initOpts })}` }, + (res) => getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + this.initialized = true + cb(null, res) + })) + } + + /** + * Delete the repo that was being used. + * If the node was marked as `disposable` this will be called + * automatically when the process is exited. + * + * @param {function(Error)} cb + * @returns {undefined} + */ + shutdown (cb) { + request.get({ host, port, path: `/shutdown` }, (res) => { getResponse(res, cb) }) + } + + /** + * Start the daemon. + * + * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. + * @param {function(Error, IpfsApi)} cb + * @returns {undefined} + */ + startDaemon (flags, cb) { + if (typeof flags === 'function') { + cb = flags + flags = {} + } + request.get({ host, port, path: `/startDaemon?${encodeParams(this._id, { flags })}` }, (res) => { + getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + this.started = true + + const apiAddr = res.data ? res.data.apiAddr : '' + const gatewayAddr = res.data ? res.data.gatewayAddr : '' + + return cb(null, _createApi(apiAddr, gatewayAddr)) + }) + }) + } + + /** + * Stop the daemon. + * + * @param {function(Error)} cb + * @returns {undefined} + */ + stopDaemon (cb) { + request.get({ host, port, path: `/stopDaemon?${encodeParams(this._id)}` }, + (res) => { + getResponse(res, (err) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null) + }) + }) + } + + /** + * Kill the `ipfs daemon` process. + * + * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent + * if the process hasn't exited yet. + * + * @param {function()} cb - Called when the process was killed. + * @returns {undefined} + */ + killProcess (cb) { + request.get({ host, port, path: `/killProcess` }, (res) => { + getResponse(res, (err) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null) + }) + }) + } + + /** + * Get the pid of the `ipfs daemon` process. + * + * @param {Function} cb + * @returns {number} + */ + daemonPid (cb) { + request.get({ host, port, path: `/daemonPid` }, (res) => { + getResponse(res, (err, data) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null, data) + }) + }) + } + + /** + * Call `ipfs config` + * + * If no `key` is passed, the whole config is returned as an object. + * + * @param {string} [key] - A specific config to retrieve. + * @param {function(Error, (Object|string))} cb + * @returns {undefined} + */ + getConfig (key, cb) { + if (typeof key === 'function') { + cb = key + key = null + } + + request.get({ host, port, path: `/getConfig?${encodeParams(this._id, { key })}` }, (res) => { + getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null, res.data) + }) + }) + } + + /** + * Set a config value. + * + * @param {string} key + * @param {string} value + * @param {function(Error)} cb + * @returns {undefined} + */ + setConfig (key, value, cb) { + request.get({ host, port, path: `/setConfig?${encodeParams(this._id, { key, value })}` }, (res) => { + getResponse(res, (err, data) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null, data) + }) + }) + } + + /** + * Replace the configuration with a given file + * + * @param {string} file - path to the new config file + * @param {function(Error)} cb + * @returns {undefined} + */ + + replaceConf (file, cb) { + cb(new Error('not implemented')) + } + } + + return { + spawn: (opts, cb) => { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + request.get({ host, port, path: `/spawn?${encodeParams(this._id, { opts })}` }, (res) => { + getResponse(res, (err, res) => { + if (err) { + return cb(err) + } + + const apiAddr = res.data ? res.data.apiAddr : '' + const gatewayAddr = res.data ? res.data.gatewayAddr : '' + + const node = new Node( + res._id, + apiAddr, + gatewayAddr) + + cb(null, { ctl: _createApi(apiAddr, gatewayAddr), ctrl: node }) + }) + }) + } + } +} + +module.exports = createRemoteFactory diff --git a/src/remote/index.js b/src/remote/index.js index 4fdcf7f0..b39aca5f 100644 --- a/src/remote/index.js +++ b/src/remote/index.js @@ -1,302 +1,9 @@ 'use strict' -const request = require('http') -const IpfsApi = require('ipfs-api') -const multiaddr = require('multiaddr') -const utils = require('./utils') +const server = require('./server') +const remoteController = require('./client') -const encodeParams = utils.encodeParams -const getResponse = utils.getResponse - -const createRemoteFactory = (host, port) => { - // create a client - - if (!host) { - host = 'localhost' - } - - if (!port) { - port = 9999 - } - - class Node { - constructor (id, apiAddr, gwAddrs) { - this._id = id - this._apiAddr = multiaddr(apiAddr) - this._gwAddr = multiaddr(gwAddrs) - this.initialized = false - this.started = false - } - - /** - * Get the address of connected IPFS API. - * - * @returns {Multiaddr} - */ - get apiAddr () { - return this._apiAddr - } - - /** - * Set the address of connected IPFS API. - * - * @param {Multiaddr} addr - * @returns {void} - */ - set apiAddr (addr) { - this._apiAddr = addr - } - - /** - * Get the address of connected IPFS HTTP Gateway. - * - * @returns {Multiaddr} - */ - get gatewayAddr () { - return this._gwAddr - } - - /** - * Set the address of connected IPFS Gateway. - * - * @param {Multiaddr} addr - * @returns {void} - */ - set gatewayAddr (addr) { - this._gwAddr = addr - } - - /** - * Initialize a repo. - * - * @param {Object} [initOpts={}] - * @param {number} [initOpts.keysize=2048] - The bit size of the identiy key. - * @param {string} [initOpts.directory=IPFS_PATH] - The location of the repo. - * @param {function (Error, Node)} cb - * @returns {undefined} - */ - init (initOpts, cb) { - if (typeof initOpts === 'function') { - cb = initOpts - initOpts = {} - } - request.get({ host, port, path: `/init?${encodeParams(this._id, { initOpts })}` }, - (res) => getResponse(res, (err, res) => { - if (err) { - return cb(err) - } - - this.initialized = true - cb(null, res) - })) - } - - /** - * Delete the repo that was being used. - * If the node was marked as `disposable` this will be called - * automatically when the process is exited. - * - * @param {function(Error)} cb - * @returns {undefined} - */ - shutdown (cb) { - request.get({ host, port, path: `/shutdown` }, (res) => { getResponse(res, cb) }) - } - - /** - * Start the daemon. - * - * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. - * @param {function(Error, IpfsApi)} cb - * @returns {undefined} - */ - startDaemon (flags, cb) { - if (typeof flags === 'function') { - cb = flags - flags = {} - } - request.get({ host, port, path: `/startDaemon?${encodeParams(this._id, { flags })}` }, (res) => { - getResponse(res, (err, res) => { - if (err) { - return cb(err) - } - - this.started = true - - const apiAddr = res.data ? res.data.apiAddr : '' - const gatewayAddr = res.data ? res.data.gatewayAddr : '' - - const api = IpfsApi(apiAddr) - api.apiHost = multiaddr(apiAddr).nodeAddress().address - api.apiPort = multiaddr(apiAddr).nodeAddress().port - api.gatewayHost = multiaddr(gatewayAddr).nodeAddress().address - api.gatewayPort = multiaddr(gatewayAddr).nodeAddress().port - - return cb(null, api) - }) - }) - } - - /** - * Stop the daemon. - * - * @param {function(Error)} cb - * @returns {undefined} - */ - stopDaemon (cb) { - request.get({ host, port, path: `/stopDaemon?${encodeParams(this._id)}` }, - (res) => { - getResponse(res, (err) => { - if (err) { - return cb(err) - } - - this.started = false - cb(null) - }) - }) - } - - /** - * Kill the `ipfs daemon` process. - * - * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent - * if the process hasn't exited yet. - * - * @param {function()} cb - Called when the process was killed. - * @returns {undefined} - */ - killProcess (cb) { - request.get({ host, port, path: `/killProcess` }, (res) => { - getResponse(res, (err) => { - if (err) { - return cb(err) - } - - this.started = false - cb(null) - }) - }) - } - - /** - * Get the pid of the `ipfs daemon` process. - * - * @param {Function} cb - * @returns {number} - */ - daemonPid (cb) { - request.get({ host, port, path: `/daemonPid` }, (res) => { - getResponse(res, (err, data) => { - if (err) { - return cb(err) - } - - this.started = false - cb(null, data) - }) - }) - } - - /** - * Call `ipfs config` - * - * If no `key` is passed, the whole config is returned as an object. - * - * @param {string} [key] - A specific config to retrieve. - * @param {function(Error, (Object|string))} cb - * @returns {undefined} - */ - getConfig (key, cb) { - if (typeof key === 'function') { - cb = key - key = null - } - - request.get({ host, port, path: `/getConfig?${encodeParams(this._id, { key })}` }, (res) => { - getResponse(res, (err, res) => { - if (err) { - return cb(err) - } - - this.started = false - cb(null, res.data) - }) - }) - } - - /** - * Set a config value. - * - * @param {string} key - * @param {string} value - * @param {function(Error)} cb - * @returns {undefined} - */ - setConfig (key, value, cb) { - request.get({ host, port, path: `/setConfig?${encodeParams(this._id, { key, value })}` }, (res) => { - getResponse(res, (err, data) => { - if (err) { - return cb(err) - } - - this.started = false - cb(null, data) - }) - }) - } - - /** - * Replace the configuration with a given file - * - * @param {string} file - path to the new config file - * @param {function(Error)} cb - * @returns {undefined} - */ - - replaceConf (file, cb) { - cb(new Error('not implemented')) - } - } - - return { - spawn: (opts, cb) => { - if (typeof opts === 'function') { - cb = opts - opts = {} - } - - request.get({ host, port, path: `/spawn?${encodeParams(this._id, { opts })}` }, (res) => { - getResponse(res, (err, res) => { - if (err) { - return cb(err) - } - - const apiAddr = res.data ? res.data.apiAddr : '' - const gatewayAddr = res.data ? res.data.gatewayAddr : '' - - const node = new Node( - res._id, - apiAddr, - gatewayAddr) - - let api - if (apiAddr) { - api = IpfsApi(apiAddr) - api.apiHost = multiaddr(apiAddr).nodeAddress().address - api.apiPort = multiaddr(apiAddr).nodeAddress().port - } - - if (gatewayAddr) { - api.gatewayHost = multiaddr(gatewayAddr).nodeAddress().address - api.gatewayPort = multiaddr(gatewayAddr).nodeAddress().port - } - - cb(null, { ctl: api, ctrl: node }) - }) - }) - } - } -} - -module.exports = createRemoteFactory +module.exports = { + server, + remoteController +} \ No newline at end of file diff --git a/src/remote/routes.js b/src/remote/routes.js index 56588908..33ce482d 100644 --- a/src/remote/routes.js +++ b/src/remote/routes.js @@ -42,8 +42,6 @@ module.exports = (server) => { /** * Get the address of connected IPFS API. - * - * @returns {Multiaddr} */ server.route({ method: 'GET', diff --git a/test/daemon.js b/test/daemon.js index 62f2678d..d9e026dd 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -5,12 +5,12 @@ const daemon = require('./spawning') const isNode = require('detect-node') const factory = require('../src') -let ipfsdFactory +let ipfsdController if (isNode) { - ipfsdFactory = factory.localFactory + ipfsdController = factory.localController } else { - ipfsdFactory = factory.remoteFactory() + ipfsdController = factory.remoteController() } describe('ipfsd-ctl', () => { @@ -22,10 +22,10 @@ describe('ipfsd-ctl', () => { })) describe('Go daemon', () => { - daemon(ipfsdFactory, false)() + daemon(ipfsdController, false)() }) describe('Js daemon', () => { - daemon(ipfsdFactory, true)() + daemon(ipfsdController, true)() }) }) diff --git a/test/spawning.js b/test/spawning.js index c84c7307..65d34c14 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -19,9 +19,9 @@ function tempDir (isJs) { return path.join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) } -module.exports = (ipfsdFactory, isJs) => { +module.exports = (ipfsdController, isJs) => { return () => { - const VERSION_STRING = isJs ? 'js-ipfs version: 0.26.0' : 'ipfs version 0.4.13' + const VERSION_STRING = isJs ? 'js-ipfs version: 0.27.0' : 'ipfs version 0.4.13' const API_PORT = isJs ? '5002' : '5001' const GW_PORT = isJs ? '9090' : '8080' @@ -29,7 +29,7 @@ module.exports = (ipfsdFactory, isJs) => { if (!isNode) { this.skip() } - ipfsdFactory.version({ isJs }, (err, version) => { + ipfsdController.version({ isJs }, (err, version) => { expect(err).to.not.exist() expect(version).to.be.eql(VERSION_STRING) done() @@ -44,7 +44,7 @@ module.exports = (ipfsdFactory, isJs) => { after((done) => node.stopDaemon(done)) it('create node', (done) => { - ipfsdFactory.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { + ipfsdController.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() @@ -123,7 +123,7 @@ module.exports = (ipfsdFactory, isJs) => { after((done) => node.stopDaemon(done)) it('create node and init', (done) => { - ipfsdFactory.spawn({ isJs, start: false, disposable: true }, (err, ipfsd) => { + ipfsdController.spawn({ isJs, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() @@ -194,7 +194,7 @@ module.exports = (ipfsdFactory, isJs) => { after((done) => node.stopDaemon(done)) it('create init and start node', (done) => { - ipfsdFactory.spawn({ isJs }, (err, ipfsd) => { + ipfsdController.spawn({ isJs }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.exist() @@ -267,7 +267,7 @@ module.exports = (ipfsdFactory, isJs) => { let node async.waterfall([ - (cb) => ipfsdFactory.spawn(options, cb), + (cb) => ipfsdController.spawn(options, cb), (ipfsd, cb) => { node = ipfsd.ctrl node.getConfig('Addresses.API', cb) @@ -287,7 +287,7 @@ module.exports = (ipfsdFactory, isJs) => { // let ipfs before((done) => { - ipfsdFactory.spawn({ isJs }, (err, res) => { + ipfsdController.spawn({ isJs }, (err, res) => { if (err) { return done(err) } @@ -343,7 +343,7 @@ module.exports = (ipfsdFactory, isJs) => { let node before((done) => { - ipfsdFactory.spawn({ start: false }, (err, ret) => { + ipfsdController.spawn({ start: false }, (err, ret) => { expect(err).to.not.exist() node = ret.ctrl node.startDaemon((err, ignore) => { @@ -403,7 +403,7 @@ module.exports = (ipfsdFactory, isJs) => { describe('validate api', () => { it('starts the daemon and returns valid API and gateway addresses', (done) => { - ipfsdFactory.spawn({ isJs, config: null }, (err, ipfsd) => { + ipfsdController.spawn({ isJs, config: null }, (err, ipfsd) => { expect(err).to.not.exist() const api = ipfsd.ctl const node = ipfsd.ctrl @@ -435,7 +435,7 @@ module.exports = (ipfsdFactory, isJs) => { if (isJs) { this.skip() } else { - ipfsdFactory.spawn({ start: false }, (err, ipfsd) => { + ipfsdController.spawn({ start: false }, (err, ipfsd) => { expect(err).to.not.exist() ipfsd.ctrl.startDaemon(['--should-not-exist'], (err) => { expect(err).to.exist() diff --git a/test/start-stop.js b/test/start-stop.js index 9407024a..b2119bfa 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -13,7 +13,7 @@ const path = require('path') const exec = require('../src/exec') const factory = require('../src') -const ipfsdFactory = factory.localFactory +const ipfsdFactory = factory.localController module.exports = (isJs) => { return () => { From 9c432d2bf807eb10eeeed9856c886205b7135d8f Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 5 Dec 2017 15:06:27 -0600 Subject: [PATCH 12/85] more cleanup --- package.json | 4 +++- src/remote/index.js | 2 +- test/spawning.js | 6 ++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 52b7c84d..3bd2495c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ }, "browser": { "./src/daemon.js": false, + "./src/exec.js": false, + "hapi": false, "glob": false, "fs": false, "stream": "readable-stream", @@ -67,7 +69,7 @@ "guid": "0.0.12", "hapi": "^16.6.2", "http-browserify": "^1.7.0", - "ipfs-api": "^17.1.3", + "ipfs-api": "^17.2.0", "lodash.clonewith": "^4.5.0", "lodash.get": "^4.4.2", "lodash.mapvalues": "^4.6.0", diff --git a/src/remote/index.js b/src/remote/index.js index b39aca5f..2449bcd6 100644 --- a/src/remote/index.js +++ b/src/remote/index.js @@ -6,4 +6,4 @@ const remoteController = require('./client') module.exports = { server, remoteController -} \ No newline at end of file +} diff --git a/test/spawning.js b/test/spawning.js index 65d34c14..e246b4d4 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -37,7 +37,7 @@ module.exports = (ipfsdController, isJs) => { }) describe('daemon spawning', () => { - describe('spawn a bare node', () => { + describe.only('spawn a bare node', () => { let node = null let api = null @@ -275,9 +275,7 @@ module.exports = (ipfsdController, isJs) => { ], (err, res) => { expect(err).to.not.exist() expect(res).to.be.eql(addr) - async.series([ - (cb) => node.stopDaemon(cb) - ], done) + node.stopDaemon(done) }) }) }) From 06c84105f0a1e9d4dc79151c82b097daab3890a0 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 6 Dec 2017 09:58:48 -0600 Subject: [PATCH 13/85] fix: mapValues -> mapvalues --- src/remote/utils.js | 2 +- test/exec.js | 2 +- test/spawning.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/remote/utils.js b/src/remote/utils.js index b3cefdf0..d71b9c62 100644 --- a/src/remote/utils.js +++ b/src/remote/utils.js @@ -1,7 +1,7 @@ 'use strict' const Qs = require('qs') -const mapValues = require('lodash.mapValues') +const mapValues = require('lodash.mapvalues') exports.getResponse = (res, cb) => { let data = '' diff --git a/test/exec.js b/test/exec.js index be6fc3c5..a0f0dc6d 100644 --- a/test/exec.js +++ b/test/exec.js @@ -57,7 +57,7 @@ function makeCheck (n, done) { // exiting as it once was when the test was designed // - [ ] Need test vector or figure out why tail changed // Ref: https://github.com/ipfs/js-ipfsd-ctl/pull/160#issuecomment-325669206 -describe.skip('exec', () => { +describe('exec', () => { it('SIGTERM kills hang', (done) => { const tok = token() diff --git a/test/spawning.js b/test/spawning.js index e246b4d4..3c2f41e9 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -37,7 +37,7 @@ module.exports = (ipfsdController, isJs) => { }) describe('daemon spawning', () => { - describe.only('spawn a bare node', () => { + describe('spawn a bare node', () => { let node = null let api = null From c8866e2a5eb00afafcac1ac5d152dc32706920fd Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 6 Dec 2017 10:05:46 -0600 Subject: [PATCH 14/85] chore: updating ci files --- .appveyor.yml | 23 ++++++++++++++--------- circle.yml | 8 ++++++++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index b3d47701..58aef650 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -1,23 +1,28 @@ +version: "{build}" + environment: matrix: - nodejs_version: "6" - nodejs_version: "8" -# cache: -# - node_modules - -platform: - - x64 +matrix: + fast_finish: true install: - - ps: Install-Product node $env:nodejs_version $env:platform + # Install Node.js + - ps: Install-Product node $env:nodejs_version + + # Upgrade npm + - npm install -g npm + + # Output our current versions for debugging - node --version - npm --version + + # Install our package dependencies - npm install test_script: - - npm test + - npm run test:node build: off - -version: "{build}" diff --git a/circle.yml b/circle.yml index 00096937..5d59fb8d 100644 --- a/circle.yml +++ b/circle.yml @@ -3,12 +3,20 @@ machine: node: version: stable +test: + post: + - npm run coverage -- --upload + dependencies: pre: - google-chrome --version - curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb + - for v in $(curl http://archive.ubuntu.com/ubuntu/pool/main/n/nss/ | grep "href=" | grep "libnss3.*deb\"" -o | grep -o "libnss3.*deb" | grep "3.28" | grep "14.04"); do curl -L -o $v http://archive.ubuntu.com/ubuntu/pool/main/n/nss/$v; done && rm libnss3-tools*_i386.deb libnss3-dev*_i386.deb - sudo dpkg -i google-chrome.deb || true + - sudo dpkg -i libnss3*.deb || true - sudo apt-get update + - sudo apt-get install -f || true + - sudo dpkg -i libnss3*.deb - sudo apt-get install -f - sudo apt-get install --only-upgrade lsb-base - sudo dpkg -i google-chrome.deb From 75856509a8bb3753abc9fb6176d48955ef8056b3 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 6 Dec 2017 11:11:07 -0600 Subject: [PATCH 15/85] test: timeouts --- package.json | 6 +++--- test/spawning.js | 51 ++++++++++++++++++++++++++++++++-------------- test/start-stop.js | 8 +++++--- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 3bd2495c..bb370fdc 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,9 @@ "scripts": { "lint": "aegir lint", "coverage": "aegir coverage", - "test": "aegir test -t node -t browser --no-cors --timeout 50000", - "test:node": "aegir test -t node --timeout 50000", - "test:browser": "aegir test -t browser --no-cors --timeout 50000", + "test": "aegir test -t node -t browser --no-cors", + "test:node": "aegir test -t node", + "test:browser": "aegir test -t browser --no-cors", "docs": "aegir docs", "release": "aegir release -t node", "release-minor": "aegir release --type minor -t node", diff --git a/test/spawning.js b/test/spawning.js index 3c2f41e9..b41b1cd7 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -41,7 +41,10 @@ module.exports = (ipfsdController, isJs) => { let node = null let api = null - after((done) => node.stopDaemon(done)) + after(function (done) { + this.timeout(20 * 1000) + node.stopDaemon(done) + }) it('create node', (done) => { ipfsdController.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { @@ -53,7 +56,8 @@ module.exports = (ipfsdController, isJs) => { }) }) - it('init node', (done) => { + it('init node', function (done) { + this.timeout(20 * 1000) node.init((err) => { expect(err).to.not.exist() expect(node.initialized).to.be.ok() @@ -61,7 +65,8 @@ module.exports = (ipfsdController, isJs) => { }) }) - it('start node', (done) => { + it('start node', function (done) { + this.timeout(30 * 1000) node.startDaemon((err, a) => { api = a expect(err).to.not.exist() @@ -76,7 +81,8 @@ module.exports = (ipfsdController, isJs) => { let store let retrieve - before((done) => { + before(function (done) { + this.timeout(30 * 1000) async.waterfall([ (cb) => api.block.put(blorb, cb), (block, cb) => { @@ -120,9 +126,13 @@ module.exports = (ipfsdController, isJs) => { let node = null let api = null - after((done) => node.stopDaemon(done)) + after(function (done) { + this.timeout(20 * 1000) + node.stopDaemon(done) + }) - it('create node and init', (done) => { + it('create node and init', function (done) { + this.timeout(30 * 1000) ipfsdController.spawn({ isJs, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() @@ -132,7 +142,8 @@ module.exports = (ipfsdController, isJs) => { }) }) - it('start node', (done) => { + it('start node', function (done) { + this.timeout(30 * 1000) node.startDaemon((err, a) => { api = a expect(err).to.not.exist() @@ -147,7 +158,8 @@ module.exports = (ipfsdController, isJs) => { let store let retrieve - before((done) => { + before(function (done) { + this.timeout(20 * 1000) async.waterfall([ (cb) => api.block.put(blorb, cb), (block, cb) => { @@ -191,9 +203,13 @@ module.exports = (ipfsdController, isJs) => { let node = null let api = null - after((done) => node.stopDaemon(done)) + after(function (done) { + this.timeout(20 * 1000) + node.stopDaemon(done) + }) - it('create init and start node', (done) => { + it('create init and start node', function (done) { + this.timeout(20 * 1000) ipfsdController.spawn({ isJs }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() @@ -210,7 +226,8 @@ module.exports = (ipfsdController, isJs) => { let store let retrieve - before((done) => { + before(function (done) { + this.timeout(20 * 1000) async.waterfall([ (cb) => api.block.put(blorb, cb), (block, cb) => { @@ -257,7 +274,8 @@ module.exports = (ipfsdController, isJs) => { 'Addresses.API': addr } - it('allows passing ipfs config options to spawn', (done) => { + it('allows passing ipfs config options to spawn', function (done) { + this.timeout(20 * 1000) const options = { config, repoPath, @@ -284,7 +302,8 @@ module.exports = (ipfsdController, isJs) => { let node // let ipfs - before((done) => { + before(function (done) { + this.timeout(20 * 1000) ipfsdController.spawn({ isJs }, (err, res) => { if (err) { return done(err) @@ -340,7 +359,8 @@ module.exports = (ipfsdController, isJs) => { let ipfs let node - before((done) => { + before(function (done) { + this.timeout(20 * 1000) ipfsdController.spawn({ start: false }, (err, ret) => { expect(err).to.not.exist() node = ret.ctrl @@ -400,7 +420,8 @@ module.exports = (ipfsdController, isJs) => { }) describe('validate api', () => { - it('starts the daemon and returns valid API and gateway addresses', (done) => { + it('starts the daemon and returns valid API and gateway addresses', function (done) { + this.timeout(20 * 1000) ipfsdController.spawn({ isJs, config: null }, (err, ipfsd) => { expect(err).to.not.exist() const api = ipfsd.ctl diff --git a/test/start-stop.js b/test/start-stop.js index b2119bfa..0b2c3d6c 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -20,7 +20,8 @@ module.exports = (isJs) => { describe('starting and stopping', () => { let node - describe(`create and init a node (ctlr)`, () => { + describe(`create and init a node (ctlr)`, function () { + this.timeout(20 * 1000) before((done) => { ipfsdFactory.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() @@ -45,7 +46,8 @@ module.exports = (isJs) => { describe('starting', () => { let ipfs - before((done) => { + before(function (done) { + this.timeout(20 * 1000) node.startDaemon((err, res) => { expect(err).to.not.exist() @@ -86,7 +88,7 @@ module.exports = (isJs) => { }) it('should be stopped', function () { - this.timeout(20 * 1000) // shutdown grace period is already 10500 + this.timeout(30 * 1000) // shutdown grace period is already 10500 expect(node.daemonPid()).to.not.exist() expect(stopped).to.equal(true) From 373d5b8f244c42b8e74102e223e58d77a00e0d6d Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 6 Dec 2017 11:50:01 -0600 Subject: [PATCH 16/85] feat: enable cors in hapi --- package.json | 2 +- src/remote/server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bb370fdc..3b2d09b9 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.js", "scripts": { "lint": "aegir lint", - "coverage": "aegir coverage", + "coverage": "aegir coverage --timeout 50000", "test": "aegir test -t node -t browser --no-cors", "test:node": "aegir test -t node", "test:browser": "aegir test -t browser --no-cors", diff --git a/src/remote/server.js b/src/remote/server.js index 432f5a2b..61f9e052 100644 --- a/src/remote/server.js +++ b/src/remote/server.js @@ -19,7 +19,7 @@ exports.start = function start (port, host, cb) { host = host || 'localhost' server = new Hapi.Server() - server.connection({ port, host }) + server.connection({ port, host, routes: { cors: true } }) routes(server) server.start(cb) From 5d0adc30c8c3879a71d1b3e95b722d153a170995 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 6 Dec 2017 12:04:00 -0600 Subject: [PATCH 17/85] updated comment --- test/exec.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/exec.js b/test/exec.js index a0f0dc6d..0a292ee9 100644 --- a/test/exec.js +++ b/test/exec.js @@ -57,6 +57,8 @@ function makeCheck (n, done) { // exiting as it once was when the test was designed // - [ ] Need test vector or figure out why tail changed // Ref: https://github.com/ipfs/js-ipfsd-ctl/pull/160#issuecomment-325669206 +// UPDATE: 12/06/2017 - `tail` seems to work fine on all ci systems. +// I'm leaving it enabled for now. This does need a different approach for windows though. describe('exec', () => { it('SIGTERM kills hang', (done) => { const tok = token() From aa475d8a29e9774763a5b469d1530e8abb4465d7 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 6 Dec 2017 12:34:59 -0600 Subject: [PATCH 18/85] fix: run node and browser tests in appveyor --- .appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.appveyor.yml b/.appveyor.yml index 58aef650..a2b02e3e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -23,6 +23,6 @@ install: - npm install test_script: - - npm run test:node + - npm run test build: off From c408f5772e0687a23983ecc1ab8e4b62c6ffadd5 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 6 Dec 2017 22:54:06 -0600 Subject: [PATCH 19/85] fix: query string handling --- src/daemon.js | 6 +----- src/remote/utils.js | 6 +++++- test/spawning.js | 23 +++++++++++++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/daemon.js b/src/daemon.js index 4d94d259..37c99ee3 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -374,11 +374,7 @@ class Node { * @returns {undefined} */ setConfig (key, value, callback) { - this._run( - ['config', key, value, '--json'], - { env: this.env }, - callback - ) + setConfigValue(this, key, value, callback) } /** diff --git a/src/remote/utils.js b/src/remote/utils.js index d71b9c62..2a5fab9a 100644 --- a/src/remote/utils.js +++ b/src/remote/utils.js @@ -22,7 +22,7 @@ exports.getResponse = (res, cb) => { } exports.encodeParams = (id, params) => { - return Qs.stringify({ id, params }) + return Qs.stringify({ id, params }, { arrayFormat: 'brackets' }) } exports.makeResponse = (id, data) => { @@ -36,6 +36,10 @@ exports.parseQuery = (query) => { // hence the use of mapValues let parsed = Qs.parse(query) const transformer = (val) => { + if (Array.isArray(val)) { + return val + } + if (typeof val === 'object') { return mapValues(val, transformer) } diff --git a/test/spawning.js b/test/spawning.js index b41b1cd7..432b65fb 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -270,7 +270,10 @@ module.exports = (ipfsdController, isJs) => { describe('spawn a node and pass init options', () => { const repoPath = tempDir(isJs) const addr = '/ip4/127.0.0.1/tcp/5678' + const swarmAddr1 = '/ip4/127.0.0.1/tcp/35555/ws' + const swarmAddr2 = '/ip4/127.0.0.1/tcp/35666' const config = { + 'Addresses.Swarm': [swarmAddr1, swarmAddr2], 'Addresses.API': addr } @@ -288,11 +291,21 @@ module.exports = (ipfsdController, isJs) => { (cb) => ipfsdController.spawn(options, cb), (ipfsd, cb) => { node = ipfsd.ctrl - node.getConfig('Addresses.API', cb) + node.getConfig('Addresses.API', (err, res) => { + expect(err).to.not.exist() + expect(res).to.be.eql(addr) + cb() + }) + }, + (cb) => { + node.getConfig('Addresses.Swarm', (err, res) => { + expect(err).to.not.exist() + expect(JSON.parse(res)).to.deep.eql([swarmAddr1, swarmAddr2]) + cb() + }) } - ], (err, res) => { + ], (err) => { expect(err).to.not.exist() - expect(res).to.be.eql(addr) node.stopDaemon(done) }) }) @@ -300,7 +313,6 @@ module.exports = (ipfsdController, isJs) => { describe('change config of a disposable node', () => { let node - // let ipfs before(function (done) { this.timeout(20 * 1000) @@ -309,7 +321,6 @@ module.exports = (ipfsdController, isJs) => { return done(err) } node = res.ctrl - // ipfs = res.ctl done() }) }) @@ -332,7 +343,7 @@ module.exports = (ipfsdController, isJs) => { }) }) - it.skip('Should set a config value', (done) => { + it('Should set a config value', (done) => { async.series([ (cb) => node.setConfig('Bootstrap', 'null', cb), (cb) => node.getConfig('Bootstrap', cb) From 5e4e69a146ca7e043828768f5d07a1425bfac29d Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 12:30:56 -0600 Subject: [PATCH 20/85] feat: use POST for spawn --- src/remote/client.js | 14 +++++++++++++- src/remote/routes.js | 7 +++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/remote/client.js b/src/remote/client.js index 85aa07d9..ee6db4ac 100644 --- a/src/remote/client.js +++ b/src/remote/client.js @@ -276,7 +276,16 @@ const createRemoteFactory = (host, port) => { opts = {} } - request.get({ host, port, path: `/spawn?${encodeParams(this._id, { opts })}` }, (res) => { + opts = opts || {} + const req = request.request({ + host, + port, + method: 'POST', + path: `/spawn`, + headers: { + 'Content-Type': 'application/json' + } + }, (res) => { getResponse(res, (err, res) => { if (err) { return cb(err) @@ -293,6 +302,9 @@ const createRemoteFactory = (host, port) => { cb(null, { ctl: _createApi(apiAddr, gatewayAddr), ctrl: node }) }) }) + + req.write(JSON.stringify({ opts })) + req.end() } } } diff --git a/src/remote/routes.js b/src/remote/routes.js index 33ce482d..236a0d4a 100644 --- a/src/remote/routes.js +++ b/src/remote/routes.js @@ -15,12 +15,11 @@ module.exports = (server) => { * The repo is created in a temporary location and cleaned up on process exit. **/ server.route({ - method: 'GET', + method: 'POST', path: '/spawn', handler: (request, reply) => { - const qr = parseQuery(request.query) - const opts = qr.params.opts || {} - ipfsFactory.spawn(opts, (err, ipfsd) => { + const payload = request.payload || {} + ipfsFactory.spawn(payload.opts, (err, ipfsd) => { if (err) { return reply(boom.badRequest(err)) } From 3b82182412908549b60adf8c82680e94140ae541 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 13:49:34 -0600 Subject: [PATCH 21/85] feat: move shared tests to its own file --- test/add-retrive.js | 55 +++++++++++++ test/spawning.js | 189 ++++++++------------------------------------ 2 files changed, 86 insertions(+), 158 deletions(-) create mode 100644 test/add-retrive.js diff --git a/test/add-retrive.js b/test/add-retrive.js new file mode 100644 index 00000000..2fb48691 --- /dev/null +++ b/test/add-retrive.js @@ -0,0 +1,55 @@ +/* eslint-env mocha */ +'use strict' + +const async = require('async') +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +module.exports = () => { + describe('should add and retrieve content', function () { + const blorb = Buffer.from('blorb') + let store + let retrieve + + before(function (done) { + this.timeout(30 * 1000) + async.waterfall([ + (cb) => this.api.block.put(blorb, cb), + (block, cb) => { + store = block.cid.toBaseEncodedString() + this.api.block.get(store, cb) + }, + (_block, cb) => { + retrieve = _block.data + cb() + } + ], done) + }) + + it('should be able to store objects', () => { + expect(store) + .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.be.eql('blorb') + }) + + it('should have started the daemon and returned an api with host/port', function () { + expect(this.api).to.have.property('id') + expect(this.api).to.have.property('apiHost') + expect(this.api).to.have.property('apiPort') + }) + + it('should be able to store objects', () => { + expect(store) + .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + }) + + it('should be able to retrieve objects', () => { + expect(retrieve.toString()).to.equal('blorb') + }) + }) +} diff --git a/test/spawning.js b/test/spawning.js index 432b65fb..717a8d1f 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -15,6 +15,8 @@ const os = require('os') const isNode = require('detect-node') const isWindows = os.platform() === 'win32' +const addRetrieveTests = require('./add-retrive') + function tempDir (isJs) { return path.join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) } @@ -37,98 +39,55 @@ module.exports = (ipfsdController, isJs) => { }) describe('daemon spawning', () => { - describe('spawn a bare node', () => { - let node = null - let api = null + describe('spawn a bare node', function () { + this.node = null + this.api = null after(function (done) { this.timeout(20 * 1000) - node.stopDaemon(done) + this.node.stopDaemon(done) }) - it('create node', (done) => { + it('create node', function (done) { ipfsdController.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() - node = ipfsd.ctrl + this.node = ipfsd.ctrl done() }) }) it('init node', function (done) { this.timeout(20 * 1000) - node.init((err) => { + this.node.init((err) => { expect(err).to.not.exist() - expect(node.initialized).to.be.ok() + expect(this.node.initialized).to.be.ok() done() }) }) it('start node', function (done) { this.timeout(30 * 1000) - node.startDaemon((err, a) => { - api = a + this.node.startDaemon((err, a) => { + this.api = a expect(err).to.not.exist() - expect(api).to.exist() - expect(api.id).to.exist() + expect(this.api).to.exist() + expect(this.api.id).to.exist() done() }) }) - describe('should add and retrieve content', () => { - const blorb = Buffer.from('blorb') - let store - let retrieve - - before(function (done) { - this.timeout(30 * 1000) - async.waterfall([ - (cb) => api.block.put(blorb, cb), - (block, cb) => { - store = block.cid.toBaseEncodedString() - api.block.get(store, cb) - }, - (_block, cb) => { - retrieve = _block.data - cb() - } - ], done) - }) - - it('should be able to store objects', () => { - expect(store) - .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.be.eql('blorb') - }) - - it('should have started the daemon and returned an api with host/port', () => { - expect(api).to.have.property('id') - expect(api).to.have.property('apiHost') - expect(api).to.have.property('apiPort') - }) - - it('should be able to store objects', () => { - expect(store) - .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.equal('blorb') - }) - }) + addRetrieveTests() }) - describe('spawn an initialized node', () => { - let node = null - let api = null + describe('spawn an initialized node', function () { + this.node = null + this.api = null after(function (done) { this.timeout(20 * 1000) - node.stopDaemon(done) + this.node.stopDaemon(done) }) it('create node and init', function (done) { @@ -137,75 +96,32 @@ module.exports = (ipfsdController, isJs) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() - node = ipfsd.ctrl + this.node = ipfsd.ctrl done() }) }) it('start node', function (done) { this.timeout(30 * 1000) - node.startDaemon((err, a) => { - api = a + this.node.startDaemon((err, a) => { + this.api = a expect(err).to.not.exist() - expect(api).to.exist() - expect(api.id).to.exist() + expect(this.api).to.exist() + expect(this.api.id).to.exist() done() }) }) - describe('should add and retrieve content', () => { - const blorb = Buffer.from('blorb') - let store - let retrieve - - before(function (done) { - this.timeout(20 * 1000) - async.waterfall([ - (cb) => api.block.put(blorb, cb), - (block, cb) => { - store = block.cid.toBaseEncodedString() - api.block.get(store, cb) - }, - (_block, cb) => { - retrieve = _block.data - cb() - } - ], done) - }) - - it('should be able to store objects', () => { - expect(store) - .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.be.eql('blorb') - }) - - it('should have started the daemon and returned an api with host/port', () => { - expect(api).to.have.property('id') - expect(api).to.have.property('apiHost') - expect(api).to.have.property('apiPort') - }) - - it('should be able to store objects', () => { - expect(store) - .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.equal('blorb') - }) - }) + addRetrieveTests() }) describe('spawn a node and attach api', () => { - let node = null - let api = null + this.node = null + this.api = null after(function (done) { this.timeout(20 * 1000) - node.stopDaemon(done) + this.node.stopDaemon(done) }) it('create init and start node', function (done) { @@ -215,56 +131,13 @@ module.exports = (ipfsdController, isJs) => { expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.exist() expect(ipfsd.ctl.id).to.exist() - node = ipfsd.ctrl - api = ipfsd.ctl + this.node = ipfsd.ctrl + this.api = ipfsd.ctl done() }) }) - describe('should add and retrieve content', () => { - const blorb = Buffer.from('blorb') - let store - let retrieve - - before(function (done) { - this.timeout(20 * 1000) - async.waterfall([ - (cb) => api.block.put(blorb, cb), - (block, cb) => { - store = block.cid.toBaseEncodedString() - api.block.get(store, cb) - }, - (_block, cb) => { - retrieve = _block.data - cb() - } - ], done) - }) - - it('should be able to store objects', () => { - expect(store) - .to.eql('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.be.eql('blorb') - }) - - it('should have started the daemon and returned an api with host/port', () => { - expect(api).to.have.property('id') - expect(api).to.have.property('apiHost') - expect(api).to.have.property('apiPort') - }) - - it('should be able to store objects', () => { - expect(store) - .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.equal('blorb') - }) - }) + addRetrieveTests() }) describe('spawn a node and pass init options', () => { From da2ef0764d7a4b4d906c9071e8fa20d618ee880b Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 15:17:07 -0600 Subject: [PATCH 22/85] test: skip tests untill js-ipfs pr 1134 is merged --- src/remote/client.js | 6 +++--- test/spawning.js | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/remote/client.js b/src/remote/client.js index ee6db4ac..e7422f67 100644 --- a/src/remote/client.js +++ b/src/remote/client.js @@ -8,7 +8,7 @@ const utils = require('./utils') const encodeParams = utils.encodeParams const getResponse = utils.getResponse -function _createApi (apiAddr, gwAddr) { +function createApi (apiAddr, gwAddr) { let api if (apiAddr) { api = IpfsApi(apiAddr) @@ -142,7 +142,7 @@ const createRemoteFactory = (host, port) => { const apiAddr = res.data ? res.data.apiAddr : '' const gatewayAddr = res.data ? res.data.gatewayAddr : '' - return cb(null, _createApi(apiAddr, gatewayAddr)) + return cb(null, createApi(apiAddr, gatewayAddr)) }) }) } @@ -299,7 +299,7 @@ const createRemoteFactory = (host, port) => { apiAddr, gatewayAddr) - cb(null, { ctl: _createApi(apiAddr, gatewayAddr), ctrl: node }) + cb(null, { ctl: createApi(apiAddr, gatewayAddr), ctrl: node }) }) }) diff --git a/test/spawning.js b/test/spawning.js index 717a8d1f..e5f18c82 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -216,7 +216,8 @@ module.exports = (ipfsdController, isJs) => { }) }) - it('Should set a config value', (done) => { + // TODO: skip until https://github.com/ipfs/js-ipfs/pull/1134 is merged + it.skip('Should set a config value', (done) => { async.series([ (cb) => node.setConfig('Bootstrap', 'null', cb), (cb) => node.getConfig('Bootstrap', cb) From a10b2c570aeb1ba166d3b8d7129c7612d550ced7 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 15:53:06 -0600 Subject: [PATCH 23/85] docs: updating readme --- README.md | 78 +++++++++++++++++++++++++++++++++++++++++--- src/local.js | 18 ++++++---- src/remote/server.js | 1 + 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index d8859fa5..271763ed 100644 --- a/README.md +++ b/README.md @@ -30,23 +30,93 @@ npm install --save ipfsd-ctl IPFS daemons are already easy to start and stop, but this module is here to do it from JavaScript itself. +### Local node + ```js // Start a disposable node, and get access to the api -// print the node id, and kill the temporary daemon +// print the node id, and stop the temporary daemon // IPFS_PATH will point to /tmp/ipfs_***** and will be // cleaned up when the process exits. -var ipfsd = require('ipfsd-ctl') +const factory = require('ipfsd-ctl') +const localController = factory.localController -ipfsd.disposableApi(function (err, ipfs) { +localController.spawn(function (err, ipfsd) { + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl ipfs.id(function (err, id) { console.log(id) - process.exit() + node.stopDaemon() }) }) ``` +### Remote node + +```js +// Start a remote disposable node, and get access to the api +// print the node id, and stop the temporary daemon + +// IPFS_PATH will point to /tmp/ipfs_***** and will be +// cleaned up when the process exits. + +const ipfsd = require('ipfsd-ctl') +const server = ipfsd.server + +server.start((err) => { + if (err) { + throw err + } + + const remoteController = ipfsd.remoteController(port || 9999) + remoteController.spawn(function (err, controller) { + const ipfs = controller.ctl + const node = controller.ctrl + ipfs.id(function (err, id) { + console.log(id) + node.stopDaemon() + server.stop() + }) + }) +}) +``` + +It's also possible to start the server from `.aegir` per and post hooks. For reference take a look at the `.aegir` file in this repository. + + +## API + +### Create factory + +- `localController` - create a local controller +- `remoteController([port])` - create a remote controller, usable from browsers +- `server` - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. + +Both of this methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes + +### Spawn nodes + +```js + /** + Spawn an IPFS node, either js-ipfs or go-ipfs + + @param {Object} [options={}] - various config options and ipfs config parameters (see valid options below) + @param {Function} cb(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` + */ + spawn(options, cb) +``` + +Where `options` is: + +- `js` bool - spawn a js or go node (default go) +- `init` bool - should the node be initialized +- `start` bool - should the node be started +- `repoPath` string - the repository path to use for this node, ignored if node is disposable +- `disposable` bool - a new repo is created and initialized for each invocation +- `config` - ipfs configuration options + + If you need want to use an existing ipfs installation you can set `$IPFS_EXEC=/path/to/ipfs` to ensure it uses that. For more details see https://ipfs.github.io/js-ipfsd-ctl/. diff --git a/src/local.js b/src/local.js index fe13bf75..9c7a90a5 100644 --- a/src/local.js +++ b/src/local.js @@ -38,13 +38,19 @@ const IpfsDaemonController = { }, /** - * Spawn an IPFS node - * The repo is created in a temporary location and cleaned up on process exit. + * Spawn an IPFS node, either js-ipfs or go-ipfs * - * @memberof IpfsDaemonController - * @param {Object} [opts={}] - * @param {function(Error, {ctl: IpfsApi, ctrl: Node})} callback - * @returns {undefined} + * Options are: + * - `js` bool - spawn a js or go node (default go) + * - `init` bool - should the node be initialized + * - `start` bool - should the node be started + * - `repoPath` string - the repository path to use for this node, ignored if node is disposable + * - `disposable` bool - a new repo is created and initialized for each invocation + * - `config` - ipfs configuration options + * + * @param {Object} [opts={}] - various config options and ipfs config parameters + * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` + * @return {undefined} */ spawn (opts, callback) { if (typeof opts === 'function') { diff --git a/src/remote/server.js b/src/remote/server.js index 61f9e052..c43e169f 100644 --- a/src/remote/server.js +++ b/src/remote/server.js @@ -26,5 +26,6 @@ exports.start = function start (port, host, cb) { } exports.stop = function stop (cb) { + cb = cb || (() => {}) server.stop(cb) } From e766c22b22025840d4cfe35b8b35d70aa7b4f4bf Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 16:04:32 -0600 Subject: [PATCH 24/85] docs: typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 271763ed..f1b03253 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ server.start((err) => { }) ``` -It's also possible to start the server from `.aegir` per and post hooks. For reference take a look at the `.aegir` file in this repository. +It's also possible to start the server from `.aegir` `pre` and `post` hooks. For reference take a look at the `.aegir` file in this repository. ## API From 259ad3d1ee9b2c32717ceca3a9b4ab8926f522f6 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 16:06:24 -0600 Subject: [PATCH 25/85] docs: small change to readme --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f1b03253..963774f6 100644 --- a/README.md +++ b/README.md @@ -89,12 +89,11 @@ It's also possible to start the server from `.aegir` `pre` and `post` hooks. For ### Create factory -- `localController` - create a local controller -- `remoteController([port])` - create a remote controller, usable from browsers +- This methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes + - `localController` - create a local controller + - `remoteController([port])` - create a remote controller, usable from browsers - `server` - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. -Both of this methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes - ### Spawn nodes ```js From 279f71a29d98af46df2f9afb7d54ca1539db1a5a Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 16:09:06 -0600 Subject: [PATCH 26/85] docs: small change to readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 963774f6..051e7f9b 100644 --- a/README.md +++ b/README.md @@ -108,9 +108,9 @@ It's also possible to start the server from `.aegir` `pre` and `post` hooks. For Where `options` is: -- `js` bool - spawn a js or go node (default go) -- `init` bool - should the node be initialized -- `start` bool - should the node be started +- `js` bool (default false) - spawn a js or go node (default go) +- `init` bool (default true) - should the node be initialized +- `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable - `disposable` bool - a new repo is created and initialized for each invocation - `config` - ipfs configuration options From d60f288de3f2ddaeb238b3e3e6befefe9998ce81 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 16:30:19 -0600 Subject: [PATCH 27/85] chore: updating js-ipfs version --- test/spawning.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spawning.js b/test/spawning.js index e5f18c82..1dc78c3a 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -23,7 +23,7 @@ function tempDir (isJs) { module.exports = (ipfsdController, isJs) => { return () => { - const VERSION_STRING = isJs ? 'js-ipfs version: 0.27.0' : 'ipfs version 0.4.13' + const VERSION_STRING = isJs ? 'js-ipfs version: 0.27.1' : 'ipfs version 0.4.13' const API_PORT = isJs ? '5002' : '5001' const GW_PORT = isJs ? '9090' : '8080' From 50e2b981365ab74114db88372b207b68e5facc45 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 16:43:54 -0600 Subject: [PATCH 28/85] wip: examples --- examples/disposableApi.js | 18 ------------------ examples/id.js | 17 ++++++++++------- examples/local-disposable.js | 33 +++++++++++++++++++++++++++++++++ examples/local.js | 18 ++++++++++++------ examples/remote-disposable.js | 27 +++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 31 deletions(-) delete mode 100644 examples/disposableApi.js create mode 100644 examples/local-disposable.js create mode 100644 examples/remote-disposable.js diff --git a/examples/disposableApi.js b/examples/disposableApi.js deleted file mode 100644 index 0889d698..00000000 --- a/examples/disposableApi.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint no-console: 0 */ -'use strict' - -// Start a disposable node, and get access to the api -// print the node id - -// IPFS_PATH will point to /tmp/ipfs_***** and will be -// cleaned up when the process exits. - -const ipfsd = require('../') - -ipfsd.disposableApi((err, ipfs) => { - if (err) throw err - ipfs.id((err, id) => { - if (err) throw err - console.log(id) - }) -}) diff --git a/examples/id.js b/examples/id.js index 68c61b59..8846c6f6 100644 --- a/examples/id.js +++ b/examples/id.js @@ -1,22 +1,25 @@ /* eslint no-console: 0 */ 'use strict' -var ipfsd = require('../') +const factory = require('../') +const localController = factory.localController -ipfsd.disposableApi(function (err, ipfs) { - if (err) throw err +localController.spawn(function (err, ipfsd) { + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl ipfs.id(function (err, id) { - if (err) throw err console.log('alice') console.log(id) + node.stopDaemon() }) }) -ipfsd.disposableApi(function (err, ipfs) { - if (err) throw err +localController.spawn(function (err, ipfsd) { + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl ipfs.id(function (err, id) { - if (err) throw err console.log('bob') console.log(id) + node.stopDaemon() }) }) diff --git a/examples/local-disposable.js b/examples/local-disposable.js new file mode 100644 index 00000000..533bc52e --- /dev/null +++ b/examples/local-disposable.js @@ -0,0 +1,33 @@ +/* eslint no-console: 0 */ +'use strict' + +// Start a disposable node, and get access to the api +// print the node id + +// IPFS_PATH will point to /tmp/ipfs_***** and will be +// cleaned up when the process exits. + +const factory = require('../') +const localController = factory.localController + +// start a go daemon +localController.spawn((err, ipfsd) => { + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl + ipfs.id(function (err, id) { + console.log('go-ipfs') + console.log(id) + node.stopDaemon() + }) +}) + +// start a js daemon +localController.spawn({ isJs: true }, (err, ipfsd) => { + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl + ipfs.id(function (err, id) { + console.log('js-ipfs') + console.log(id) + node.stopDaemon() + }) +}) diff --git a/examples/local.js b/examples/local.js index 79cdfe97..e56584ce 100644 --- a/examples/local.js +++ b/examples/local.js @@ -1,12 +1,18 @@ /* eslint no-console: 0 */ 'use strict' -var ipfsd = require('../') +const factory = require('../') +const localController = factory.localController // opens an api connection to local running ipfs node -ipfsd.local(function (err, ipfs) { - if (err) throw err - - console.log(ipfs) -}) +localController.spawn({ disposable: false }, (err, ipfsd) => { + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl + ipfs.id(function (err, id) { + console.log('go-ipfs') + console.log(id) + node.stopDaemon() + }) + } +) diff --git a/examples/remote-disposable.js b/examples/remote-disposable.js new file mode 100644 index 00000000..b9b3fb21 --- /dev/null +++ b/examples/remote-disposable.js @@ -0,0 +1,27 @@ +/* eslint no-console: 0 */ +'use strict' + +// Start a remote disposable node, and get access to the api +// print the node id, and stop the temporary daemon + +// IPFS_PATH will point to /tmp/ipfs_***** and will be +// cleaned up when the process exits. + +const ipfsd = require('../') +const server = ipfsd.server + +server.start((err) => { + if (err) { + throw err + } + + const remoteController = ipfsd.remoteController() + remoteController.spawn(function (err, controller) { + const ipfs = controller.ctl + const node = controller.ctrl + ipfs.id(function (err, id) { + console.log(id) + node.stopDaemon(() => server.stop()) + }) + }) +}) \ No newline at end of file From 067dbc7d5b141c6f48315b505948e962dfc60661 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 17:27:14 -0600 Subject: [PATCH 29/85] feat: add support for local node and fix examples --- examples/electron-asar/app.js | 6 ++-- examples/electron-asar/package.json | 2 +- examples/id.js | 14 +++++++++ examples/local-disposable.js | 16 ++++++++++ examples/local.js | 45 ++++++++++++++++++++++------- examples/remote-disposable.js | 10 ++++++- src/local.js | 8 +++++ 7 files changed, 87 insertions(+), 14 deletions(-) diff --git a/examples/electron-asar/app.js b/examples/electron-asar/app.js index 61fbb99c..95b64c4b 100644 --- a/examples/electron-asar/app.js +++ b/examples/electron-asar/app.js @@ -2,7 +2,7 @@ 'use strict' const { app, ipcMain, BrowserWindow } = require('electron') -const ipfsd = require('ipfsd-ctl') +const ipfsd = require('../..') app.on('ready', () => { const win = new BrowserWindow({ @@ -15,11 +15,13 @@ ipcMain.on('start', ({ sender }) => { console.log('starting disposable IPFS') sender.send('message', 'starting disposable IPFS') - ipfsd.disposableApi((err, ipfs) => { + ipfsd.localController.spawn((err, ipfsd) => { if (err) { sender.send('error', err) throw err } + + const ipfs = ipfsd.ctl console.log('get id') sender.send('message', 'get id') ipfs.id(function (err, id) { diff --git a/examples/electron-asar/package.json b/examples/electron-asar/package.json index 5be4c215..5c48c77e 100644 --- a/examples/electron-asar/package.json +++ b/examples/electron-asar/package.json @@ -3,7 +3,7 @@ "private": true, "main": "./app.js", "dependencies": { - "ipfsd-ctl": "*" + "ipfsd-ctl": "file:../.." }, "devDependencies": { "electron": "^1.7.6", diff --git a/examples/id.js b/examples/id.js index 8846c6f6..f9ffaf1d 100644 --- a/examples/id.js +++ b/examples/id.js @@ -5,9 +5,16 @@ const factory = require('../') const localController = factory.localController localController.spawn(function (err, ipfsd) { + if (err) { + throw err + } + const ipfs = ipfsd.ctl const node = ipfsd.ctrl ipfs.id(function (err, id) { + if (err) { + throw err + } console.log('alice') console.log(id) node.stopDaemon() @@ -15,9 +22,16 @@ localController.spawn(function (err, ipfsd) { }) localController.spawn(function (err, ipfsd) { + if (err) { + throw err + } + const ipfs = ipfsd.ctl const node = ipfsd.ctrl ipfs.id(function (err, id) { + if (err) { + throw err + } console.log('bob') console.log(id) node.stopDaemon() diff --git a/examples/local-disposable.js b/examples/local-disposable.js index 533bc52e..dd255615 100644 --- a/examples/local-disposable.js +++ b/examples/local-disposable.js @@ -12,9 +12,17 @@ const localController = factory.localController // start a go daemon localController.spawn((err, ipfsd) => { + if (err) { + throw err + } + const ipfs = ipfsd.ctl const node = ipfsd.ctrl ipfs.id(function (err, id) { + if (err) { + throw err + } + console.log('go-ipfs') console.log(id) node.stopDaemon() @@ -23,9 +31,17 @@ localController.spawn((err, ipfsd) => { // start a js daemon localController.spawn({ isJs: true }, (err, ipfsd) => { + if (err) { + throw err + } + const ipfs = ipfsd.ctl const node = ipfsd.ctrl ipfs.id(function (err, id) { + if (err) { + throw err + } + console.log('js-ipfs') console.log(id) node.stopDaemon() diff --git a/examples/local.js b/examples/local.js index e56584ce..d608ad41 100644 --- a/examples/local.js +++ b/examples/local.js @@ -4,15 +4,40 @@ const factory = require('../') const localController = factory.localController -// opens an api connection to local running ipfs node - +// opens an api connection to local running go-ipfs node localController.spawn({ disposable: false }, (err, ipfsd) => { - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { - console.log('go-ipfs') - console.log(id) - node.stopDaemon() - }) + if (err) { + throw err } -) + + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl + ipfs.id(function (err, id) { + if (err) { + throw err + } + + console.log('go-ipfs') + console.log(id) + node.stopDaemon() + }) +}) + +// opens an api connection to local running js-ipfs node +localController.spawn({ isJs: true, disposable: false }, (err, ipfsd) => { + if (err) { + throw err + } + + const ipfs = ipfsd.ctl + const node = ipfsd.ctrl + ipfs.id(function (err, id) { + if (err) { + throw err + } + + console.log('js-ipfs') + console.log(id) + node.stopDaemon() + }) +}) diff --git a/examples/remote-disposable.js b/examples/remote-disposable.js index b9b3fb21..ed5069d4 100644 --- a/examples/remote-disposable.js +++ b/examples/remote-disposable.js @@ -17,11 +17,19 @@ server.start((err) => { const remoteController = ipfsd.remoteController() remoteController.spawn(function (err, controller) { + if (err) { + throw err + } + const ipfs = controller.ctl const node = controller.ctrl ipfs.id(function (err, id) { + if (err) { + throw err + } + console.log(id) node.stopDaemon(() => server.stop()) }) }) -}) \ No newline at end of file +}) diff --git a/src/local.js b/src/local.js index 9c7a90a5..a22561fe 100644 --- a/src/local.js +++ b/src/local.js @@ -2,6 +2,7 @@ const merge = require('lodash.merge') const waterfall = require('async/waterfall') +const join = require('path').join const Node = require('./daemon') @@ -63,6 +64,13 @@ const IpfsDaemonController = { options.init = (typeof options.init !== 'undefined' ? options.init : true) options.start = options.init && options.start // don't start if not initialized + if (!options.disposable) { + options.init = false + options.repoPath = process.env.IPFS_PATH || + join(process.env.HOME || + process.env.USERPROFILE, options.isJs ? '.jsipfs' : '.ipfs') + } + const node = new Node(options) waterfall([ From 74eba9834e85644a1189fc707091aebc182d17ff Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 7 Dec 2017 23:49:20 -0600 Subject: [PATCH 30/85] feat: default params and allowing cmd args to spawn --- package.json | 2 +- src/daemon.js | 2 +- src/local.js | 12 ++++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3b2d09b9..53ea8d44 100644 --- a/package.json +++ b/package.json @@ -71,9 +71,9 @@ "http-browserify": "^1.7.0", "ipfs-api": "^17.2.0", "lodash.clonewith": "^4.5.0", + "lodash.defaultsdeep": "^4.6.0", "lodash.get": "^4.4.2", "lodash.mapvalues": "^4.6.0", - "lodash.merge": "^4.6.0", "multiaddr": "^3.0.1", "once": "^1.4.0", "qs": "^6.5.1", diff --git a/src/daemon.js b/src/daemon.js index 37c99ee3..3da3661c 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -222,7 +222,7 @@ class Node { flags = [] } - const args = ['daemon'].concat(flags) + const args = ['daemon'].concat(flags || []) callback = once(callback) diff --git a/src/local.js b/src/local.js index a22561fe..d970ad6a 100644 --- a/src/local.js +++ b/src/local.js @@ -1,6 +1,6 @@ 'use strict' -const merge = require('lodash.merge') +const defaults = require('lodash.defaultsdeep') const waterfall = require('async/waterfall') const join = require('path').join @@ -13,7 +13,10 @@ const defaultOptions = { 'PUT', 'POST', 'GET' - ] + ], + 'Addresses.Swarm': [`/ip4/127.0.0.1/tcp/0`], + 'Addresses.API': `/ip4/127.0.0.1/tcp/0`, + 'Addresses.Gateway': `/ip4/127.0.0.1/tcp/0` }, disposable: true, start: true, @@ -48,6 +51,7 @@ const IpfsDaemonController = { * - `repoPath` string - the repository path to use for this node, ignored if node is disposable * - `disposable` bool - a new repo is created and initialized for each invocation * - `config` - ipfs configuration options + * - args - array of cmd line arguments to be passed to ipfs daemon * * @param {Object} [opts={}] - various config options and ipfs config parameters * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` @@ -60,7 +64,7 @@ const IpfsDaemonController = { } let options = {} - merge(options, defaultOptions, opts || {}) + defaults(options, opts || {}, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) options.start = options.init && options.start // don't start if not initialized @@ -75,7 +79,7 @@ const IpfsDaemonController = { waterfall([ (cb) => options.init ? node.init(cb) : cb(null, node), - (node, cb) => options.start ? node.startDaemon(cb) : cb(null, null) + (node, cb) => options.start ? node.startDaemon(options.args, cb) : cb(null, null) ], (err, api) => { if (err) { return callback(err) From 78f37a183c4a33dcbaae8bdb340c363bf6fe1dbc Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 8 Dec 2017 23:01:50 -0600 Subject: [PATCH 31/85] several small changes --- src/daemon.js | 4 ++++ src/local.js | 8 +++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/daemon.js b/src/daemon.js index 3da3661c..afa05121 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -134,6 +134,10 @@ class Node { return this._gatewayAddr } + get repoPath () { + return this.path + } + /** * Is the node started * diff --git a/src/local.js b/src/local.js index d970ad6a..5c1f4c63 100644 --- a/src/local.js +++ b/src/local.js @@ -51,7 +51,7 @@ const IpfsDaemonController = { * - `repoPath` string - the repository path to use for this node, ignored if node is disposable * - `disposable` bool - a new repo is created and initialized for each invocation * - `config` - ipfs configuration options - * - args - array of cmd line arguments to be passed to ipfs daemon + * - `args` - array of cmd line arguments to be passed to ipfs daemon * * @param {Object} [opts={}] - various config options and ipfs config parameters * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` @@ -66,13 +66,11 @@ const IpfsDaemonController = { let options = {} defaults(options, opts || {}, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) - options.start = options.init && options.start // don't start if not initialized if (!options.disposable) { - options.init = false - options.repoPath = process.env.IPFS_PATH || + options.repoPath = options.repoPath || (process.env.IPFS_PATH || join(process.env.HOME || - process.env.USERPROFILE, options.isJs ? '.jsipfs' : '.ipfs') + process.env.USERPROFILE, options.isJs ? '.jsipfs' : '.ipfs')) } const node = new Node(options) From 43ed8214f271fa28bd42a94590a1df4a3e1d6005 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Mon, 11 Dec 2017 10:30:13 -0600 Subject: [PATCH 32/85] feat: rework examples to have their own package.json chore: update circle ci to latests config --- README.md | 1 + circle.yml | 10 +--------- examples/electron-asar/app.js | 8 ++++++-- examples/{ => id}/id.js | 2 +- examples/id/package.json | 14 ++++++++++++++ .../{ => local-disposable}/local-disposable.js | 2 +- examples/local-disposable/package.json | 14 ++++++++++++++ examples/{ => local}/local.js | 2 +- examples/local/package.json | 14 ++++++++++++++ examples/remote-disposable/package.json | 14 ++++++++++++++ .../{ => remote-disposable}/remote-disposable.js | 2 +- src/daemon.js | 2 +- src/local.js | 4 +++- test/spawning.js | 13 +++++++++---- 14 files changed, 81 insertions(+), 21 deletions(-) rename examples/{ => id}/id.js (94%) create mode 100644 examples/id/package.json rename examples/{ => local-disposable}/local-disposable.js (95%) create mode 100644 examples/local-disposable/package.json rename examples/{ => local}/local.js (95%) create mode 100644 examples/local/package.json create mode 100644 examples/remote-disposable/package.json rename examples/{ => remote-disposable}/remote-disposable.js (95%) diff --git a/README.md b/README.md index 051e7f9b..8efe0514 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Where `options` is: - `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable - `disposable` bool - a new repo is created and initialized for each invocation +- `args` - array of cmd line arguments to be passed to ipfs daemon - `config` - ipfs configuration options diff --git a/circle.yml b/circle.yml index 5d59fb8d..2c57d119 100644 --- a/circle.yml +++ b/circle.yml @@ -3,21 +3,13 @@ machine: node: version: stable -test: - post: - - npm run coverage -- --upload - dependencies: pre: - google-chrome --version - curl -L -o google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - - for v in $(curl http://archive.ubuntu.com/ubuntu/pool/main/n/nss/ | grep "href=" | grep "libnss3.*deb\"" -o | grep -o "libnss3.*deb" | grep "3.28" | grep "14.04"); do curl -L -o $v http://archive.ubuntu.com/ubuntu/pool/main/n/nss/$v; done && rm libnss3-tools*_i386.deb libnss3-dev*_i386.deb - sudo dpkg -i google-chrome.deb || true - - sudo dpkg -i libnss3*.deb || true - sudo apt-get update - - sudo apt-get install -f || true - - sudo dpkg -i libnss3*.deb - sudo apt-get install -f - sudo apt-get install --only-upgrade lsb-base - sudo dpkg -i google-chrome.deb - - google-chrome --version + - google-chrome --version \ No newline at end of file diff --git a/examples/electron-asar/app.js b/examples/electron-asar/app.js index 95b64c4b..a272d805 100644 --- a/examples/electron-asar/app.js +++ b/examples/electron-asar/app.js @@ -1,8 +1,12 @@ /* eslint no-console: 0 */ 'use strict' -const { app, ipcMain, BrowserWindow } = require('electron') -const ipfsd = require('../..') +const electron = require('electron') +const app = electron.app +const ipcMain = electron.ipcMain +const BrowserWindow = electron.BrowserWindow + +const ipfsd = require('ipfsd-ctl') app.on('ready', () => { const win = new BrowserWindow({ diff --git a/examples/id.js b/examples/id/id.js similarity index 94% rename from examples/id.js rename to examples/id/id.js index f9ffaf1d..d22aecd7 100644 --- a/examples/id.js +++ b/examples/id/id.js @@ -1,7 +1,7 @@ /* eslint no-console: 0 */ 'use strict' -const factory = require('../') +const factory = require('ipfsd-ctl') const localController = factory.localController localController.spawn(function (err, ipfsd) { diff --git a/examples/id/package.json b/examples/id/package.json new file mode 100644 index 00000000..3294355e --- /dev/null +++ b/examples/id/package.json @@ -0,0 +1,14 @@ +{ + "name": "id", + "version": "1.0.0", + "description": "", + "main": "id.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "ipfsd-ctl": "file:../.." + }, + "author": "", + "license": "MIT" +} diff --git a/examples/local-disposable.js b/examples/local-disposable/local-disposable.js similarity index 95% rename from examples/local-disposable.js rename to examples/local-disposable/local-disposable.js index dd255615..44dd3a79 100644 --- a/examples/local-disposable.js +++ b/examples/local-disposable/local-disposable.js @@ -7,7 +7,7 @@ // IPFS_PATH will point to /tmp/ipfs_***** and will be // cleaned up when the process exits. -const factory = require('../') +const factory = require('ipfsd-ctl') const localController = factory.localController // start a go daemon diff --git a/examples/local-disposable/package.json b/examples/local-disposable/package.json new file mode 100644 index 00000000..4f992a61 --- /dev/null +++ b/examples/local-disposable/package.json @@ -0,0 +1,14 @@ +{ + "name": "local-disposable", + "version": "1.0.0", + "description": "", + "main": "local-disposable.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "ipfsd-ctl": "file:../.." + }, + "author": "", + "license": "MIT" +} diff --git a/examples/local.js b/examples/local/local.js similarity index 95% rename from examples/local.js rename to examples/local/local.js index d608ad41..f86f661f 100644 --- a/examples/local.js +++ b/examples/local/local.js @@ -1,7 +1,7 @@ /* eslint no-console: 0 */ 'use strict' -const factory = require('../') +const factory = require('ipfsd-ctl') const localController = factory.localController // opens an api connection to local running go-ipfs node diff --git a/examples/local/package.json b/examples/local/package.json new file mode 100644 index 00000000..d4bd3e00 --- /dev/null +++ b/examples/local/package.json @@ -0,0 +1,14 @@ +{ + "name": "local", + "version": "1.0.0", + "description": "", + "main": "local.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "ipfsd-ctl": "file:../.." + }, + "author": "", + "license": "MIT" +} diff --git a/examples/remote-disposable/package.json b/examples/remote-disposable/package.json new file mode 100644 index 00000000..61885524 --- /dev/null +++ b/examples/remote-disposable/package.json @@ -0,0 +1,14 @@ +{ + "name": "remote-disposable", + "version": "1.0.0", + "description": "", + "main": "remote-disposable.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "ipfsd-ctl": "file:../.." + }, + "author": "", + "license": "MIT" +} diff --git a/examples/remote-disposable.js b/examples/remote-disposable/remote-disposable.js similarity index 95% rename from examples/remote-disposable.js rename to examples/remote-disposable/remote-disposable.js index ed5069d4..8b561bee 100644 --- a/examples/remote-disposable.js +++ b/examples/remote-disposable/remote-disposable.js @@ -7,7 +7,7 @@ // IPFS_PATH will point to /tmp/ipfs_***** and will be // cleaned up when the process exits. -const ipfsd = require('../') +const ipfsd = require('ipfsd-ctl') const server = ipfsd.server server.start((err) => { diff --git a/src/daemon.js b/src/daemon.js index afa05121..251ac05c 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -83,7 +83,7 @@ function tempDir (isJs) { } /** - * Controll a go-ipfs node. + * Controll a go-ipfs or js-ipfs node. */ class Node { /** diff --git a/src/local.js b/src/local.js index 5c1f4c63..c0dc10dc 100644 --- a/src/local.js +++ b/src/local.js @@ -3,6 +3,7 @@ const defaults = require('lodash.defaultsdeep') const waterfall = require('async/waterfall') const join = require('path').join +const flat = require('flat') const Node = require('./daemon') @@ -64,10 +65,11 @@ const IpfsDaemonController = { } let options = {} - defaults(options, opts || {}, defaultOptions) + defaults(options, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) if (!options.disposable) { + options.init = false options.repoPath = options.repoPath || (process.env.IPFS_PATH || join(process.env.HOME || process.env.USERPROFILE, options.isJs ? '.jsipfs' : '.ipfs')) diff --git a/test/spawning.js b/test/spawning.js index 1dc78c3a..117af82a 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -23,7 +23,7 @@ function tempDir (isJs) { module.exports = (ipfsdController, isJs) => { return () => { - const VERSION_STRING = isJs ? 'js-ipfs version: 0.27.1' : 'ipfs version 0.4.13' + const VERSION_STRING = isJs ? 'js-ipfs version: 0.27.3' : 'ipfs version 0.4.13' const API_PORT = isJs ? '5002' : '5001' const GW_PORT = isJs ? '9090' : '8080' @@ -140,14 +140,19 @@ module.exports = (ipfsdController, isJs) => { addRetrieveTests() }) - describe('spawn a node and pass init options', () => { + describe.only('spawn a node and pass init options', () => { const repoPath = tempDir(isJs) const addr = '/ip4/127.0.0.1/tcp/5678' const swarmAddr1 = '/ip4/127.0.0.1/tcp/35555/ws' const swarmAddr2 = '/ip4/127.0.0.1/tcp/35666' const config = { - 'Addresses.Swarm': [swarmAddr1, swarmAddr2], - 'Addresses.API': addr + Addresses: { + Swarm: [ + swarmAddr1, + swarmAddr2 + ], + API: addr + } } it('allows passing ipfs config options to spawn', function (done) { From 62832e16b7eebbb1cd7f9dfa516c877b72516ba2 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 12 Dec 2017 12:37:16 -0600 Subject: [PATCH 33/85] feat: allow passing config values as object move utils functions to its own file --- package.json | 1 + src/daemon.js | 74 +++----------------------- src/local.js | 4 +- src/utils.js | 112 +++++++++++++++++++++++++++++++++++++++ test/api.js | 134 +++++++++++++++++++++++++++++++++++++++++++++++ test/daemon.js | 14 +++-- test/exec.js | 9 ++++ test/spawning.js | 122 +----------------------------------------- test/utils.js | 60 +++++++++++++++++++++ 9 files changed, 337 insertions(+), 193 deletions(-) create mode 100644 src/utils.js create mode 100644 test/api.js create mode 100644 test/utils.js diff --git a/package.json b/package.json index 53ea8d44..be5859c6 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "go-ipfs-dep": "0.4.13", "guid": "0.0.12", "hapi": "^16.6.2", + "hat": "0.0.3", "http-browserify": "^1.7.0", "ipfs-api": "^17.2.0", "lodash.clonewith": "^4.5.0", diff --git a/src/daemon.js b/src/daemon.js index 251ac05c..b4d29ad6 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -7,81 +7,21 @@ const multiaddr = require('multiaddr') const rimraf = require('rimraf') const shutdown = require('shutdown') const path = require('path') -const join = path.join const once = require('once') -const os = require('os') const truthy = require('truthy') +const utils = require('./utils') -const isWindows = os.platform() === 'win32' +const tryJsonParse = utils.tryJsonParse +const parseConfig = utils.parseConfig +const tempDir = utils.tempDir +const findIpfsExecutable = utils.findIpfsExecutable +const setConfigValue = utils.setConfigValue +const configureNode = utils.configureNode const exec = require('./exec') const GRACE_PERIOD = 10500 // amount of ms to wait before sigkill -function findIpfsExecutable (isJs, rootPath) { - let appRoot = path.join(rootPath, '..') - // If inside .asar try to load from .asar.unpacked - // this only works if asar was built with - // asar --unpack-dir=node_modules/go-ipfs-dep/* (not tested) - // or - // electron-packager ./ --asar.unpackDir=node_modules/go-ipfs-dep - if (appRoot.includes(`.asar${path.sep}`)) { - appRoot = appRoot.replace(`.asar${path.sep}`, `.asar.unpacked${path.sep}`) - } - const appName = isWindows ? 'ipfs.exe' : 'ipfs' - const depPath = isJs - ? path.join('ipfs', 'src', 'cli', 'bin.js') - : path.join('go-ipfs-dep', 'go-ipfs', appName) - const npm3Path = path.join(appRoot, '../', depPath) - const npm2Path = path.join(appRoot, 'node_modules', depPath) - - if (fs.existsSync(npm3Path)) { - return npm3Path - } - if (fs.existsSync(npm2Path)) { - return npm2Path - } - - throw new Error('Cannot find the IPFS executable') -} - -function setConfigValue (node, key, value, callback) { - exec( - node.exec, - ['config', key, value, '--json'], - { env: node.env }, - callback - ) -} - -function configureNode (node, conf, callback) { - async.eachOfSeries(conf, (value, key, cb) => { - setConfigValue(node, key, JSON.stringify(value), cb) - }, callback) -} - -function tryJsonParse (input, callback) { - let res - try { - res = JSON.parse(input) - } catch (err) { - return callback(err) - } - callback(null, res) -} - -// Consistent error handling -function parseConfig (path, callback) { - async.waterfall([ - (cb) => fs.readFile(join(path, 'config'), cb), - (file, cb) => tryJsonParse(file.toString(), cb) - ], callback) -} - -function tempDir (isJs) { - return join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) -} - /** * Controll a go-ipfs or js-ipfs node. */ diff --git a/src/local.js b/src/local.js index c0dc10dc..56099f79 100644 --- a/src/local.js +++ b/src/local.js @@ -3,7 +3,7 @@ const defaults = require('lodash.defaultsdeep') const waterfall = require('async/waterfall') const join = require('path').join -const flat = require('flat') +const flatten = require('./utils').flatten const Node = require('./daemon') @@ -64,6 +64,8 @@ const IpfsDaemonController = { opts = defaultOptions } + opts.config = flatten(opts.config) + let options = {} defaults(options, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 00000000..d4d56a1f --- /dev/null +++ b/src/utils.js @@ -0,0 +1,112 @@ +'use strict' + +const async = require('async') +const fs = require('fs') +const hat = require('hat') +const os = require('os') +const path = require('path') +const exec = require('./exec') + +const join = path.join + +const isWindows = os.platform() === 'win32' + +// taken from https://github.com/hughsk/flat +exports.flatten = (target) => { + let output = {} + const step = (object, prev) => { + object = object || {} + Object.keys(object).forEach(function (key) { + var value = object[key] + var isarray = Array.isArray(value) + var type = Object.prototype.toString.call(value) + var isbuffer = Buffer.isBuffer(value) + var isobject = ( + type === '[object Object]' || + type === '[object Array]' + ) + + var newKey = prev + ? prev + '.' + key + : key + + if (!isarray && !isbuffer && isobject && Object.keys(value).length) { + return step(value, newKey) + } + + output[newKey] = value + }) + } + + step(target) + + return output +} + +function tryJsonParse (input, callback) { + let res + try { + res = JSON.parse(input) + } catch (err) { + return callback(err) + } + callback(null, res) +} + +exports.tryJsonParse = tryJsonParse + +// Consistent error handling +exports.parseConfig = (path, callback) => { + async.waterfall([ + (cb) => fs.readFile(join(path, 'config'), cb), + (file, cb) => tryJsonParse(file.toString(), cb) + ], callback) +} + +exports.tempDir = (isJs) => { + return join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${hat()}`) +} + +exports.findIpfsExecutable = (isJs, rootPath) => { + let appRoot = rootPath ? path.join(rootPath, '..') : process.cwd() + // If inside .asar try to load from .asar.unpacked + // this only works if asar was built with + // asar --unpack-dir=node_modules/go-ipfs-dep/* (not tested) + // or + // electron-packager ./ --asar.unpackDir=node_modules/go-ipfs-dep + if (appRoot.includes(`.asar${path.sep}`)) { + appRoot = appRoot.replace(`.asar${path.sep}`, `.asar.unpacked${path.sep}`) + } + const appName = isWindows ? 'ipfs.exe' : 'ipfs' + const depPath = isJs + ? path.join('ipfs', 'src', 'cli', 'bin.js') + : path.join('go-ipfs-dep', 'go-ipfs', appName) + const npm3Path = path.join(appRoot, '../', depPath) + const npm2Path = path.join(appRoot, 'node_modules', depPath) + + if (fs.existsSync(npm3Path)) { + return npm3Path + } + if (fs.existsSync(npm2Path)) { + return npm2Path + } + + throw new Error('Cannot find the IPFS executable') +} + +function setConfigValue (node, key, value, callback) { + exec( + node.exec, + ['config', key, value, '--json'], + { env: node.env }, + callback + ) +} + +exports.setConfigValue = setConfigValue + +exports.configureNode = (node, conf, callback) => { + async.eachOfSeries(conf, (value, key, cb) => { + setConfigValue(node, key, JSON.stringify(value), cb) + }, callback) +} diff --git a/test/api.js b/test/api.js new file mode 100644 index 00000000..33f43c21 --- /dev/null +++ b/test/api.js @@ -0,0 +1,134 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const ipfsApi = require('ipfs-api') +const multiaddr = require('multiaddr') +const os = require('os') + +const isWindows = os.platform() === 'win32' + +module.exports = (ipfsdController, isJs) => { + return () => { + const API_PORT = isJs ? '5002' : '5001' + const GW_PORT = isJs ? '9090' : '8080' + + describe('ipfs-api version', () => { + let ipfs + let node + + before(function (done) { + this.timeout(20 * 1000) + ipfsdController.spawn({ start: false }, (err, ret) => { + expect(err).to.not.exist() + node = ret.ctrl + node.startDaemon((err, ignore) => { + expect(err).to.not.exist() + ipfs = ipfsApi(node.apiAddr) + done() + }) + }) + }) + + after((done) => node.stopDaemon(done)) + + // skip on windows for now + // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 + // fails on windows see https://github.com/ipfs/js-ipfs-api/issues/408 + if (isWindows || !isNode) { + return it.skip('uses the correct ipfs-api') + } + + it('uses the correct ipfs-api', (done) => { + ipfs.util.addFromFs(path.join(__dirname, 'fixtures/'), { + recursive: true + }, (err, res) => { + expect(err).to.not.exist() + + const added = res[res.length - 1] + + // Temporary: Need to see what is going on on windows + expect(res).to.deep.equal([ + { + path: 'fixtures/test.txt', + hash: 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD', + size: 19 + }, + { + path: 'fixtures', + hash: 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU', + size: 73 + } + ]) + + expect(res.length).to.equal(2) + expect(added).to.have.property('path', 'fixtures') + expect(added).to.have.property( + 'hash', + 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU' + ) + expect(res[0]).to.have.property('path', 'fixtures/test.txt') + expect(res[0]).to.have.property( + 'hash', + 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD' + ) + done() + }) + }) + }) + + describe('validate api', () => { + it('starts the daemon and returns valid API and gateway addresses', function (done) { + this.timeout(20 * 1000) + ipfsdController.spawn({ isJs, config: null }, (err, ipfsd) => { + expect(err).to.not.exist() + const api = ipfsd.ctl + const node = ipfsd.ctrl + + // Check for props in daemon + expect(node).to.have.property('apiAddr') + expect(node).to.have.property('gatewayAddr') + expect(node.apiAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(node.apiAddr)).to.equal(true) + expect(node.gatewayAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(node.gatewayAddr)).to.equal(true) + + // Check for props in ipfs-api instance + expect(api).to.have.property('apiHost') + expect(api).to.have.property('apiPort') + expect(api).to.have.property('gatewayHost') + expect(api).to.have.property('gatewayPort') + expect(api.apiHost).to.equal('127.0.0.1') + expect(api.apiPort).to.equal(API_PORT) + expect(api.gatewayHost).to.equal('127.0.0.1') + expect(api.gatewayPort).to.equal(GW_PORT) + + node.stopDaemon(done) + }) + }) + + it('allows passing flags', function (done) { + // skip in js, since js-ipfs doesn't fail on unrecognized args, it prints the help instead + if (isJs) { + this.skip() + } else { + ipfsdController.spawn({ start: false }, (err, ipfsd) => { + expect(err).to.not.exist() + ipfsd.ctrl.startDaemon(['--should-not-exist'], (err) => { + expect(err).to.exist() + expect(err.message) + .to.match(/Unrecognized option 'should-not-exist'/) + + done() + }) + }) + } + }) + }) + } +} \ No newline at end of file diff --git a/test/daemon.js b/test/daemon.js index d9e026dd..b8eeec63 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -2,6 +2,7 @@ 'use strict' const daemon = require('./spawning') +const api = require('./api') const isNode = require('detect-node') const factory = require('../src') @@ -15,17 +16,20 @@ if (isNode) { describe('ipfsd-ctl', () => { // clean up IPFS env - afterEach(() => Object.keys(process.env).forEach((key) => { - if (key.includes('IPFS')) { - delete process.env[key] - } - })) + afterEach(() => Object.keys(process.env) + .forEach((key) => { + if (key.includes('IPFS')) { + delete process.env[key] + } + })) describe('Go daemon', () => { daemon(ipfsdController, false)() + api(ipfsdController, false) }) describe('Js daemon', () => { daemon(ipfsdController, true)() + api(ipfsdController, false) }) }) diff --git a/test/exec.js b/test/exec.js index 0a292ee9..c597c843 100644 --- a/test/exec.js +++ b/test/exec.js @@ -8,6 +8,10 @@ const cp = require('child_process') const path = require('path') const exec = require('../src/exec') +const os = require('os') + +const isWindows = os.platform() === 'win32' + const survivor = path.join(__dirname, 'survivor') function token () { @@ -60,6 +64,11 @@ function makeCheck (n, done) { // UPDATE: 12/06/2017 - `tail` seems to work fine on all ci systems. // I'm leaving it enabled for now. This does need a different approach for windows though. describe('exec', () => { + // TODO: skip on windows for now + if (isWindows) { + return + } + it('SIGTERM kills hang', (done) => { const tok = token() diff --git a/test/spawning.js b/test/spawning.js index 117af82a..03673022 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -8,12 +8,9 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) -const ipfsApi = require('ipfs-api') -const multiaddr = require('multiaddr') const path = require('path') const os = require('os') const isNode = require('detect-node') -const isWindows = os.platform() === 'win32' const addRetrieveTests = require('./add-retrive') @@ -23,9 +20,7 @@ function tempDir (isJs) { module.exports = (ipfsdController, isJs) => { return () => { - const VERSION_STRING = isJs ? 'js-ipfs version: 0.27.3' : 'ipfs version 0.4.13' - const API_PORT = isJs ? '5002' : '5001' - const GW_PORT = isJs ? '9090' : '8080' + const VERSION_STRING = isJs ? `js-ipfs version: ${require('ipfs/package.json').version}` : 'ipfs version 0.4.13' it('prints the version', function (done) { if (!isNode) { @@ -140,7 +135,7 @@ module.exports = (ipfsdController, isJs) => { addRetrieveTests() }) - describe.only('spawn a node and pass init options', () => { + describe('spawn a node and pass init options', () => { const repoPath = tempDir(isJs) const addr = '/ip4/127.0.0.1/tcp/5678' const swarmAddr1 = '/ip4/127.0.0.1/tcp/35555/ws' @@ -244,119 +239,6 @@ module.exports = (ipfsdController, isJs) => { } }) }) - - describe('ipfs-api version', () => { - let ipfs - let node - - before(function (done) { - this.timeout(20 * 1000) - ipfsdController.spawn({ start: false }, (err, ret) => { - expect(err).to.not.exist() - node = ret.ctrl - node.startDaemon((err, ignore) => { - expect(err).to.not.exist() - ipfs = ipfsApi(node.apiAddr) - done() - }) - }) - }) - - after((done) => node.stopDaemon(done)) - - // skip on windows for now - // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 - // fails on windows see https://github.com/ipfs/js-ipfs-api/issues/408 - if (isWindows || !isNode) { - return it.skip('uses the correct ipfs-api') - } - - it('uses the correct ipfs-api', (done) => { - ipfs.util.addFromFs(path.join(__dirname, 'fixtures/'), { - recursive: true - }, (err, res) => { - expect(err).to.not.exist() - - const added = res[res.length - 1] - - // Temporary: Need to see what is going on on windows - expect(res).to.deep.equal([ - { - path: 'fixtures/test.txt', - hash: 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD', - size: 19 - }, - { - path: 'fixtures', - hash: 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU', - size: 73 - } - ]) - - expect(res.length).to.equal(2) - expect(added).to.have.property('path', 'fixtures') - expect(added).to.have.property( - 'hash', - 'QmXkiTdnfRJjiQREtF5dWf2X4V9awNHQSn9YGofwVY4qUU' - ) - expect(res[0]).to.have.property('path', 'fixtures/test.txt') - expect(res[0]).to.have.property( - 'hash', - 'Qmf412jQZiuVUtdgnB36FXFX7xg5V6KEbSJ4dpQuhkLyfD' - ) - done() - }) - }) - }) - - describe('validate api', () => { - it('starts the daemon and returns valid API and gateway addresses', function (done) { - this.timeout(20 * 1000) - ipfsdController.spawn({ isJs, config: null }, (err, ipfsd) => { - expect(err).to.not.exist() - const api = ipfsd.ctl - const node = ipfsd.ctrl - - // Check for props in daemon - expect(node).to.have.property('apiAddr') - expect(node).to.have.property('gatewayAddr') - expect(node.apiAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(node.apiAddr)).to.equal(true) - expect(node.gatewayAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(node.gatewayAddr)).to.equal(true) - - // Check for props in ipfs-api instance - expect(api).to.have.property('apiHost') - expect(api).to.have.property('apiPort') - expect(api).to.have.property('gatewayHost') - expect(api).to.have.property('gatewayPort') - expect(api.apiHost).to.equal('127.0.0.1') - expect(api.apiPort).to.equal(API_PORT) - expect(api.gatewayHost).to.equal('127.0.0.1') - expect(api.gatewayPort).to.equal(GW_PORT) - - node.stopDaemon(done) - }) - }) - - it('allows passing flags', function (done) { - // skip in js, since js-ipfs doesn't fail on unrecognized args, it prints the help instead - if (isJs) { - this.skip() - } else { - ipfsdController.spawn({ start: false }, (err, ipfsd) => { - expect(err).to.not.exist() - ipfsd.ctrl.startDaemon(['--should-not-exist'], (err) => { - expect(err).to.exist() - expect(err.message) - .to.match(/Unrecognized option 'should-not-exist'/) - - done() - }) - }) - } - }) - }) }) } } diff --git a/test/utils.js b/test/utils.js new file mode 100644 index 00000000..c6a91bbc --- /dev/null +++ b/test/utils.js @@ -0,0 +1,60 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const fs = require('fs') +const utils = require('../src/utils') +const flatten = utils.flatten +const tempDir = utils.tempDir +const findIpfsExecutable = utils.findIpfsExecutable + +describe('utils', () => { + describe('flatten config', () => { + it('should flatten', () => { + expect(flatten({ a: { b: { c: [1, 2, 3] } } })).to.deep.equal({ 'a.b.c': [1, 2, 3] }) + }) + + it('should handle nulls', () => { + expect(flatten(null)).to.deep.equal({}) + }) + + it('should handle undefined', () => { + expect(flatten(undefined)).to.deep.equal({}) + }) + }) + + describe('tmp dir', () => { + it('should create tmp directory path for go-ipfs', () => { + const tmpDir = tempDir() + expect(tmpDir).to.exist() + expect(tmpDir).to.include('ipfs_') + }) + + it('should create tmp directory path for js-ipfs', () => { + const tmpDir = tempDir(true) + expect(tmpDir).to.exist() + expect(tmpDir).to.include('jsipfs_') + }) + }) + + describe('find executable', () => { + it('should find go executable', () => { + const execPath = findIpfsExecutable(false, __dirname) + expect(execPath).to.exist() + expect(execPath).to.include('go-ipfs-dep/go-ipfs/ipfs') + expect(fs.existsSync(execPath)).to.be.ok() + }) + + it('should find go executable', () => { + const execPath = findIpfsExecutable(true, __dirname) + expect(execPath).to.exist() + expect(execPath).to.include('ipfs/src/cli/bin.js') + expect(fs.existsSync(execPath)).to.be.ok() + }) + }) +}) From 3c0845bb86f20ff7dd4949bd58008e2f7c8d3b63 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 12 Dec 2017 22:35:04 -0600 Subject: [PATCH 34/85] docs: updating readme --- .aegir.js | 2 +- README.md | 201 +++++++++++++++++++++++++++++++++++++++++--------- package.json | 1 + src/daemon.js | 5 ++ test/api.js | 4 +- test/node.js | 1 + 6 files changed, 177 insertions(+), 37 deletions(-) diff --git a/.aegir.js b/.aegir.js index 6a5ea8f3..d2791092 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,6 +1,6 @@ 'use strict' -const server = require('./src/remote/server') +const server = require('./src').server module.exports = { karma: { diff --git a/README.md b/README.md index 8efe0514..c247767e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ - [Install](#install) - [Usage](#usage) +- [API](#api) - [Contribute](#contribute) - [License](#license) @@ -39,10 +40,10 @@ IPFS daemons are already easy to start and stop, but this module is here to do i // IPFS_PATH will point to /tmp/ipfs_***** and will be // cleaned up when the process exits. -const factory = require('ipfsd-ctl') -const localController = factory.localController +const daemonFactory = require('ipfsd-ctl') +const local = daemonFactory.localController -localController.spawn(function (err, ipfsd) { +local.spawn(function (err, ipfsd) { const ipfs = ipfsd.ctl const node = ipfsd.ctrl ipfs.id(function (err, id) { @@ -61,64 +62,194 @@ localController.spawn(function (err, ipfsd) { // IPFS_PATH will point to /tmp/ipfs_***** and will be // cleaned up when the process exits. -const ipfsd = require('ipfsd-ctl') -const server = ipfsd.server +const daemonFactory = require('ipfsd-ctl') +const server = daemonFactory.server server.start((err) => { if (err) { throw err } - const remoteController = ipfsd.remoteController(port || 9999) - remoteController.spawn(function (err, controller) { - const ipfs = controller.ctl - const node = controller.ctrl - ipfs.id(function (err, id) { + const remote = daemonFactory.remoteController(port || 9999) + remote.spawn(function (err, controller) { + const ipfsCtl = controller.ctl + const ipfsCtrl = controller.ctrl + ipfsCtl.id(function (err, id) { console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() server.stop() }) }) }) ``` -It's also possible to start the server from `.aegir` `pre` and `post` hooks. For reference take a look at the `.aegir` file in this repository. +It's also possible to start the server from `.aegir` `pre` and `post` hooks. +```js +'use strict' + +const server = require('./src').server + +module.exports = { + karma: { + files: [{ + pattern: 'test/fixtures/**/*', + watched: false, + served: true, + included: false + }], + singleRun: true + }, + hooks: { + browser: { + pre: server.start, + post: server.stop + } + } +} +``` ## API -### Create factory +### Daemon Factory -- This methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes - - `localController` - create a local controller - - `remoteController([port])` - create a remote controller, usable from browsers -- `server` - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. +#### Create factory -### Spawn nodes +- `daemonFactory.localController` - create a local controller +- `daemonFactory.remoteController([port])` - create a remote controller, usable from browsers + - This methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes +- `daemonFactory.server` - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. -```js - /** - Spawn an IPFS node, either js-ipfs or go-ipfs +#### Spawn nodes - @param {Object} [options={}] - various config options and ipfs config parameters (see valid options below) - @param {Function} cb(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` - */ - spawn(options, cb) -``` +> Spawn either a js-ipfs or go-ipfs node through `localController` or `remoteController` + +`spawn([options], cb)` + +- `options` - is an optional object with various options and ipfs config parameters + - `js` bool (default false) - spawn a js or go node (default go) + - `init` bool (default true) - should the node be initialized + - `start` bool (default true) - should the node be started + - `repoPath` string - the repository path to use for this node, ignored if node is disposable + - `disposable` bool - a new repo is created and initialized for each invocation + - `args` - array of cmd line arguments to be passed to ipfs daemon + - `config` - ipfs configuration options + + - `cb(err, {ctl: , ctrl: })` - a callback that receives an object with two members: + - `ctl` an [ipfs-api](https://github.com/ipfs/js-ipfs-api) instance attached to the newly created ipfs node + - `ctrl` an instance of a daemon controller object + + +### IPFS Client (ctl) + +> An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) + + +### IPFS Daemon Controller (ctrl) + + +#### `apiAddr` (getter) + +> Get the address (multiaddr) of connected IPFS API. + +- returns multiaddr + +#### `gatewayAddr` (getter) + +> Get the address (multiaddr) of connected IPFS HTTP Gateway. + +- returns multiaddr + +#### `repoPath` (getter) + +> Get the current repo path. + +- returns string + +#### `started` (getter) + +> Is the node started. + +- returns boolean + +#### `init (initOpts, callback)` + +> Initialize a repo. + +- initOpts (optional) - options object with the following entries + - keysize (default 2048) - The bit size of the identiy key. + - directory (default IPFS_PATH) - The location of the repo. + - function (Error, Node) callback - receives an instance of this Node on success or an instance of `Error` on failure + + +#### `shutdown (callback)` + +> Delete the repo that was being used. If the node was marked as `disposable` this will be called automatically when the process is exited. + +- function(Error) callback + +#### `startDaemon (flags, callback)` + +> Start the daemon. + +- flags - Flags array to be passed to the `ipfs daemon` command. +- function(Error, IpfsApi)} callback - function that receives an instance of `ipfs-api` on success or an instance of `Error` on failure + + +#### `stopDaemon (callback)` + +> Stop the daemon. + +- function(Error) callback - function that receives an instance of `Error` on failure + +#### `killProcess (callback)` + +> Kill the `ipfs daemon` process. + +First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process hasn't exited yet. + +- function() callback - Called once the process is killed + + +#### `daemonPid ()` + +> Get the pid of the `ipfs daemon` process. + +- returns the pid number + + +#### `getConfig (key, callback)` + +> Call `ipfs config` + +If no `key` is passed, the whole config is returned as an object. + +- key (optional) - A specific config to retrieve. +- function(Error, (Object|string) callback - function that reseives an object or string on success or an `Error` instance on failure + + +#### `setConfig (key, value, callback)` + +> Set a config value. + +- key - the key to set +- value - the value to set the key to +- function(Error) callback + + +#### `replaceConf (file, callback)` +> Replace the configuration with a given file -Where `options` is: +- file - path to the new config file +- function(Error) callback -- `js` bool (default false) - spawn a js or go node (default go) -- `init` bool (default true) - should the node be initialized -- `start` bool (default true) - should the node be started -- `repoPath` string - the repository path to use for this node, ignored if node is disposable -- `disposable` bool - a new repo is created and initialized for each invocation -- `args` - array of cmd line arguments to be passed to ipfs daemon -- `config` - ipfs configuration options +#### `version (callback)` -If you need want to use an existing ipfs installation you can set `$IPFS_EXEC=/path/to/ipfs` to ensure it uses that. +> Get the version of ipfs +- function(Error, string) callback + For more details see https://ipfs.github.io/js-ipfsd-ctl/. ### Packaging diff --git a/package.json b/package.json index be5859c6..1364c8e3 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ ], "license": "MIT", "dependencies": { + "@ljharb/eslint-config": "^12.2.1", "async": "^2.6.0", "debug": "^3.1.0", "detect-node": "^2.0.3", diff --git a/src/daemon.js b/src/daemon.js index b4d29ad6..ae99ecc7 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -74,6 +74,11 @@ class Node { return this._gatewayAddr } + /** + * Get the current repo path + * + * @return {string} + */ get repoPath () { return this.path } diff --git a/test/api.js b/test/api.js index 33f43c21..c9aae194 100644 --- a/test/api.js +++ b/test/api.js @@ -10,7 +10,9 @@ chai.use(dirtyChai) const ipfsApi = require('ipfs-api') const multiaddr = require('multiaddr') const os = require('os') +const path = require('path') +const isNode = require('detect-node') const isWindows = os.platform() === 'win32' module.exports = (ipfsdController, isJs) => { @@ -131,4 +133,4 @@ module.exports = (ipfsdController, isJs) => { }) }) } -} \ No newline at end of file +} diff --git a/test/node.js b/test/node.js index 6612aeb8..b379988f 100644 --- a/test/node.js +++ b/test/node.js @@ -4,6 +4,7 @@ require('./daemon') require('./exec') +require('./utils') const startStop = require('./start-stop') const install = require('./npm-installs') From 14b4f3af7e080b94acbaa40ae7ce1265859fe160 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 12 Dec 2017 22:42:34 -0600 Subject: [PATCH 35/85] docs: small changes --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c247767e..0471ecd9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Appveyor CI](https://ci.appveyor.com/api/projects/status/4p9r12ch0jtthnha?svg=true)](https://ci.appveyor.com/project/wubalubadubdub/js-ipfsd-ctl-a9ywu) [![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) -> Control an ipfs node daemon using Node.js +> Control an ipfs node daemon using either Node.js or the browser ## Table of Contents @@ -117,7 +117,7 @@ module.exports = { - `daemonFactory.localController` - create a local controller - `daemonFactory.remoteController([port])` - create a remote controller, usable from browsers - - This methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes + - These methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes - `daemonFactory.server` - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. #### Spawn nodes @@ -136,8 +136,8 @@ module.exports = { - `config` - ipfs configuration options - `cb(err, {ctl: , ctrl: })` - a callback that receives an object with two members: - - `ctl` an [ipfs-api](https://github.com/ipfs/js-ipfs-api) instance attached to the newly created ipfs node - - `ctrl` an instance of a daemon controller object + - `ctl` - an [ipfs-api](https://github.com/ipfs/js-ipfs-api) instance attached to the newly created ipfs node + - `ctrl` - an instance of a daemon controller object ### IPFS Client (ctl) From 5b8e044604a033abdfd76bab88751d2271b99738 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 12 Dec 2017 23:18:33 -0600 Subject: [PATCH 36/85] docs: adding diagram --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index 0471ecd9..2c46d3c0 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,29 @@ > Control an ipfs node daemon using either Node.js or the browser +``` + + + +-----+ + | H | + | T | + +-----------------------------+ | T | + | NODE.JS | +-----------------------+ | P | +-----------------------------+ + | | | | | | | BROWSER | + | +-----------------------+ | | IPFS Daemon | | S | | | + | | Local Daemon Ctrl | | | | | E | | +----------------------+ | + | | +------- -------- R -----|---- Remote Daemon Ctrl | | + | +-----------------------+ | +-----|-----------|-----+ | V | | | | | + | | | | | E | | +----------------------+ | + | +-----------------------+ | | | | R | | | + | | IPFS API | | | | +-----+ | +----------------------+ | + | | -------------+ | | | IPFS API | | + | +-----------------------+ | +-----------------------|---- | | + | | | +----------------------+ | + +-----------------------------+ +-----------------------------+ + +``` + ## Table of Contents - [Install](#install) From 132ce96de4befa00ff6a1101c5b304562b61da8f Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 12 Dec 2017 23:37:23 -0600 Subject: [PATCH 37/85] feat: run non disposable nodes on default addresses/ports --- src/local.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/local.js b/src/local.js index 56099f79..cc5b5a51 100644 --- a/src/local.js +++ b/src/local.js @@ -66,6 +66,14 @@ const IpfsDaemonController = { opts.config = flatten(opts.config) + // remove random ports for non disposable nodes + // we want them to run on default addresses/ports + if (!opts.disposable) { + delete defaultOptions.config['Addresses.Swarm'] + delete defaultOptions.config['Addresses.API'] + delete defaultOptions.config['Addresses.Gateway'] + } + let options = {} defaults(options, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) From e40e4820e6e61f8157ae24e4eef1a95c69674af2 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 12 Dec 2017 23:38:50 -0600 Subject: [PATCH 38/85] fix: NODE.JS to Node.js --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2c46d3c0..489d2b6b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ | H | | T | +-----------------------------+ | T | - | NODE.JS | +-----------------------+ | P | +-----------------------------+ + | Node.js | +-----------------------+ | P | +-----------------------------+ | | | | | | | BROWSER | | +-----------------------+ | | IPFS Daemon | | S | | | | | Local Daemon Ctrl | | | | | E | | +----------------------+ | From 088eb2273e7195883181064fe793cf2dda7e9893 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 13 Dec 2017 11:27:45 -0600 Subject: [PATCH 39/85] feat: split defaults into options and config --- src/local.js | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/local.js b/src/local.js index cc5b5a51..3baf4f63 100644 --- a/src/local.js +++ b/src/local.js @@ -8,22 +8,23 @@ const flatten = require('./utils').flatten const Node = require('./daemon') const defaultOptions = { - config: { - 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], - 'API.HTTPHeaders.Access-Control-Allow-Methods': [ - 'PUT', - 'POST', - 'GET' - ], - 'Addresses.Swarm': [`/ip4/127.0.0.1/tcp/0`], - 'Addresses.API': `/ip4/127.0.0.1/tcp/0`, - 'Addresses.Gateway': `/ip4/127.0.0.1/tcp/0` - }, disposable: true, start: true, init: true } +const defaultConfig = { + 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], + 'API.HTTPHeaders.Access-Control-Allow-Methods': [ + 'PUT', + 'POST', + 'GET' + ], + 'Addresses.Swarm': [`/ip4/127.0.0.1/tcp/0`], + 'Addresses.API': `/ip4/127.0.0.1/tcp/0`, + 'Addresses.Gateway': `/ip4/127.0.0.1/tcp/0` +} + /** * Control go-ipfs nodes directly from JavaScript. * @@ -64,27 +65,24 @@ const IpfsDaemonController = { opts = defaultOptions } - opts.config = flatten(opts.config) - - // remove random ports for non disposable nodes - // we want them to run on default addresses/ports - if (!opts.disposable) { - delete defaultOptions.config['Addresses.Swarm'] - delete defaultOptions.config['Addresses.API'] - delete defaultOptions.config['Addresses.Gateway'] - } - let options = {} defaults(options, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) if (!options.disposable) { + delete defaultConfig['Addresses.Swarm'] + delete defaultConfig['Addresses.API'] + delete defaultConfig['Addresses.Gateway'] + options.init = false options.repoPath = options.repoPath || (process.env.IPFS_PATH || join(process.env.HOME || process.env.USERPROFILE, options.isJs ? '.jsipfs' : '.ipfs')) } + options.config = flatten(opts.config) + defaults(options.config, options.config, defaultConfig) + const node = new Node(options) waterfall([ From f10693fc132d82989633c2d3f2c593431839acee Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 13 Dec 2017 11:53:58 -0600 Subject: [PATCH 40/85] docs: fixing naming conventions in readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 489d2b6b..d042806b 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,11 @@ const daemonFactory = require('ipfsd-ctl') const local = daemonFactory.localController local.spawn(function (err, ipfsd) { - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() }) }) ``` From 38026c6aebc1bd718555e20205b57a50151eea06 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 14 Dec 2017 18:09:50 -0600 Subject: [PATCH 41/85] wip: reworking factory instantiation --- README.md | 60 ++++++++++--------- examples/id/id.js | 16 ++--- examples/local-disposable/local-disposable.js | 27 ++++----- examples/local/local.js | 8 +-- .../remote-disposable/remote-disposable.js | 19 +++--- package.json | 3 +- src/daemon.js | 4 +- src/index.js | 18 ++++-- src/local.js | 1 + src/remote/server.js | 10 +--- test/daemon.js | 21 +++---- test/start-stop.js | 5 +- 12 files changed, 91 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index d042806b..ecf9c58d 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ > Control an ipfs node daemon using either Node.js or the browser ``` - - +-----+ | H | | T | @@ -32,7 +30,6 @@ | +-----------------------+ | +-----------------------|---- | | | | | +----------------------+ | +-----------------------------+ +-----------------------------+ - ``` ## Table of Contents @@ -60,13 +57,10 @@ IPFS daemons are already easy to start and stop, but this module is here to do i // Start a disposable node, and get access to the api // print the node id, and stop the temporary daemon -// IPFS_PATH will point to /tmp/ipfs_***** and will be -// cleaned up when the process exits. +const controllerFactory = require('ipfsd-ctl') +const daemonFactory = controllerFactory() -const daemonFactory = require('ipfsd-ctl') -const local = daemonFactory.localController - -local.spawn(function (err, ipfsd) { +daemonFactory.spawn(function (err, ipfsd) { const ipfsCtl = ipfsd.ctl const ipfsCtrl = ipfsd.ctrl ipfsCtl.id(function (err, id) { @@ -82,21 +76,18 @@ local.spawn(function (err, ipfsd) { // Start a remote disposable node, and get access to the api // print the node id, and stop the temporary daemon -// IPFS_PATH will point to /tmp/ipfs_***** and will be -// cleaned up when the process exits. - -const daemonFactory = require('ipfsd-ctl') -const server = daemonFactory.server +const controllerFactory = require('ipfsd-ctl') +const daemonFactory = controllerFactory() -server.start((err) => { +const port = 9999 +daemonFactory.start(port, (err) => { if (err) { throw err } - const remote = daemonFactory.remoteController(port || 9999) - remote.spawn(function (err, controller) { - const ipfsCtl = controller.ctl - const ipfsCtrl = controller.ctrl + daemonFactory.spawn(function (err, ipfsd) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl ipfsCtl.id(function (err, id) { console.log(id) ipfsCtrl.stopDaemon() @@ -138,16 +129,25 @@ module.exports = { #### Create factory -- `daemonFactory.localController` - create a local controller -- `daemonFactory.remoteController([port])` - create a remote controller, usable from browsers - - These methods return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes -- `daemonFactory.server` - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. +```js +const controllerFactory = require('ipfsd-ctl') +const daemonFactory = controllerFactory() +``` + +> Create a factory that will expose the `daemonFactory.spawn` method + +- These method return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes + + +> `daemonFactory.server` + +- exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. #### Spawn nodes > Spawn either a js-ipfs or go-ipfs node through `localController` or `remoteController` -`spawn([options], cb)` +`spawn([options], callback)` - `options` - is an optional object with various options and ipfs config parameters - `js` bool (default false) - spawn a js or go node (default go) @@ -158,10 +158,11 @@ module.exports = { - `args` - array of cmd line arguments to be passed to ipfs daemon - `config` - ipfs configuration options - - `cb(err, {ctl: , ctrl: })` - a callback that receives an object with two members: - - `ctl` - an [ipfs-api](https://github.com/ipfs/js-ipfs-api) instance attached to the newly created ipfs node - - `ctrl` - an instance of a daemon controller object - + - `callback` - is a function with the signature `cb(err, ipfsd)` where: + - `err` - is the error set if spawning the node is unsuccessful + - `ipfsd` - is an object with two properties: + - `ctl` - an [ipfs-api](https://github.com/ipfs/js-ipfs-api) instance attached to the newly created ipfs node + - `ctrl` - an instance of a daemon controller object ### IPFS Client (ctl) @@ -170,6 +171,7 @@ module.exports = { ### IPFS Daemon Controller (ctrl) +> The IPFS daemon controller that allows interacting with the spawned IPFS process #### `apiAddr` (getter) @@ -177,7 +179,7 @@ module.exports = { - returns multiaddr -#### `gatewayAddr` (getter) +#### `gatewayAddr` (getter) > Get the address (multiaddr) of connected IPFS HTTP Gateway. diff --git a/examples/id/id.js b/examples/id/id.js index d22aecd7..9652d55b 100644 --- a/examples/id/id.js +++ b/examples/id/id.js @@ -1,10 +1,10 @@ /* eslint no-console: 0 */ 'use strict' -const factory = require('ipfsd-ctl') -const localController = factory.localController +const controllerFactory = require('ipfsd-ctl') +const daemonFactory = controllerFactory() -localController.spawn(function (err, ipfsd) { +daemonFactory.spawn(function (err, ipfsd) { if (err) { throw err } @@ -21,19 +21,19 @@ localController.spawn(function (err, ipfsd) { }) }) -localController.spawn(function (err, ipfsd) { +daemonFactory.spawn(function (err, ipfsd) { if (err) { throw err } - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { if (err) { throw err } console.log('bob') console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() }) }) diff --git a/examples/local-disposable/local-disposable.js b/examples/local-disposable/local-disposable.js index 44dd3a79..86e5c402 100644 --- a/examples/local-disposable/local-disposable.js +++ b/examples/local-disposable/local-disposable.js @@ -4,46 +4,43 @@ // Start a disposable node, and get access to the api // print the node id -// IPFS_PATH will point to /tmp/ipfs_***** and will be -// cleaned up when the process exits. - -const factory = require('ipfsd-ctl') -const localController = factory.localController +const controllerFactory = require('ipfsd-ctl') +const daemonFactory = controllerFactory() // start a go daemon -localController.spawn((err, ipfsd) => { +daemonFactory.spawn((err, ipfsd) => { if (err) { throw err } - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { if (err) { throw err } console.log('go-ipfs') console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() }) }) // start a js daemon -localController.spawn({ isJs: true }, (err, ipfsd) => { +daemonFactory.spawn({ isJs: true }, (err, ipfsd) => { if (err) { throw err } - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { if (err) { throw err } console.log('js-ipfs') console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() }) }) diff --git a/examples/local/local.js b/examples/local/local.js index f86f661f..e70feebf 100644 --- a/examples/local/local.js +++ b/examples/local/local.js @@ -1,11 +1,11 @@ /* eslint no-console: 0 */ 'use strict' -const factory = require('ipfsd-ctl') -const localController = factory.localController +const controllerFactory = require('ipfsd-ctl') +const daemonFactory = controllerFactory() // opens an api connection to local running go-ipfs node -localController.spawn({ disposable: false }, (err, ipfsd) => { +daemonFactory.spawn({ disposable: false }, (err, ipfsd) => { if (err) { throw err } @@ -24,7 +24,7 @@ localController.spawn({ disposable: false }, (err, ipfsd) => { }) // opens an api connection to local running js-ipfs node -localController.spawn({ isJs: true, disposable: false }, (err, ipfsd) => { +daemonFactory.spawn({ isJs: true, disposable: false }, (err, ipfsd) => { if (err) { throw err } diff --git a/examples/remote-disposable/remote-disposable.js b/examples/remote-disposable/remote-disposable.js index 8b561bee..496c8135 100644 --- a/examples/remote-disposable/remote-disposable.js +++ b/examples/remote-disposable/remote-disposable.js @@ -4,32 +4,29 @@ // Start a remote disposable node, and get access to the api // print the node id, and stop the temporary daemon -// IPFS_PATH will point to /tmp/ipfs_***** and will be -// cleaned up when the process exits. - -const ipfsd = require('ipfsd-ctl') -const server = ipfsd.server +const controllerFactory = require('ipfsd-ctl') +const daemonFactory = controllerFactory({ remote: true }) +const server = controllerFactory.server server.start((err) => { if (err) { throw err } - const remoteController = ipfsd.remoteController() - remoteController.spawn(function (err, controller) { + daemonFactory.spawn(function (err, ipfsd) { if (err) { throw err } - const ipfs = controller.ctl - const node = controller.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { if (err) { throw err } console.log(id) - node.stopDaemon(() => server.stop()) + ipfsCtrl.stopDaemon(() => server.stop()) }) }) }) diff --git a/package.json b/package.json index 1364c8e3..7b6c458f 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ ], "license": "MIT", "dependencies": { - "@ljharb/eslint-config": "^12.2.1", "async": "^2.6.0", "debug": "^3.1.0", "detect-node": "^2.0.3", @@ -91,7 +90,7 @@ "aegir": "^12.2.0", "chai": "^4.1.2", "dirty-chai": "^2.0.1", - "ipfs": "^0.27.0", + "ipfs": "~0.27.0", "is-running": "1.0.5", "mkdirp": "^0.5.1", "multihashes": "~0.4.12", diff --git a/src/daemon.js b/src/daemon.js index ae99ecc7..1637c745 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -38,11 +38,10 @@ class Node { const isJs = truthy(process.env.IPFS_JS) this.opts = opts || { isJs: isJs || false } - process.env.IPFS_JS = this.opts.isJs this.path = this.opts.disposable ? tempDir(isJs) : (this.opts.repoPath || tempDir(isJs)) this.disposable = this.opts.disposable - this.exec = process.env.IPFS_EXEC || findIpfsExecutable(this.opts.isJs, rootPath) + this.exec = this.opts.executable || process.env.IPFS_EXEC || findIpfsExecutable(this.opts.isJs, rootPath) this.subprocess = null this.initialized = fs.existsSync(path) this.clean = true @@ -115,7 +114,6 @@ class Node { if (initOpts.directory && initOpts.directory !== this.path) { this.path = initOpts.directory - this.env.IPFS_PATH = this.path } this._run(['init', '-b', keySize], { env: this.env }, (err, result) => { diff --git a/src/index.js b/src/index.js index 853c0aa8..ce8b4cd2 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,19 @@ const localController = require('./local') const remote = require('./remote') +const isNode = require('detect-node') +const defaults = require('lodash.defaultsdeep') -module.exports = { - localController, - remoteController: remote.remoteController, - server: remote.server +function controllerFactory (opts) { + const options = defaults({}, opts, { remote: !isNode }) + + if (options.remote) { + return remote.remoteController(options.port) + } + + return localController } + +controllerFactory.server = remote.server + +module.exports = controllerFactory diff --git a/src/local.js b/src/local.js index 3baf4f63..67d60fe6 100644 --- a/src/local.js +++ b/src/local.js @@ -54,6 +54,7 @@ const IpfsDaemonController = { * - `disposable` bool - a new repo is created and initialized for each invocation * - `config` - ipfs configuration options * - `args` - array of cmd line arguments to be passed to ipfs daemon + * - `executable` - path to the desired IPFS executable to spawn * * @param {Object} [opts={}] - various config options and ipfs config parameters * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` diff --git a/src/remote/server.js b/src/remote/server.js index c43e169f..7813ea06 100644 --- a/src/remote/server.js +++ b/src/remote/server.js @@ -4,22 +4,16 @@ const Hapi = require('hapi') const routes = require('./routes') let server = null -exports.start = function start (port, host, cb) { +exports.start = function start (port, cb) { if (typeof port === 'function') { cb = port port = 9999 } - if (typeof host === 'function') { - cb = host - host = 'localhost' - } - port = port || 9999 - host = host || 'localhost' server = new Hapi.Server() - server.connection({ port, host, routes: { cors: true } }) + server.connection({ port, host: 'localhost', routes: { cors: true } }) routes(server) server.start(cb) diff --git a/test/daemon.js b/test/daemon.js index b8eeec63..076c359c 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -3,18 +3,11 @@ const daemon = require('./spawning') const api = require('./api') -const isNode = require('detect-node') -const factory = require('../src') - -let ipfsdController - -if (isNode) { - ipfsdController = factory.localController -} else { - ipfsdController = factory.remoteController() -} +const controllerFactory = require('../src') describe('ipfsd-ctl', () => { + const daemonFactory = controllerFactory() + // clean up IPFS env afterEach(() => Object.keys(process.env) .forEach((key) => { @@ -24,12 +17,12 @@ describe('ipfsd-ctl', () => { })) describe('Go daemon', () => { - daemon(ipfsdController, false)() - api(ipfsdController, false) + daemon(daemonFactory, false)() + api(daemonFactory, false) }) describe('Js daemon', () => { - daemon(ipfsdController, true)() - api(ipfsdController, false) + daemon(daemonFactory, true)() + api(daemonFactory, false) }) }) diff --git a/test/start-stop.js b/test/start-stop.js index 0b2c3d6c..2559c744 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -12,8 +12,7 @@ const once = require('once') const path = require('path') const exec = require('../src/exec') -const factory = require('../src') -const ipfsdFactory = factory.localController +const daemonFactory = require('../src')() module.exports = (isJs) => { return () => { @@ -23,7 +22,7 @@ module.exports = (isJs) => { describe(`create and init a node (ctlr)`, function () { this.timeout(20 * 1000) before((done) => { - ipfsdFactory.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { + daemonFactory.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() From 2f68fd01ec9816f50c24c069d5212f061dceb239 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 15 Dec 2017 12:24:39 -0600 Subject: [PATCH 42/85] feat: reworking with DaemonFactory --- README.md | 33 +++++++++---------- examples/electron-asar/app.js | 5 +-- examples/id/id.js | 8 ++--- examples/id/package.json | 2 +- examples/local-disposable/local-disposable.js | 8 ++--- examples/local-disposable/package.json | 2 +- examples/local/local.js | 8 ++--- examples/local/package.json | 2 +- examples/remote-disposable/package.json | 2 +- .../remote-disposable/remote-disposable.js | 8 ++--- src/index.js | 20 ++++++----- test/daemon.js | 12 +++---- test/spawning.js | 14 ++++---- test/start-stop.js | 5 +-- 14 files changed, 67 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index ecf9c58d..3f1d9f67 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,10 @@ IPFS daemons are already easy to start and stop, but this module is here to do i // Start a disposable node, and get access to the api // print the node id, and stop the temporary daemon -const controllerFactory = require('ipfsd-ctl') -const daemonFactory = controllerFactory() +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create() -daemonFactory.spawn(function (err, ipfsd) { +df.spawn(function (err, ipfsd) { const ipfsCtl = ipfsd.ctl const ipfsCtrl = ipfsd.ctrl ipfsCtl.id(function (err, id) { @@ -76,16 +76,16 @@ daemonFactory.spawn(function (err, ipfsd) { // Start a remote disposable node, and get access to the api // print the node id, and stop the temporary daemon -const controllerFactory = require('ipfsd-ctl') -const daemonFactory = controllerFactory() +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create() const port = 9999 -daemonFactory.start(port, (err) => { +df.start(port, (err) => { if (err) { throw err } - daemonFactory.spawn(function (err, ipfsd) { + df.spawn(function (err, ipfsd) { const ipfsCtl = ipfsd.ctl const ipfsCtrl = ipfsd.ctrl ipfsCtl.id(function (err, id) { @@ -127,25 +127,24 @@ module.exports = { ### Daemon Factory -#### Create factory +#### Create a `DaemonFactory` ```js -const controllerFactory = require('ipfsd-ctl') -const daemonFactory = controllerFactory() +const DemonFactory = require('ipfsd-ctl') +const df = DemonFactory.create([opts]) ``` -> Create a factory that will expose the `daemonFactory.spawn` method +> Create a factory that will expose the `df.spawn` method - These method return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes - -> `daemonFactory.server` +> `df.server` - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. -#### Spawn nodes +#### Spawn a new daemon with `df.spawn` -> Spawn either a js-ipfs or go-ipfs node through `localController` or `remoteController` +> Spawn either a js-ipfs or go-ipfs node `spawn([options], callback)` @@ -168,6 +167,8 @@ const daemonFactory = controllerFactory() > An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) +This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsdCtrl.startDaemon()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. + ### IPFS Daemon Controller (ctrl) @@ -275,8 +276,6 @@ If no `key` is passed, the whole config is returned as an object. - function(Error, string) callback -For more details see https://ipfs.github.io/js-ipfsd-ctl/. - ### Packaging `ipfsd-ctl` can be packaged in Electron applications, but the ipfs binary diff --git a/examples/electron-asar/app.js b/examples/electron-asar/app.js index a272d805..49098677 100644 --- a/examples/electron-asar/app.js +++ b/examples/electron-asar/app.js @@ -6,7 +6,8 @@ const app = electron.app const ipcMain = electron.ipcMain const BrowserWindow = electron.BrowserWindow -const ipfsd = require('ipfsd-ctl') +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create() app.on('ready', () => { const win = new BrowserWindow({ @@ -19,7 +20,7 @@ ipcMain.on('start', ({ sender }) => { console.log('starting disposable IPFS') sender.send('message', 'starting disposable IPFS') - ipfsd.localController.spawn((err, ipfsd) => { + df.spawn((err, ipfsd) => { if (err) { sender.send('error', err) throw err diff --git a/examples/id/id.js b/examples/id/id.js index 9652d55b..fc018bf4 100644 --- a/examples/id/id.js +++ b/examples/id/id.js @@ -1,10 +1,10 @@ /* eslint no-console: 0 */ 'use strict' -const controllerFactory = require('ipfsd-ctl') -const daemonFactory = controllerFactory() +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create() -daemonFactory.spawn(function (err, ipfsd) { +df.spawn(function (err, ipfsd) { if (err) { throw err } @@ -21,7 +21,7 @@ daemonFactory.spawn(function (err, ipfsd) { }) }) -daemonFactory.spawn(function (err, ipfsd) { +df.spawn(function (err, ipfsd) { if (err) { throw err } diff --git a/examples/id/package.json b/examples/id/package.json index 3294355e..ccfa45a2 100644 --- a/examples/id/package.json +++ b/examples/id/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../.." + "ipfsd-ctl": "file:../" }, "author": "", "license": "MIT" diff --git a/examples/local-disposable/local-disposable.js b/examples/local-disposable/local-disposable.js index 86e5c402..902ca477 100644 --- a/examples/local-disposable/local-disposable.js +++ b/examples/local-disposable/local-disposable.js @@ -4,11 +4,11 @@ // Start a disposable node, and get access to the api // print the node id -const controllerFactory = require('ipfsd-ctl') -const daemonFactory = controllerFactory() +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create() // start a go daemon -daemonFactory.spawn((err, ipfsd) => { +df.spawn((err, ipfsd) => { if (err) { throw err } @@ -27,7 +27,7 @@ daemonFactory.spawn((err, ipfsd) => { }) // start a js daemon -daemonFactory.spawn({ isJs: true }, (err, ipfsd) => { +df.spawn({ isJs: true }, (err, ipfsd) => { if (err) { throw err } diff --git a/examples/local-disposable/package.json b/examples/local-disposable/package.json index 4f992a61..089a296b 100644 --- a/examples/local-disposable/package.json +++ b/examples/local-disposable/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../.." + "ipfsd-ctl": "file:../" }, "author": "", "license": "MIT" diff --git a/examples/local/local.js b/examples/local/local.js index e70feebf..9346b5a7 100644 --- a/examples/local/local.js +++ b/examples/local/local.js @@ -1,11 +1,11 @@ /* eslint no-console: 0 */ 'use strict' -const controllerFactory = require('ipfsd-ctl') -const daemonFactory = controllerFactory() +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create() // opens an api connection to local running go-ipfs node -daemonFactory.spawn({ disposable: false }, (err, ipfsd) => { +df.spawn({ disposable: false }, (err, ipfsd) => { if (err) { throw err } @@ -24,7 +24,7 @@ daemonFactory.spawn({ disposable: false }, (err, ipfsd) => { }) // opens an api connection to local running js-ipfs node -daemonFactory.spawn({ isJs: true, disposable: false }, (err, ipfsd) => { +df.spawn({ isJs: true, disposable: false }, (err, ipfsd) => { if (err) { throw err } diff --git a/examples/local/package.json b/examples/local/package.json index d4bd3e00..2124c6cb 100644 --- a/examples/local/package.json +++ b/examples/local/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../.." + "ipfsd-ctl": "file:../" }, "author": "", "license": "MIT" diff --git a/examples/remote-disposable/package.json b/examples/remote-disposable/package.json index 61885524..b5e79ae5 100644 --- a/examples/remote-disposable/package.json +++ b/examples/remote-disposable/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../.." + "ipfsd-ctl": "file:../" }, "author": "", "license": "MIT" diff --git a/examples/remote-disposable/remote-disposable.js b/examples/remote-disposable/remote-disposable.js index 496c8135..4df13923 100644 --- a/examples/remote-disposable/remote-disposable.js +++ b/examples/remote-disposable/remote-disposable.js @@ -4,16 +4,16 @@ // Start a remote disposable node, and get access to the api // print the node id, and stop the temporary daemon -const controllerFactory = require('ipfsd-ctl') -const daemonFactory = controllerFactory({ remote: true }) -const server = controllerFactory.server +const DaemonFactory = require('ipfsd-ctl') +const df = DaemonFactory.create({ remote: true }) +const server = DaemonFactory.server server.start((err) => { if (err) { throw err } - daemonFactory.spawn(function (err, ipfsd) { + df.spawn(function (err, ipfsd) { if (err) { throw err } diff --git a/src/index.js b/src/index.js index ce8b4cd2..f4a477ba 100644 --- a/src/index.js +++ b/src/index.js @@ -5,16 +5,20 @@ const remote = require('./remote') const isNode = require('detect-node') const defaults = require('lodash.defaultsdeep') -function controllerFactory (opts) { - const options = defaults({}, opts, { remote: !isNode }) +class DaemonFactory { + static create (opts) { + const options = defaults({}, opts, { remote: !isNode }) - if (options.remote) { - return remote.remoteController(options.port) + if (options.remote) { + return remote.remoteController(options.port) + } + + return localController } - return localController + static get server () { + return remote.server + } } -controllerFactory.server = remote.server - -module.exports = controllerFactory +module.exports = DaemonFactory diff --git a/test/daemon.js b/test/daemon.js index 076c359c..11a22736 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -3,10 +3,10 @@ const daemon = require('./spawning') const api = require('./api') -const controllerFactory = require('../src') +const DaemonFactory = require('../src') describe('ipfsd-ctl', () => { - const daemonFactory = controllerFactory() + const df = DaemonFactory.create() // clean up IPFS env afterEach(() => Object.keys(process.env) @@ -17,12 +17,12 @@ describe('ipfsd-ctl', () => { })) describe('Go daemon', () => { - daemon(daemonFactory, false)() - api(daemonFactory, false) + daemon(df, false)() + api(df, false) }) describe('Js daemon', () => { - daemon(daemonFactory, true)() - api(daemonFactory, false) + daemon(df, true)() + api(df, false) }) }) diff --git a/test/spawning.js b/test/spawning.js index 03673022..3aa80a52 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -18,7 +18,7 @@ function tempDir (isJs) { return path.join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) } -module.exports = (ipfsdController, isJs) => { +module.exports = (df, isJs) => { return () => { const VERSION_STRING = isJs ? `js-ipfs version: ${require('ipfs/package.json').version}` : 'ipfs version 0.4.13' @@ -26,7 +26,7 @@ module.exports = (ipfsdController, isJs) => { if (!isNode) { this.skip() } - ipfsdController.version({ isJs }, (err, version) => { + df.version({ isJs }, (err, version) => { expect(err).to.not.exist() expect(version).to.be.eql(VERSION_STRING) done() @@ -44,7 +44,7 @@ module.exports = (ipfsdController, isJs) => { }) it('create node', function (done) { - ipfsdController.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() @@ -87,7 +87,7 @@ module.exports = (ipfsdController, isJs) => { it('create node and init', function (done) { this.timeout(30 * 1000) - ipfsdController.spawn({ isJs, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ isJs, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() @@ -121,7 +121,7 @@ module.exports = (ipfsdController, isJs) => { it('create init and start node', function (done) { this.timeout(20 * 1000) - ipfsdController.spawn({ isJs }, (err, ipfsd) => { + df.spawn({ isJs }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.exist() @@ -161,7 +161,7 @@ module.exports = (ipfsdController, isJs) => { let node async.waterfall([ - (cb) => ipfsdController.spawn(options, cb), + (cb) => df.spawn(options, cb), (ipfsd, cb) => { node = ipfsd.ctrl node.getConfig('Addresses.API', (err, res) => { @@ -189,7 +189,7 @@ module.exports = (ipfsdController, isJs) => { before(function (done) { this.timeout(20 * 1000) - ipfsdController.spawn({ isJs }, (err, res) => { + df.spawn({ isJs }, (err, res) => { if (err) { return done(err) } diff --git a/test/start-stop.js b/test/start-stop.js index 2559c744..59e18581 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -12,7 +12,8 @@ const once = require('once') const path = require('path') const exec = require('../src/exec') -const daemonFactory = require('../src')() +const DaemonFactory = require('../src') +const df = DaemonFactory.create() module.exports = (isJs) => { return () => { @@ -22,7 +23,7 @@ module.exports = (isJs) => { describe(`create and init a node (ctlr)`, function () { this.timeout(20 * 1000) before((done) => { - daemonFactory.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() From d970ee5b4a87e1c969543adb7fec910fff16cb97 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 15 Dec 2017 13:02:41 -0600 Subject: [PATCH 43/85] docs: small tweaks to the readme --- README.md | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 3f1d9f67..5ee771a2 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,9 @@ df.spawn(function (err, ipfsd) { // print the node id, and stop the temporary daemon const DaemonFactory = require('ipfsd-ctl') -const df = DaemonFactory.create() const port = 9999 +const df = DaemonFactory.create({remote: true, port}) df.start(port, (err) => { if (err) { throw err @@ -136,9 +136,11 @@ const df = DemonFactory.create([opts]) > Create a factory that will expose the `df.spawn` method -- These method return a factory that exposes the `spawn` method, which allows spawning and controlling ipfs nodes +- `opts` - an optional object with the following properties + - `remote` bool - indicates if the factory should spawn local or remote nodes. By default, local nodes are spawned in Node.js and remote nodes are spawned in Browser environments. + - `port` number - the port number to use for the remote factory. It should match the port on which `df.server` was started. Defaults to 9999. -> `df.server` +> `df.server` the bundled HTTP server used by the remote controller. - exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. @@ -198,35 +200,35 @@ This instance is returned for each successfully started IPFS daemon, when either - returns boolean -#### `init (initOpts, callback)` +#### `init ([initOpts], callback)` > Initialize a repo. -- initOpts (optional) - options object with the following entries - - keysize (default 2048) - The bit size of the identiy key. - - directory (default IPFS_PATH) - The location of the repo. - - function (Error, Node) callback - receives an instance of this Node on success or an instance of `Error` on failure +- `initOpts` (optional) - options object with the following entries + - `keysize` (default 2048) - The bit size of the identiy key. + - `directory` (default IPFS_PATH) - The location of the repo. + - `function (Error, Node)` callback - receives an instance of this Node on success or an instance of `Error` on failure #### `shutdown (callback)` > Delete the repo that was being used. If the node was marked as `disposable` this will be called automatically when the process is exited. -- function(Error) callback +- `function(Error)` callback #### `startDaemon (flags, callback)` > Start the daemon. -- flags - Flags array to be passed to the `ipfs daemon` command. -- function(Error, IpfsApi)} callback - function that receives an instance of `ipfs-api` on success or an instance of `Error` on failure +- `flags` - Flags array to be passed to the `ipfs daemon` command. +- `function(Error, IpfsApi)}` callback - function that receives an instance of `ipfs-api` on success or an instance of `Error` on failure #### `stopDaemon (callback)` > Stop the daemon. -- function(Error) callback - function that receives an instance of `Error` on failure +- `function(Error)` callback - function that receives an instance of `Error` on failure #### `killProcess (callback)` @@ -234,7 +236,7 @@ This instance is returned for each successfully started IPFS daemon, when either First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process hasn't exited yet. -- function() callback - Called once the process is killed +- `function()` callback - Called once the process is killed #### `daemonPid ()` @@ -250,31 +252,31 @@ First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process has If no `key` is passed, the whole config is returned as an object. -- key (optional) - A specific config to retrieve. -- function(Error, (Object|string) callback - function that reseives an object or string on success or an `Error` instance on failure +- `key` (optional) - A specific config to retrieve. +- `function(Error, (Object|string)` callback - function that reseives an object or string on success or an `Error` instance on failure #### `setConfig (key, value, callback)` > Set a config value. -- key - the key to set -- value - the value to set the key to -- function(Error) callback +- `key` - the key to set +- `value` - the value to set the key to +- `function(Error)` callback #### `replaceConf (file, callback)` > Replace the configuration with a given file -- file - path to the new config file -- function(Error) callback +- `file` - path to the new config file +- `function(Error)` callback #### `version (callback)` > Get the version of ipfs -- function(Error, string) callback +- `function(Error, string)` callback ### Packaging From 28ac0339979ec770db8e3b5716f420c29b7b54de Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 15 Dec 2017 20:18:47 -0600 Subject: [PATCH 44/85] fix: naming conventions --- examples/id/id.js | 8 +-- examples/id/package.json | 2 +- examples/local-disposable/package.json | 2 +- examples/local/local.js | 16 ++--- examples/local/package.json | 2 +- examples/remote-disposable/package.json | 2 +- test/add-retrive.js | 10 ++-- test/api.js | 52 ++++++++--------- test/spawning.js | 77 ++++++++++++------------- test/start-stop.js | 28 ++++----- 10 files changed, 99 insertions(+), 100 deletions(-) diff --git a/examples/id/id.js b/examples/id/id.js index fc018bf4..57fa01ce 100644 --- a/examples/id/id.js +++ b/examples/id/id.js @@ -9,15 +9,15 @@ df.spawn(function (err, ipfsd) { throw err } - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { if (err) { throw err } console.log('alice') console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() }) }) diff --git a/examples/id/package.json b/examples/id/package.json index ccfa45a2..3294355e 100644 --- a/examples/id/package.json +++ b/examples/id/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../" + "ipfsd-ctl": "file:../.." }, "author": "", "license": "MIT" diff --git a/examples/local-disposable/package.json b/examples/local-disposable/package.json index 089a296b..4f992a61 100644 --- a/examples/local-disposable/package.json +++ b/examples/local-disposable/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../" + "ipfsd-ctl": "file:../.." }, "author": "", "license": "MIT" diff --git a/examples/local/local.js b/examples/local/local.js index 9346b5a7..acf91bd9 100644 --- a/examples/local/local.js +++ b/examples/local/local.js @@ -10,16 +10,16 @@ df.spawn({ disposable: false }, (err, ipfsd) => { throw err } - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { if (err) { throw err } console.log('go-ipfs') console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() }) }) @@ -29,15 +29,15 @@ df.spawn({ isJs: true, disposable: false }, (err, ipfsd) => { throw err } - const ipfs = ipfsd.ctl - const node = ipfsd.ctrl - ipfs.id(function (err, id) { + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl + ipfsCtl.id(function (err, id) { if (err) { throw err } console.log('js-ipfs') console.log(id) - node.stopDaemon() + ipfsCtrl.stopDaemon() }) }) diff --git a/examples/local/package.json b/examples/local/package.json index 2124c6cb..d4bd3e00 100644 --- a/examples/local/package.json +++ b/examples/local/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../" + "ipfsd-ctl": "file:../.." }, "author": "", "license": "MIT" diff --git a/examples/remote-disposable/package.json b/examples/remote-disposable/package.json index b5e79ae5..61885524 100644 --- a/examples/remote-disposable/package.json +++ b/examples/remote-disposable/package.json @@ -7,7 +7,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "ipfsd-ctl": "file:../" + "ipfsd-ctl": "file:../.." }, "author": "", "license": "MIT" diff --git a/test/add-retrive.js b/test/add-retrive.js index 2fb48691..321c7dfe 100644 --- a/test/add-retrive.js +++ b/test/add-retrive.js @@ -16,10 +16,10 @@ module.exports = () => { before(function (done) { this.timeout(30 * 1000) async.waterfall([ - (cb) => this.api.block.put(blorb, cb), + (cb) => this.ipfsCtl.block.put(blorb, cb), (block, cb) => { store = block.cid.toBaseEncodedString() - this.api.block.get(store, cb) + this.ipfsCtl.block.get(store, cb) }, (_block, cb) => { retrieve = _block.data @@ -38,9 +38,9 @@ module.exports = () => { }) it('should have started the daemon and returned an api with host/port', function () { - expect(this.api).to.have.property('id') - expect(this.api).to.have.property('apiHost') - expect(this.api).to.have.property('apiPort') + expect(this.ipfsCtl).to.have.property('id') + expect(this.ipfsCtl).to.have.property('apiHost') + expect(this.ipfsCtl).to.have.property('apiPort') }) it('should be able to store objects', () => { diff --git a/test/api.js b/test/api.js index c9aae194..5d0bfa71 100644 --- a/test/api.js +++ b/test/api.js @@ -21,23 +21,23 @@ module.exports = (ipfsdController, isJs) => { const GW_PORT = isJs ? '9090' : '8080' describe('ipfs-api version', () => { - let ipfs - let node + let ipfsCtl + let ipfsCtrl before(function (done) { this.timeout(20 * 1000) - ipfsdController.spawn({ start: false }, (err, ret) => { + ipfsdController.spawn({ start: false }, (err, ipfsd) => { expect(err).to.not.exist() - node = ret.ctrl - node.startDaemon((err, ignore) => { + ipfsCtrl = ipfsd.ctrl + ipfsCtrl.startDaemon((err, ignore) => { expect(err).to.not.exist() - ipfs = ipfsApi(node.apiAddr) + ipfsCtl = ipfsApi(ipfsCtrl.apiAddr) done() }) }) }) - after((done) => node.stopDaemon(done)) + after((done) => ipfsCtrl.stopDaemon(done)) // skip on windows for now // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 @@ -47,7 +47,7 @@ module.exports = (ipfsdController, isJs) => { } it('uses the correct ipfs-api', (done) => { - ipfs.util.addFromFs(path.join(__dirname, 'fixtures/'), { + ipfsCtl.util.addFromFs(path.join(__dirname, 'fixtures/'), { recursive: true }, (err, res) => { expect(err).to.not.exist() @@ -89,28 +89,28 @@ module.exports = (ipfsdController, isJs) => { this.timeout(20 * 1000) ipfsdController.spawn({ isJs, config: null }, (err, ipfsd) => { expect(err).to.not.exist() - const api = ipfsd.ctl - const node = ipfsd.ctrl + const ipfsCtl = ipfsd.ctl + const ipfsCtrl = ipfsd.ctrl // Check for props in daemon - expect(node).to.have.property('apiAddr') - expect(node).to.have.property('gatewayAddr') - expect(node.apiAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(node.apiAddr)).to.equal(true) - expect(node.gatewayAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(node.gatewayAddr)).to.equal(true) + expect(ipfsCtrl).to.have.property('apiAddr') + expect(ipfsCtrl).to.have.property('gatewayAddr') + expect(ipfsCtrl.apiAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(ipfsCtrl.apiAddr)).to.equal(true) + expect(ipfsCtrl.gatewayAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(ipfsCtrl.gatewayAddr)).to.equal(true) // Check for props in ipfs-api instance - expect(api).to.have.property('apiHost') - expect(api).to.have.property('apiPort') - expect(api).to.have.property('gatewayHost') - expect(api).to.have.property('gatewayPort') - expect(api.apiHost).to.equal('127.0.0.1') - expect(api.apiPort).to.equal(API_PORT) - expect(api.gatewayHost).to.equal('127.0.0.1') - expect(api.gatewayPort).to.equal(GW_PORT) - - node.stopDaemon(done) + expect(ipfsCtl).to.have.property('apiHost') + expect(ipfsCtl).to.have.property('apiPort') + expect(ipfsCtl).to.have.property('gatewayHost') + expect(ipfsCtl).to.have.property('gatewayPort') + expect(ipfsCtl.apiHost).to.equal('127.0.0.1') + expect(ipfsCtl.apiPort).to.equal(API_PORT) + expect(ipfsCtl.gatewayHost).to.equal('127.0.0.1') + expect(ipfsCtl.gatewayPort).to.equal(GW_PORT) + + ipfsCtrl.stopDaemon(done) }) }) diff --git a/test/spawning.js b/test/spawning.js index 3aa80a52..8321c94f 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -35,12 +35,12 @@ module.exports = (df, isJs) => { describe('daemon spawning', () => { describe('spawn a bare node', function () { - this.node = null - this.api = null + this.ipfsCtrl = null + this.ipfsCtl = null after(function (done) { this.timeout(20 * 1000) - this.node.stopDaemon(done) + this.ipfsCtrl.stopDaemon(done) }) it('create node', function (done) { @@ -48,27 +48,27 @@ module.exports = (df, isJs) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() - this.node = ipfsd.ctrl + this.ipfsCtrl = ipfsd.ctrl done() }) }) it('init node', function (done) { this.timeout(20 * 1000) - this.node.init((err) => { + this.ipfsCtrl.init((err) => { expect(err).to.not.exist() - expect(this.node.initialized).to.be.ok() + expect(this.ipfsCtrl.initialized).to.be.ok() done() }) }) it('start node', function (done) { this.timeout(30 * 1000) - this.node.startDaemon((err, a) => { - this.api = a + this.ipfsCtrl.startDaemon((err, ipfs) => { + this.ipfsCtl = ipfs expect(err).to.not.exist() - expect(this.api).to.exist() - expect(this.api.id).to.exist() + expect(this.ipfsCtl).to.exist() + expect(this.ipfsCtl.id).to.exist() done() }) }) @@ -77,12 +77,12 @@ module.exports = (df, isJs) => { }) describe('spawn an initialized node', function () { - this.node = null - this.api = null + this.ipfsCtrl = null + this.ipfsCtl = null after(function (done) { this.timeout(20 * 1000) - this.node.stopDaemon(done) + this.ipfsCtrl.stopDaemon(done) }) it('create node and init', function (done) { @@ -91,18 +91,18 @@ module.exports = (df, isJs) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() - this.node = ipfsd.ctrl + this.ipfsCtrl = ipfsd.ctrl done() }) }) it('start node', function (done) { this.timeout(30 * 1000) - this.node.startDaemon((err, a) => { - this.api = a + this.ipfsCtrl.startDaemon((err, ipfs) => { + this.ipfsCtl = ipfs expect(err).to.not.exist() - expect(this.api).to.exist() - expect(this.api.id).to.exist() + expect(this.ipfsCtl).to.exist() + expect(this.ipfsCtl.id).to.exist() done() }) }) @@ -111,12 +111,12 @@ module.exports = (df, isJs) => { }) describe('spawn a node and attach api', () => { - this.node = null - this.api = null + this.ipfsCtrl = null + this.ipfsCtl = null after(function (done) { this.timeout(20 * 1000) - this.node.stopDaemon(done) + this.ipfsCtrl.stopDaemon(done) }) it('create init and start node', function (done) { @@ -126,8 +126,8 @@ module.exports = (df, isJs) => { expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.exist() expect(ipfsd.ctl.id).to.exist() - this.node = ipfsd.ctrl - this.api = ipfsd.ctl + this.ipfsCtrl = ipfsd.ctrl + this.ipfsCtl = ipfsd.ctl done() }) }) @@ -159,19 +159,19 @@ module.exports = (df, isJs) => { isJs } - let node + let ipfsCtrl async.waterfall([ (cb) => df.spawn(options, cb), (ipfsd, cb) => { - node = ipfsd.ctrl - node.getConfig('Addresses.API', (err, res) => { + ipfsCtrl = ipfsd.ctrl + ipfsCtrl.getConfig('Addresses.API', (err, res) => { expect(err).to.not.exist() expect(res).to.be.eql(addr) cb() }) }, (cb) => { - node.getConfig('Addresses.Swarm', (err, res) => { + ipfsCtrl.getConfig('Addresses.Swarm', (err, res) => { expect(err).to.not.exist() expect(JSON.parse(res)).to.deep.eql([swarmAddr1, swarmAddr2]) cb() @@ -179,29 +179,29 @@ module.exports = (df, isJs) => { } ], (err) => { expect(err).to.not.exist() - node.stopDaemon(done) + ipfsCtrl.stopDaemon(done) }) }) }) describe('change config of a disposable node', () => { - let node + let ipfsCtrl before(function (done) { this.timeout(20 * 1000) - df.spawn({ isJs }, (err, res) => { + df.spawn({ isJs }, (err, ipfsd) => { if (err) { return done(err) } - node = res.ctrl + ipfsCtrl = ipfsd.ctrl done() }) }) - after((done) => node.stopDaemon(done)) + after((done) => ipfsCtrl.stopDaemon(done)) it('Should return a config value', (done) => { - node.getConfig('Bootstrap', (err, config) => { + ipfsCtrl.getConfig('Bootstrap', (err, config) => { expect(err).to.not.exist() expect(config).to.exist() done() @@ -209,18 +209,17 @@ module.exports = (df, isJs) => { }) it('Should return the whole config', (done) => { - node.getConfig((err, config) => { + ipfsCtrl.getConfig((err, config) => { expect(err).to.not.exist() expect(config).to.exist() done() }) }) - // TODO: skip until https://github.com/ipfs/js-ipfs/pull/1134 is merged - it.skip('Should set a config value', (done) => { + it('Should set a config value', (done) => { async.series([ - (cb) => node.setConfig('Bootstrap', 'null', cb), - (cb) => node.getConfig('Bootstrap', cb) + (cb) => ipfsCtrl.setConfig('Bootstrap', 'null', cb), + (cb) => ipfsCtrl.getConfig('Bootstrap', cb) ], (err, res) => { expect(err).to.not.exist() expect(res[1]).to.be.eql('null') @@ -232,7 +231,7 @@ module.exports = (df, isJs) => { if (isJs) { this.skip() // js doesn't fail on invalid config } else { - node.setConfig('Bootstrap', 'true', (err) => { + ipfsCtrl.setConfig('Bootstrap', 'true', (err) => { expect(err.message).to.match(/failed to set config value/) done() }) diff --git a/test/start-stop.js b/test/start-stop.js index 59e18581..59f3eec1 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -18,7 +18,7 @@ const df = DaemonFactory.create() module.exports = (isJs) => { return () => { describe('starting and stopping', () => { - let node + let ipfsCtrl describe(`create and init a node (ctlr)`, function () { this.timeout(20 * 1000) @@ -27,32 +27,32 @@ module.exports = (isJs) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() - node = ipfsd.ctrl + ipfsCtrl = ipfsd.ctrl done() }) }) - it('should returned a node', () => { - expect(node).to.exist() + it('should return a node', () => { + expect(ipfsCtrl).to.exist() }) it('daemon should not be running', () => { - expect(node.daemonPid()).to.not.exist() + expect(ipfsCtrl.daemonPid()).to.not.exist() }) }) let pid describe('starting', () => { - let ipfs + let ipfsCtl before(function (done) { this.timeout(20 * 1000) - node.startDaemon((err, res) => { + ipfsCtrl.startDaemon((err, ipfs) => { expect(err).to.not.exist() - pid = node.daemonPid() - ipfs = res + pid = ipfsCtrl.daemonPid() + ipfsCtl = ipfs // actually running? done = once(done) @@ -61,7 +61,7 @@ module.exports = (isJs) => { }) it('should be running', () => { - expect(ipfs.id).to.exist() + expect(ipfsCtl.id).to.exist() }) }) @@ -69,7 +69,7 @@ module.exports = (isJs) => { let stopped = false before((done) => { - node.stopDaemon((err) => { + ipfsCtrl.stopDaemon((err) => { expect(err).to.not.exist() stopped = true }) @@ -90,10 +90,10 @@ module.exports = (isJs) => { it('should be stopped', function () { this.timeout(30 * 1000) // shutdown grace period is already 10500 - expect(node.daemonPid()).to.not.exist() + expect(ipfsCtrl.daemonPid()).to.not.exist() expect(stopped).to.equal(true) - expect(fs.existsSync(path.join(node.path, 'repo.lock'))).to.not.be.ok() - expect(fs.existsSync(path.join(node.path, 'api'))).to.not.be.ok() + expect(fs.existsSync(path.join(ipfsCtrl.path, 'repo.lock'))).to.not.be.ok() + expect(fs.existsSync(path.join(ipfsCtrl.path, 'api'))).to.not.be.ok() }) }) }) From 5d1c5a449d4e97201694e1d97076c7e6c35186da Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Mon, 18 Dec 2017 17:18:32 -0600 Subject: [PATCH 45/85] wip: reworking with latest suggestions --- .aegir.js | 7 ++++--- README.md | 27 ++++++++++++--------------- package.json | 1 - src/daemon.js | 9 +++++---- src/index.js | 4 ++-- src/local.js | 3 ++- src/remote/index.js | 4 ++-- src/remote/routes.js | 4 ++-- src/remote/server.js | 31 +++++++++++++++++-------------- src/utils.js | 12 +++++++----- test/daemon.js | 8 ++++---- test/node.js | 8 ++++---- test/npm-installs.js | 12 ++++++------ test/spawning.js | 26 ++++++++++++++------------ test/start-stop.js | 4 ++-- test/utils.js | 4 ++-- 16 files changed, 85 insertions(+), 79 deletions(-) diff --git a/.aegir.js b/.aegir.js index d2791092..5e4c3224 100644 --- a/.aegir.js +++ b/.aegir.js @@ -1,7 +1,8 @@ 'use strict' -const server = require('./src').server +const createServer = require('./src').createServer +const server = createServer() module.exports = { karma: { files: [{ @@ -14,8 +15,8 @@ module.exports = { }, hooks: { browser: { - pre: server.start, - post: server.stop + pre: server.start.bind(server), + post: server.stop.bind(server) } } } diff --git a/README.md b/README.md index 5ee771a2..baf90910 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,9 @@ df.spawn(function (err, ipfsd) { const DaemonFactory = require('ipfsd-ctl') const port = 9999 +const server = DaemonFactory.createServer(port) const df = DaemonFactory.create({remote: true, port}) -df.start(port, (err) => { +server.start(port, (err) => { if (err) { throw err } @@ -93,7 +94,7 @@ df.start(port, (err) => { ipfsCtrl.stopDaemon() server.stop() }) - }) + }) }) ``` @@ -102,8 +103,9 @@ It's also possible to start the server from `.aegir` `pre` and `post` hooks. ```js 'use strict' -const server = require('./src').server +const createServer = require('./src').createServer +const server = createServer() module.exports = { karma: { files: [{ @@ -116,8 +118,8 @@ module.exports = { }, hooks: { browser: { - pre: server.start, - post: server.stop + pre: server.start.bind(server), + post: server.stop.bind(server) } } } @@ -129,20 +131,15 @@ module.exports = { #### Create a `DaemonFactory` -```js -const DemonFactory = require('ipfsd-ctl') -const df = DemonFactory.create([opts]) -``` - -> Create a factory that will expose the `df.spawn` method +> `DaemonFactory.create([options])` create a factory that will expose the `df.spawn` method -- `opts` - an optional object with the following properties +- `options` - an optional object with the following properties - `remote` bool - indicates if the factory should spawn local or remote nodes. By default, local nodes are spawned in Node.js and remote nodes are spawned in Browser environments. - - `port` number - the port number to use for the remote factory. It should match the port on which `df.server` was started. Defaults to 9999. + - `port` number - the port number to use for the remote factory. It should match the port on which `DaemonFactory.server` was started. Defaults to 9999. -> `df.server` the bundled HTTP server used by the remote controller. +> `DaemonFactory.createServer` create an instance of the bundled HTTP server used by the remote controller. -- exposes `start` and `stop` methods to start and stop the bundled http server that is required to run the remote controller. +- exposes `start` and `stop` methods to start and stop the http server. #### Spawn a new daemon with `df.spawn` diff --git a/package.json b/package.json index 7b6c458f..0274a124 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,6 @@ "detect-node": "^2.0.3", "eslint-config-standard-jsx": "^4.0.2", "go-ipfs-dep": "0.4.13", - "guid": "0.0.12", "hapi": "^16.6.2", "hat": "0.0.3", "http-browserify": "^1.7.0", diff --git a/src/daemon.js b/src/daemon.js index 1637c745..4115dffc 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -35,13 +35,14 @@ class Node { */ constructor (opts) { const rootPath = process.env.testpath ? process.env.testpath : __dirname - const isJs = truthy(process.env.IPFS_JS) + const type = truthy(process.env.IPFS_TYPE) - this.opts = opts || { isJs: isJs || false } + this.opts = opts || { type: type || 'go' } - this.path = this.opts.disposable ? tempDir(isJs) : (this.opts.repoPath || tempDir(isJs)) + const tmpDir = tempDir(opts.type === 'js') + this.path = this.opts.disposable ? tmpDir : (this.opts.repoPath || tmpDir) this.disposable = this.opts.disposable - this.exec = this.opts.executable || process.env.IPFS_EXEC || findIpfsExecutable(this.opts.isJs, rootPath) + this.exec = this.opts.executable || process.env.IPFS_EXEC || findIpfsExecutable(this.opts.type, rootPath) this.subprocess = null this.initialized = fs.existsSync(path) this.clean = true diff --git a/src/index.js b/src/index.js index f4a477ba..8887b41e 100644 --- a/src/index.js +++ b/src/index.js @@ -16,8 +16,8 @@ class DaemonFactory { return localController } - static get server () { - return remote.server + static createServer (port) { + return new remote.Server(port) } } diff --git a/src/local.js b/src/local.js index 67d60fe6..d552412f 100644 --- a/src/local.js +++ b/src/local.js @@ -8,6 +8,7 @@ const flatten = require('./utils').flatten const Node = require('./daemon') const defaultOptions = { + type: 'go', disposable: true, start: true, init: true @@ -47,7 +48,7 @@ const IpfsDaemonController = { * Spawn an IPFS node, either js-ipfs or go-ipfs * * Options are: - * - `js` bool - spawn a js or go node (default go) + * - `type` string (default 'go') - the type of the daemon to spawn, can be either 'go' or 'js' * - `init` bool - should the node be initialized * - `start` bool - should the node be started * - `repoPath` string - the repository path to use for this node, ignored if node is disposable diff --git a/src/remote/index.js b/src/remote/index.js index 2449bcd6..93f4d0aa 100644 --- a/src/remote/index.js +++ b/src/remote/index.js @@ -1,9 +1,9 @@ 'use strict' -const server = require('./server') +const Server = require('./server') const remoteController = require('./client') module.exports = { - server, + Server, remoteController } diff --git a/src/remote/routes.js b/src/remote/routes.js index 236a0d4a..c19650a5 100644 --- a/src/remote/routes.js +++ b/src/remote/routes.js @@ -1,7 +1,7 @@ 'use strict' const ipfsFactory = require('../local') -const guid = require('guid') +const hat = require('hat') const boom = require('boom') const utils = require('./utils') @@ -24,7 +24,7 @@ module.exports = (server) => { return reply(boom.badRequest(err)) } - const id = guid.raw() + const id = hat() nodes[id] = ipfsd.ctrl let api = null diff --git a/src/remote/server.js b/src/remote/server.js index 7813ea06..49035fe1 100644 --- a/src/remote/server.js +++ b/src/remote/server.js @@ -3,23 +3,26 @@ const Hapi = require('hapi') const routes = require('./routes') -let server = null -exports.start = function start (port, cb) { - if (typeof port === 'function') { - cb = port - port = 9999 +class Server { + constructor (port) { + this.server = null + this.port = port || 9999 } - port = port || 9999 + start (cb) { + cb = cb || (() => {}) - server = new Hapi.Server() - server.connection({ port, host: 'localhost', routes: { cors: true } }) + this.server = new Hapi.Server() + this.server.connection({ port: this.port, host: 'localhost', routes: { cors: true } }) - routes(server) - server.start(cb) -} + routes(this.server) + this.server.start(cb) + } -exports.stop = function stop (cb) { - cb = cb || (() => {}) - server.stop(cb) + stop (cb) { + cb = cb || (() => {}) + this.server.stop(cb) + } } + +module.exports = Server diff --git a/src/utils.js b/src/utils.js index d4d56a1f..091945c1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -67,7 +67,12 @@ exports.tempDir = (isJs) => { return join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${hat()}`) } -exports.findIpfsExecutable = (isJs, rootPath) => { +exports.findIpfsExecutable = (type, rootPath) => { + const execPath = { + go: path.join('go-ipfs-dep', 'go-ipfs', isWindows ? 'ipfs.exe' : 'ipfs'), + js: path.join('ipfs', 'src', 'cli', 'bin.js') + } + let appRoot = rootPath ? path.join(rootPath, '..') : process.cwd() // If inside .asar try to load from .asar.unpacked // this only works if asar was built with @@ -77,10 +82,7 @@ exports.findIpfsExecutable = (isJs, rootPath) => { if (appRoot.includes(`.asar${path.sep}`)) { appRoot = appRoot.replace(`.asar${path.sep}`, `.asar.unpacked${path.sep}`) } - const appName = isWindows ? 'ipfs.exe' : 'ipfs' - const depPath = isJs - ? path.join('ipfs', 'src', 'cli', 'bin.js') - : path.join('go-ipfs-dep', 'go-ipfs', appName) + const depPath = execPath[type] const npm3Path = path.join(appRoot, '../', depPath) const npm2Path = path.join(appRoot, 'node_modules', depPath) diff --git a/test/daemon.js b/test/daemon.js index 11a22736..d708db2e 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -17,12 +17,12 @@ describe('ipfsd-ctl', () => { })) describe('Go daemon', () => { - daemon(df, false)() - api(df, false) + daemon(df, 'go')() + api(df, 'go') }) describe('Js daemon', () => { - daemon(df, true)() - api(df, false) + daemon(df, 'js')() + api(df, 'js') }) }) diff --git a/test/node.js b/test/node.js index b379988f..b0ceeca4 100644 --- a/test/node.js +++ b/test/node.js @@ -11,12 +11,12 @@ const install = require('./npm-installs') describe('node', () => { describe('cleanup', () => { - startStop(true)() - startStop(false)() + startStop('go')() + startStop('js')() }) describe('install', () => { - install(true)() - install(false)() + install('go')() + install('js')() }) }) diff --git a/test/npm-installs.js b/test/npm-installs.js index 95c7b3bd..b92cf6ac 100644 --- a/test/npm-installs.js +++ b/test/npm-installs.js @@ -13,11 +13,11 @@ const path = require('path') const os = require('os') const isWindows = os.platform() === 'win32' -module.exports = (isJs) => { +module.exports = (type) => { return () => { describe('ipfs executable path', () => { const tmp = os.tmpdir() - const appName = isJs + const appName = type === 'js' ? 'bin.js' : isWindows ? 'ipfs.exe' : 'ipfs' @@ -26,7 +26,7 @@ module.exports = (isJs) => { after(() => { process.env.testpath = oldPath }) it('has the correct path when installed with npm3', (done) => { - let execPath = isJs + let execPath = type === 'js' ? 'ipfsd-ctl-test/node_modules/ipfs/src/cli' : 'ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs' @@ -39,7 +39,7 @@ module.exports = (isJs) => { delete require.cache[require.resolve('../src/daemon.js')] const Daemon = require('../src/daemon.js') - const node = new Daemon({ isJs }) + const node = new Daemon({ type }) expect(node.exec) .to.eql(path.join(tmp, `${execPath}/${appName}`)) rimraf(path.join(tmp, 'ipfsd-ctl-test'), done) @@ -47,7 +47,7 @@ module.exports = (isJs) => { }) it('has the correct path when installed with npm2', (done) => { - let execPath = isJs + let execPath = type === 'js' ? 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/ipfs/src/cli' : 'ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs' @@ -60,7 +60,7 @@ module.exports = (isJs) => { delete require.cache[require.resolve('../src/daemon.js')] const Daemon = require('../src/daemon.js') - const node = new Daemon({ isJs }) + const node = new Daemon({ type }) expect(node.exec) .to.eql(path.join(tmp, `${execPath}/${appName}`)) rimraf(path.join(tmp, 'ipfsd-ctl-test'), done) diff --git a/test/spawning.js b/test/spawning.js index 8321c94f..59777922 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -14,19 +14,21 @@ const isNode = require('detect-node') const addRetrieveTests = require('./add-retrive') -function tempDir (isJs) { - return path.join(os.tmpdir(), `${isJs ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) +function tempDir (js) { + return path.join(os.tmpdir(), `${js ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) } -module.exports = (df, isJs) => { +module.exports = (df, type) => { return () => { - const VERSION_STRING = isJs ? `js-ipfs version: ${require('ipfs/package.json').version}` : 'ipfs version 0.4.13' + const VERSION_STRING = type === 'js' + ? `js-ipfs version: ${require('ipfs/package.json').version}` + : 'ipfs version 0.4.13' it('prints the version', function (done) { if (!isNode) { this.skip() } - df.version({ isJs }, (err, version) => { + df.version({ type }, (err, version) => { expect(err).to.not.exist() expect(version).to.be.eql(VERSION_STRING) done() @@ -44,7 +46,7 @@ module.exports = (df, isJs) => { }) it('create node', function (done) { - df.spawn({ isJs, init: false, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ type, init: false, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() @@ -87,7 +89,7 @@ module.exports = (df, isJs) => { it('create node and init', function (done) { this.timeout(30 * 1000) - df.spawn({ isJs, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ type, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.not.exist() @@ -121,7 +123,7 @@ module.exports = (df, isJs) => { it('create init and start node', function (done) { this.timeout(20 * 1000) - df.spawn({ isJs }, (err, ipfsd) => { + df.spawn({ type }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() expect(ipfsd.ctl).to.exist() @@ -136,7 +138,7 @@ module.exports = (df, isJs) => { }) describe('spawn a node and pass init options', () => { - const repoPath = tempDir(isJs) + const repoPath = tempDir(type === 'js') const addr = '/ip4/127.0.0.1/tcp/5678' const swarmAddr1 = '/ip4/127.0.0.1/tcp/35555/ws' const swarmAddr2 = '/ip4/127.0.0.1/tcp/35666' @@ -156,7 +158,7 @@ module.exports = (df, isJs) => { config, repoPath, init: true, - isJs + type } let ipfsCtrl @@ -189,7 +191,7 @@ module.exports = (df, isJs) => { before(function (done) { this.timeout(20 * 1000) - df.spawn({ isJs }, (err, ipfsd) => { + df.spawn({ type }, (err, ipfsd) => { if (err) { return done(err) } @@ -228,7 +230,7 @@ module.exports = (df, isJs) => { }) it('should give an error if setting an invalid config value', function (done) { - if (isJs) { + if (type) { this.skip() // js doesn't fail on invalid config } else { ipfsCtrl.setConfig('Bootstrap', 'true', (err) => { diff --git a/test/start-stop.js b/test/start-stop.js index 59f3eec1..80ad339c 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -15,7 +15,7 @@ const exec = require('../src/exec') const DaemonFactory = require('../src') const df = DaemonFactory.create() -module.exports = (isJs) => { +module.exports = (type) => { return () => { describe('starting and stopping', () => { let ipfsCtrl @@ -23,7 +23,7 @@ module.exports = (isJs) => { describe(`create and init a node (ctlr)`, function () { this.timeout(20 * 1000) before((done) => { - df.spawn({ isJs, init: true, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ type, init: true, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd.ctrl).to.exist() diff --git a/test/utils.js b/test/utils.js index c6a91bbc..4b2bc5b8 100644 --- a/test/utils.js +++ b/test/utils.js @@ -44,14 +44,14 @@ describe('utils', () => { describe('find executable', () => { it('should find go executable', () => { - const execPath = findIpfsExecutable(false, __dirname) + const execPath = findIpfsExecutable('go', __dirname) expect(execPath).to.exist() expect(execPath).to.include('go-ipfs-dep/go-ipfs/ipfs') expect(fs.existsSync(execPath)).to.be.ok() }) it('should find go executable', () => { - const execPath = findIpfsExecutable(true, __dirname) + const execPath = findIpfsExecutable('js', __dirname) expect(execPath).to.exist() expect(execPath).to.include('ipfs/src/cli/bin.js') expect(fs.existsSync(execPath)).to.be.ok() From 79cfe2581f7c7e8c08345c17d2d06e4d157691a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 19 Dec 2017 00:35:06 -0600 Subject: [PATCH 46/85] feat: reworking endpoints --- package.json | 9 +- src/remote/client.js | 128 +++++++++++++-------------- src/remote/routes.js | 201 ++++++++++++++++++++++++------------------- src/remote/utils.js | 55 ------------ 4 files changed, 182 insertions(+), 211 deletions(-) delete mode 100644 src/remote/utils.js diff --git a/package.json b/package.json index 0274a124..d8507488 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,12 @@ }, "browser": { "./src/daemon.js": false, + "./src/remote/routes.js": false, "./src/exec.js": false, "hapi": false, "glob": false, "fs": false, + "joi": false, "stream": "readable-stream", "http": "stream-http" }, @@ -69,6 +71,7 @@ "hapi": "^16.6.2", "hat": "0.0.3", "http-browserify": "^1.7.0", + "ipfs": "^0.27.5", "ipfs-api": "^17.2.0", "lodash.clonewith": "^4.5.0", "lodash.defaultsdeep": "^4.6.0", @@ -78,23 +81,23 @@ "once": "^1.4.0", "qs": "^6.5.1", "readable-stream": "^2.3.3", - "request": "^2.83.0", "rimraf": "^2.6.2", "shutdown": "^0.3.0", "stream-http": "^2.7.2", "subcomandante": "^1.0.5", + "superagent": "^3.8.2", "truthy": "0.0.1" }, "devDependencies": { "aegir": "^12.2.0", "chai": "^4.1.2", "dirty-chai": "^2.0.1", - "ipfs": "~0.27.0", "is-running": "1.0.5", "mkdirp": "^0.5.1", "multihashes": "~0.4.12", "pre-commit": "^1.2.2", - "safe-buffer": "^5.1.1" + "safe-buffer": "^5.1.1", + "supertest": "^3.0.0" }, "repository": { "type": "git", diff --git a/src/remote/client.js b/src/remote/client.js index e7422f67..6e9f9eea 100644 --- a/src/remote/client.js +++ b/src/remote/client.js @@ -1,12 +1,8 @@ 'use strict' -const request = require('http') +const request = require('superagent') const IpfsApi = require('ipfs-api') const multiaddr = require('multiaddr') -const utils = require('./utils') - -const encodeParams = utils.encodeParams -const getResponse = utils.getResponse function createApi (apiAddr, gwAddr) { let api @@ -24,7 +20,7 @@ function createApi (apiAddr, gwAddr) { return api } -const createRemoteFactory = (host, port) => { +const createRemoteFactory = (host, port, secure) => { // create a client if (!host) { @@ -35,6 +31,9 @@ const createRemoteFactory = (host, port) => { port = 9999 } + secure = secure || false + const baseUrl = `${secure ? 'https://' : 'http://'}${host}:${port}` + class Node { constructor (id, apiAddr, gwAddrs) { this._id = id @@ -96,15 +95,19 @@ const createRemoteFactory = (host, port) => { cb = initOpts initOpts = {} } - request.get({ host, port, path: `/init?${encodeParams(this._id, { initOpts })}` }, - (res) => getResponse(res, (err, res) => { + + request + .post(`${baseUrl}/init`) + .query({ id: this._id }) + .send({ initOpts }) + .end((err, res) => { if (err) { return cb(err) } - this.initialized = true - cb(null, res) - })) + this.initialized = res.body.initialized + cb(null, res.body) + }) } /** @@ -116,7 +119,10 @@ const createRemoteFactory = (host, port) => { * @returns {undefined} */ shutdown (cb) { - request.get({ host, port, path: `/shutdown` }, (res) => { getResponse(res, cb) }) + request + .post(`${baseUrl}/shutdown`) + .query({ id: this._id }) + .end((err) => { cb(err) }) } /** @@ -131,20 +137,23 @@ const createRemoteFactory = (host, port) => { cb = flags flags = {} } - request.get({ host, port, path: `/startDaemon?${encodeParams(this._id, { flags })}` }, (res) => { - getResponse(res, (err, res) => { + + request + .post(`${baseUrl}/start`) + .query({ id: this._id }) + .send({ flags }) + .end((err, res) => { if (err) { return cb(err) } this.started = true - const apiAddr = res.data ? res.data.apiAddr : '' - const gatewayAddr = res.data ? res.data.gatewayAddr : '' + const apiAddr = res.body.api ? res.body.api.apiAddr : '' + const gatewayAddr = res.body.api ? res.body.api.gatewayAddr : '' return cb(null, createApi(apiAddr, gatewayAddr)) }) - }) } /** @@ -154,16 +163,16 @@ const createRemoteFactory = (host, port) => { * @returns {undefined} */ stopDaemon (cb) { - request.get({ host, port, path: `/stopDaemon?${encodeParams(this._id)}` }, - (res) => { - getResponse(res, (err) => { - if (err) { - return cb(err) - } - - this.started = false - cb(null) - }) + request + .post(`${baseUrl}/stop`) + .query({ id: this._id }) + .end((err) => { + if (err) { + return cb(err) + } + + this.started = false + cb(null) }) } @@ -177,8 +186,10 @@ const createRemoteFactory = (host, port) => { * @returns {undefined} */ killProcess (cb) { - request.get({ host, port, path: `/killProcess` }, (res) => { - getResponse(res, (err) => { + request + .post(`${baseUrl}/kill`) + .query({ id: this._id }) + .end((err) => { if (err) { return cb(err) } @@ -186,7 +197,6 @@ const createRemoteFactory = (host, port) => { this.started = false cb(null) }) - }) } /** @@ -196,16 +206,16 @@ const createRemoteFactory = (host, port) => { * @returns {number} */ daemonPid (cb) { - request.get({ host, port, path: `/daemonPid` }, (res) => { - getResponse(res, (err, data) => { + request + .get(`${baseUrl}/pid`) + .query({ id: this._id }) + .end((err, res) => { if (err) { return cb(err) } - this.started = false - cb(null, data) + cb(null, res.body.pid) }) - }) } /** @@ -223,16 +233,18 @@ const createRemoteFactory = (host, port) => { key = null } - request.get({ host, port, path: `/getConfig?${encodeParams(this._id, { key })}` }, (res) => { - getResponse(res, (err, res) => { + const qr = { id: this._id } + qr.key = key || undefined + request + .get(`${baseUrl}/config`) + .query(qr) + .end((err, res) => { if (err) { return cb(err) } - this.started = false - cb(null, res.data) + cb(null, res.body.config) }) - }) } /** @@ -244,16 +256,16 @@ const createRemoteFactory = (host, port) => { * @returns {undefined} */ setConfig (key, value, cb) { - request.get({ host, port, path: `/setConfig?${encodeParams(this._id, { key, value })}` }, (res) => { - getResponse(res, (err, data) => { + request.put(`${baseUrl}/config`) + .send({ key, value }) + .query({ id: this._id }) + .end((err) => { if (err) { return cb(err) } - this.started = false - cb(null, data) + cb(null) }) - }) } /** @@ -263,7 +275,6 @@ const createRemoteFactory = (host, port) => { * @param {function(Error)} cb * @returns {undefined} */ - replaceConf (file, cb) { cb(new Error('not implemented')) } @@ -277,34 +288,25 @@ const createRemoteFactory = (host, port) => { } opts = opts || {} - const req = request.request({ - host, - port, - method: 'POST', - path: `/spawn`, - headers: { - 'Content-Type': 'application/json' - } - }, (res) => { - getResponse(res, (err, res) => { + request + .post(`${baseUrl}/spawn`) + .send({ opts }) + .query({ id: this._id }) + .end((err, res) => { if (err) { return cb(err) } - const apiAddr = res.data ? res.data.apiAddr : '' - const gatewayAddr = res.data ? res.data.gatewayAddr : '' + const apiAddr = res.body.api ? res.body.api.apiAddr : '' + const gatewayAddr = res.body.api ? res.body.api.gatewayAddr : '' const node = new Node( - res._id, + res.body.id, apiAddr, gatewayAddr) cb(null, { ctl: createApi(apiAddr, gatewayAddr), ctrl: node }) }) - }) - - req.write(JSON.stringify({ opts })) - req.end() } } } diff --git a/src/remote/routes.js b/src/remote/routes.js index c19650a5..bc76b25f 100644 --- a/src/remote/routes.js +++ b/src/remote/routes.js @@ -3,10 +3,16 @@ const ipfsFactory = require('../local') const hat = require('hat') const boom = require('boom') -const utils = require('./utils') +const Joi = require('joi') +const defaults = require('lodash.defaultsdeep') -const parseQuery = utils.parseQuery -const makeResponse = utils.makeResponse +const config = { + validate: { + query: { + id: Joi.string().alphanum().required() + } + } +} let nodes = {} module.exports = (server) => { @@ -21,7 +27,7 @@ module.exports = (server) => { const payload = request.payload || {} ipfsFactory.spawn(payload.opts, (err, ipfsd) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } const id = hat() @@ -34,7 +40,7 @@ module.exports = (server) => { gatewayAddr: nodes[id].gatewayAddr ? nodes[id].gatewayAddr.toString() : '' } } - reply(makeResponse(id, api)) + reply({ id, api }) }) } }) @@ -44,11 +50,12 @@ module.exports = (server) => { */ server.route({ method: 'GET', - path: '/apiAddr', + path: '/api-addr', handler: (request, reply) => { - const id = parseQuery(request.query).id - reply(makeResponse(id, nodes[id].apiAddr())) - } + const id = request.query.id + reply({ apiAddr: nodes[id].apiAddr() }) + }, + config }) /** @@ -56,31 +63,32 @@ module.exports = (server) => { */ server.route({ method: 'GET', - path: '/getawayAddr', + path: '/getaway-addr', handler: (request, reply) => { - const id = parseQuery(request.query).id - reply(makeResponse(id, nodes[id].getawayAddr())) - } + const id = request.query.id + reply({ getawayAddr: nodes[id].getawayAddr() }) + }, + config }) /** * Initialize a repo. **/ server.route({ - method: 'GET', + method: 'POST', path: '/init', handler: (request, reply) => { - const qr = parseQuery(request.query) - const id = qr.id - const initOpts = qr.initOpts || {} - nodes[id].init(initOpts, (err, node) => { + const id = request.query.id + const payload = request.payload || {} + nodes[id].init(payload.initOpts, (err, node) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } - reply(makeResponse(id, { initialized: node.initialized })) + reply({ initialized: node.initialized }) }) - } + }, + config }) /** @@ -89,59 +97,64 @@ module.exports = (server) => { * automatically when the process is exited. **/ server.route({ - method: 'GET', + method: 'POST', path: '/shutdown', handler: (request, reply) => { - const id = parseQuery(request.query).id - nodes[id].shutdown((err, res) => { + const id = request.query.id + nodes[id].shutdown((err) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } - reply(makeResponse(id, res)) + reply().code(200) }) - } + }, + config }) /** * Start the daemon. **/ server.route({ - method: 'GET', - path: '/startDaemon', + method: 'POST', + path: '/start', handler: (request, reply) => { - const qr = parseQuery(request.query) - const id = qr.id - const flags = Object.values(qr.params ? qr.params.flags : {}) + const id = request.query.id + const payload = request.payload || {} + const flags = payload.flags || [] nodes[id].startDaemon(flags, (err) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } - reply(makeResponse(id, { - apiAddr: nodes[id].apiAddr.toString(), - gatewayAddr: nodes[id].gatewayAddr.toString() - })) + reply({ + api: { + apiAddr: nodes[id].apiAddr.toString(), + gatewayAddr: nodes[id].gatewayAddr.toString() + } + }) }) - } + }, + config }) /** * Stop the daemon. */ server.route({ - method: 'GET', - path: '/stopDaemon', + method: 'POST', + path: '/stop', handler: (request, reply) => { - const id = parseQuery(request.query).id - nodes[id].stopDaemon((err, res) => { + const id = request.query.id + nodes[id].stopDaemon((err) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } - reply(makeResponse(id, res)) + reply().code(200) }) - } + }, + config }) /** @@ -151,18 +164,19 @@ module.exports = (server) => { * if the process hasn't exited yet. */ server.route({ - method: 'GET', - path: '/killProcess', + method: 'POST', + path: '/kill', handler: (request, reply) => { - const id = parseQuery(request.query).id - nodes[id].killProcess((err, res) => { + const id = request.query.id + nodes[id].killProcess((err) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } - reply(makeResponse(id, res)) + reply().code(200) }) - } + }, + config }) /** @@ -172,11 +186,12 @@ module.exports = (server) => { */ server.route({ method: 'GET', - path: '/daemonPid', + path: '/pid', handler: (request, reply) => { - const id = parseQuery(request.query).id - reply(makeResponse(id, nodes[id].daemonPid(nodes[id]))) - } + const id = request.query.id + reply({ pid: nodes[id].daemonPid(nodes[id]) }) + }, + config }) /** @@ -186,57 +201,63 @@ module.exports = (server) => { */ server.route({ method: 'GET', - path: '/getConfig', + path: '/config', handler: (request, reply) => { - const qr = parseQuery(request.query) - const id = qr.id - const key = qr.params ? qr.params.key : null - nodes[id].getConfig(key, (err, res) => { + const id = request.query.id + const key = request.query.key + nodes[id].getConfig(key, (err, config) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } - reply(makeResponse(id, res)) + reply({ config }) }) - } + }, + config: defaults({}, { + validate: { + query: { + key: Joi.string().optional() + } + } + }, config) }) /** * Set a config value. */ server.route({ - method: 'GET', - path: '/setConfig', + method: 'PUT', + path: '/config', handler: (request, reply) => { - const qr = parseQuery(request.query) - const id = qr.id - const key = qr.params ? qr.params.key : undefined - const val = qr.params ? qr.params.value : undefined - nodes[id].setConfig(key, val, (err, res) => { - if (err) { - return reply(boom.badRequest(err)) - } + const id = request.query.id + const replace = request.query.replace + const key = request.payload.key + const val = request.payload.value - reply(makeResponse(id, res)) - }) - } - }) + if (replace) { + return nodes[id].replaceConf((err) => { + if (err) { + return reply(boom.internal(err)) + } - /** - * Replace the configuration with a given file - **/ - server.route({ - method: 'GET', - path: '/replaceConf', - handler: (request, reply) => { - const id = parseQuery(request.query).id - nodes[id].replaceConf((err, res) => { + reply().code(200) + }) + } + + nodes[id].setConfig(key, val, (err) => { if (err) { - return reply(boom.badRequest(err)) + return reply(boom.internal(err)) } - reply(null, makeResponse(id, res)) + reply().code(200) }) - } + }, + config: defaults({}, { + validate: { + query: { + replace: Joi.boolean().optional() + } + } + }, config) }) } diff --git a/src/remote/utils.js b/src/remote/utils.js deleted file mode 100644 index 2a5fab9a..00000000 --- a/src/remote/utils.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict' - -const Qs = require('qs') -const mapValues = require('lodash.mapvalues') - -exports.getResponse = (res, cb) => { - let data = '' - res.on('data', function (buf) { - data += buf.toString() - }) - - res.on('end', () => { - const response = JSON.parse(data) - if (typeof response.statusCode !== 'undefined' && response.statusCode !== 200) { - return cb(new Error(response.message)) - } - - cb(null, response) - }) - - res.on('err', cb) -} - -exports.encodeParams = (id, params) => { - return Qs.stringify({ id, params }, { arrayFormat: 'brackets' }) -} - -exports.makeResponse = (id, data) => { - return { _id: id, data: data } -} - -exports.parseQuery = (query) => { - // sadly `parse` doesn't deal with booleans correctly - // and since HAPI gives a partially processed query - // string, the `decoder` hook never gets called, - // hence the use of mapValues - let parsed = Qs.parse(query) - const transformer = (val) => { - if (Array.isArray(val)) { - return val - } - - if (typeof val === 'object') { - return mapValues(val, transformer) - } - - if (val === 'true' || val === 'false') { - val = val === 'true' - } - - return val - } - - return mapValues(parsed, transformer) -} From 0ceea38e20b9114b7be0d210de05a178a3e25bd7 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 19 Dec 2017 01:15:24 -0600 Subject: [PATCH 47/85] feat: use safe-json-parse --- README.md | 17 +++++++++++------ package.json | 1 + src/remote/client.js | 6 +++++- src/utils.js | 15 ++------------- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index baf90910..4d22f4a2 100644 --- a/README.md +++ b/README.md @@ -80,18 +80,22 @@ const DaemonFactory = require('ipfsd-ctl') const port = 9999 const server = DaemonFactory.createServer(port) -const df = DaemonFactory.create({remote: true, port}) -server.start(port, (err) => { +const df = DaemonFactory.create({ remote: true, port }) +server.start((err) => { if (err) { throw err } - - df.spawn(function (err, ipfsd) { + + df.spawn((err, ipfsd) => { + if (err) { + throw err + } + const ipfsCtl = ipfsd.ctl const ipfsCtrl = ipfsd.ctrl ipfsCtl.id(function (err, id) { console.log(id) - ipfsCtrl.stopDaemon() + ipfsCtrl.stopDaemon(() => process.exit(0)) server.stop() }) }) @@ -148,7 +152,8 @@ module.exports = { `spawn([options], callback)` - `options` - is an optional object with various options and ipfs config parameters - - `js` bool (default false) - spawn a js or go node (default go) + - `type` string (default 'go') - indicates which type of node to spawn + - current valid values are `js` and `go` - `init` bool (default true) - should the node be initialized - `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable diff --git a/package.json b/package.json index d8507488..40fb4c99 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "qs": "^6.5.1", "readable-stream": "^2.3.3", "rimraf": "^2.6.2", + "safe-json-parse": "^4.0.0", "shutdown": "^0.3.0", "stream-http": "^2.7.2", "subcomandante": "^1.0.5", diff --git a/src/remote/client.js b/src/remote/client.js index 6e9f9eea..38d2ce26 100644 --- a/src/remote/client.js +++ b/src/remote/client.js @@ -31,7 +31,11 @@ const createRemoteFactory = (host, port, secure) => { port = 9999 } - secure = secure || false + if (typeof host === 'number') { + port = host + host = 'localhost' + } + const baseUrl = `${secure ? 'https://' : 'http://'}${host}:${port}` class Node { diff --git a/src/utils.js b/src/utils.js index 091945c1..09f76dc5 100644 --- a/src/utils.js +++ b/src/utils.js @@ -6,6 +6,7 @@ const hat = require('hat') const os = require('os') const path = require('path') const exec = require('./exec') +const safeParse = require('safe-json-parse/callback') const join = path.join @@ -43,23 +44,11 @@ exports.flatten = (target) => { return output } -function tryJsonParse (input, callback) { - let res - try { - res = JSON.parse(input) - } catch (err) { - return callback(err) - } - callback(null, res) -} - -exports.tryJsonParse = tryJsonParse - // Consistent error handling exports.parseConfig = (path, callback) => { async.waterfall([ (cb) => fs.readFile(join(path, 'config'), cb), - (file, cb) => tryJsonParse(file.toString(), cb) + (file, cb) => safeParse(file.toString(), cb) ], callback) } From 81e8d61797498eb68e9afdbd5084ea1b9c54b33f Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 19 Dec 2017 14:34:17 -0600 Subject: [PATCH 48/85] feat: rework spawn with new interface --- README.md | 25 ++--- examples/electron-asar/app.js | 4 +- examples/id/id.js | 16 +-- examples/local-disposable/local-disposable.js | 14 +-- examples/local/local.js | 16 +-- .../remote-disposable/remote-disposable.js | 10 +- src/daemon.js | 11 +- src/local.js | 8 +- src/remote/client.js | 16 +-- src/remote/routes.js | 12 +- test/add-retrive.js | 10 +- test/api.js | 73 ++++++------ test/daemon.js | 4 +- test/spawning.js | 104 +++++++++--------- test/start-stop.js | 30 ++--- 15 files changed, 168 insertions(+), 185 deletions(-) diff --git a/README.md b/README.md index 4d22f4a2..d854e222 100644 --- a/README.md +++ b/README.md @@ -61,11 +61,9 @@ const DaemonFactory = require('ipfsd-ctl') const df = DaemonFactory.create() df.spawn(function (err, ipfsd) { - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id(function (err, id) { console.log(id) - ipfsCtrl.stopDaemon() + ipfsd.stop() }) }) ``` @@ -91,12 +89,9 @@ server.start((err) => { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id(function (err, id) { console.log(id) - ipfsCtrl.stopDaemon(() => process.exit(0)) - server.stop() + ipfsd.stop(server.stop) }) }) }) @@ -167,14 +162,14 @@ module.exports = { - `ctl` - an [ipfs-api](https://github.com/ipfs/js-ipfs-api) instance attached to the newly created ipfs node - `ctrl` - an instance of a daemon controller object -### IPFS Client (ctl) +### IPFS Client (api) > An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) -This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsdCtrl.startDaemon()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. +This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsdCtrl.start()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. -### IPFS Daemon Controller (ctrl) +### IPFS Daemon Controller (ipfsd) > The IPFS daemon controller that allows interacting with the spawned IPFS process @@ -212,13 +207,13 @@ This instance is returned for each successfully started IPFS daemon, when either - `function (Error, Node)` callback - receives an instance of this Node on success or an instance of `Error` on failure -#### `shutdown (callback)` +#### `cleanup (callback)` > Delete the repo that was being used. If the node was marked as `disposable` this will be called automatically when the process is exited. - `function(Error)` callback -#### `startDaemon (flags, callback)` +#### `start (flags, callback)` > Start the daemon. @@ -226,7 +221,7 @@ This instance is returned for each successfully started IPFS daemon, when either - `function(Error, IpfsApi)}` callback - function that receives an instance of `ipfs-api` on success or an instance of `Error` on failure -#### `stopDaemon (callback)` +#### `stop (callback)` > Stop the daemon. diff --git a/examples/electron-asar/app.js b/examples/electron-asar/app.js index 49098677..3f2a58b8 100644 --- a/examples/electron-asar/app.js +++ b/examples/electron-asar/app.js @@ -26,16 +26,16 @@ ipcMain.on('start', ({ sender }) => { throw err } - const ipfs = ipfsd.ctl console.log('get id') sender.send('message', 'get id') - ipfs.id(function (err, id) { + ipfsd.api.id((err, id) => { if (err) { sender.send('error', err) throw err } console.log('got id', id) sender.send('id', JSON.stringify(id)) + ipfsd.stop() }) }) }) diff --git a/examples/id/id.js b/examples/id/id.js index 57fa01ce..c23da1d2 100644 --- a/examples/id/id.js +++ b/examples/id/id.js @@ -4,36 +4,32 @@ const DaemonFactory = require('ipfsd-ctl') const df = DaemonFactory.create() -df.spawn(function (err, ipfsd) { +df.spawn((err, ipfsd) => { if (err) { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id((err, id) => { if (err) { throw err } console.log('alice') console.log(id) - ipfsCtrl.stopDaemon() + ipfsd.stop() }) }) -df.spawn(function (err, ipfsd) { +df.spawn((err, ipfsd) => { if (err) { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id((err, id) => { if (err) { throw err } console.log('bob') console.log(id) - ipfsCtrl.stopDaemon() + ipfsd.stop() }) }) diff --git a/examples/local-disposable/local-disposable.js b/examples/local-disposable/local-disposable.js index 902ca477..8202268f 100644 --- a/examples/local-disposable/local-disposable.js +++ b/examples/local-disposable/local-disposable.js @@ -13,34 +13,30 @@ df.spawn((err, ipfsd) => { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id((err, id) => { if (err) { throw err } console.log('go-ipfs') console.log(id) - ipfsCtrl.stopDaemon() + ipfsd.stop() }) }) // start a js daemon -df.spawn({ isJs: true }, (err, ipfsd) => { +df.spawn({ type: 'js' }, (err, ipfsd) => { if (err) { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id((err, id) => { if (err) { throw err } console.log('js-ipfs') console.log(id) - ipfsCtrl.stopDaemon() + ipfsd.stop() }) }) diff --git a/examples/local/local.js b/examples/local/local.js index acf91bd9..929ffe43 100644 --- a/examples/local/local.js +++ b/examples/local/local.js @@ -5,39 +5,35 @@ const DaemonFactory = require('ipfsd-ctl') const df = DaemonFactory.create() // opens an api connection to local running go-ipfs node -df.spawn({ disposable: false }, (err, ipfsd) => { +df.spawn({ disposable: true }, (err, ipfsd) => { if (err) { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id((err, id) => { if (err) { throw err } console.log('go-ipfs') console.log(id) - ipfsCtrl.stopDaemon() + ipfsd.stop() }) }) // opens an api connection to local running js-ipfs node -df.spawn({ isJs: true, disposable: false }, (err, ipfsd) => { +df.spawn({ type: 'js', disposable: true }, (err, ipfsd) => { if (err) { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id(function (err, id) { if (err) { throw err } console.log('js-ipfs') console.log(id) - ipfsCtrl.stopDaemon() + ipfsd.stop() }) }) diff --git a/examples/remote-disposable/remote-disposable.js b/examples/remote-disposable/remote-disposable.js index 4df13923..ccd7ec21 100644 --- a/examples/remote-disposable/remote-disposable.js +++ b/examples/remote-disposable/remote-disposable.js @@ -6,27 +6,25 @@ const DaemonFactory = require('ipfsd-ctl') const df = DaemonFactory.create({ remote: true }) -const server = DaemonFactory.server +const server = DaemonFactory.createServer() server.start((err) => { if (err) { throw err } - df.spawn(function (err, ipfsd) { + df.spawn((err, ipfsd) => { if (err) { throw err } - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl - ipfsCtl.id(function (err, id) { + ipfsd.api.id((err, id) => { if (err) { throw err } console.log(id) - ipfsCtrl.stopDaemon(() => server.stop()) + ipfsd.stop(() => server.stop()) }) }) }) diff --git a/src/daemon.js b/src/daemon.js index 4115dffc..0f7f4d6e 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -50,6 +50,7 @@ class Node { this._apiAddr = null this._gatewayAddr = null this._started = false + this.api = null if (this.opts.env) { Object.assign(this.env, this.opts.env) @@ -134,7 +135,7 @@ class Node { }) if (this.disposable) { - shutdown.addHandler('disposable', 1, this.shutdown.bind(this)) + shutdown.addHandler('disposable', 1, this.cleanup.bind(this)) } } @@ -146,7 +147,7 @@ class Node { * @param {function(Error)} callback * @returns {undefined} */ - shutdown (callback) { + cleanup (callback) { if (this.clean || !this.disposable) { return callback() } @@ -161,7 +162,7 @@ class Node { * @param {function(Error, IpfsApi)} callback * @returns {undefined} */ - startDaemon (flags, callback) { + start (flags, callback) { if (typeof flags === 'function') { callback = flags flags = [] @@ -234,7 +235,7 @@ class Node { * @param {function(Error)} callback * @returns {undefined} */ - stopDaemon (callback) { + stop (callback) { callback = callback || function noop () {} if (!this.subprocess) { @@ -276,7 +277,7 @@ class Node { * * @returns {number} */ - daemonPid () { + pid () { return this.subprocess && this.subprocess.pid } diff --git a/src/local.js b/src/local.js index d552412f..2516218f 100644 --- a/src/local.js +++ b/src/local.js @@ -68,7 +68,7 @@ const IpfsDaemonController = { } let options = {} - defaults(options, opts, defaultOptions) + options = defaults({}, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) if (!options.disposable) { @@ -83,19 +83,19 @@ const IpfsDaemonController = { } options.config = flatten(opts.config) - defaults(options.config, options.config, defaultConfig) + options.config = defaults({}, options.config, defaultConfig) const node = new Node(options) waterfall([ (cb) => options.init ? node.init(cb) : cb(null, node), - (node, cb) => options.start ? node.startDaemon(options.args, cb) : cb(null, null) + (node, cb) => options.start ? node.start(options.args, cb) : cb(null, null) ], (err, api) => { if (err) { return callback(err) } - callback(null, { ctl: api, ctrl: node }) + callback(null, node) }) } } diff --git a/src/remote/client.js b/src/remote/client.js index 38d2ce26..2493e392 100644 --- a/src/remote/client.js +++ b/src/remote/client.js @@ -45,6 +45,7 @@ const createRemoteFactory = (host, port, secure) => { this._gwAddr = multiaddr(gwAddrs) this.initialized = false this.started = false + this.api = createApi(apiAddr, gwAddrs) } /** @@ -122,9 +123,9 @@ const createRemoteFactory = (host, port, secure) => { * @param {function(Error)} cb * @returns {undefined} */ - shutdown (cb) { + cleanup (cb) { request - .post(`${baseUrl}/shutdown`) + .post(`${baseUrl}/cleanup`) .query({ id: this._id }) .end((err) => { cb(err) }) } @@ -136,7 +137,7 @@ const createRemoteFactory = (host, port, secure) => { * @param {function(Error, IpfsApi)} cb * @returns {undefined} */ - startDaemon (flags, cb) { + start (flags, cb) { if (typeof flags === 'function') { cb = flags flags = {} @@ -156,7 +157,8 @@ const createRemoteFactory = (host, port, secure) => { const apiAddr = res.body.api ? res.body.api.apiAddr : '' const gatewayAddr = res.body.api ? res.body.api.gatewayAddr : '' - return cb(null, createApi(apiAddr, gatewayAddr)) + this.api = createApi(apiAddr, gatewayAddr) + return cb(null, this.api) }) } @@ -166,7 +168,7 @@ const createRemoteFactory = (host, port, secure) => { * @param {function(Error)} cb * @returns {undefined} */ - stopDaemon (cb) { + stop (cb) { request .post(`${baseUrl}/stop`) .query({ id: this._id }) @@ -209,7 +211,7 @@ const createRemoteFactory = (host, port, secure) => { * @param {Function} cb * @returns {number} */ - daemonPid (cb) { + pid (cb) { request .get(`${baseUrl}/pid`) .query({ id: this._id }) @@ -309,7 +311,7 @@ const createRemoteFactory = (host, port, secure) => { apiAddr, gatewayAddr) - cb(null, { ctl: createApi(apiAddr, gatewayAddr), ctrl: node }) + cb(null, node) }) } } diff --git a/src/remote/routes.js b/src/remote/routes.js index bc76b25f..210465fe 100644 --- a/src/remote/routes.js +++ b/src/remote/routes.js @@ -31,7 +31,7 @@ module.exports = (server) => { } const id = hat() - nodes[id] = ipfsd.ctrl + nodes[id] = ipfsd let api = null if (nodes[id].started) { @@ -98,10 +98,10 @@ module.exports = (server) => { **/ server.route({ method: 'POST', - path: '/shutdown', + path: '/cleanup', handler: (request, reply) => { const id = request.query.id - nodes[id].shutdown((err) => { + nodes[id].cleanup((err) => { if (err) { return reply(boom.internal(err)) } @@ -122,7 +122,7 @@ module.exports = (server) => { const id = request.query.id const payload = request.payload || {} const flags = payload.flags || [] - nodes[id].startDaemon(flags, (err) => { + nodes[id].start(flags, (err) => { if (err) { return reply(boom.internal(err)) } @@ -146,7 +146,7 @@ module.exports = (server) => { path: '/stop', handler: (request, reply) => { const id = request.query.id - nodes[id].stopDaemon((err) => { + nodes[id].stop((err) => { if (err) { return reply(boom.internal(err)) } @@ -189,7 +189,7 @@ module.exports = (server) => { path: '/pid', handler: (request, reply) => { const id = request.query.id - reply({ pid: nodes[id].daemonPid(nodes[id]) }) + reply({ pid: nodes[id].pid(nodes[id]) }) }, config }) diff --git a/test/add-retrive.js b/test/add-retrive.js index 321c7dfe..bd44c903 100644 --- a/test/add-retrive.js +++ b/test/add-retrive.js @@ -16,10 +16,10 @@ module.exports = () => { before(function (done) { this.timeout(30 * 1000) async.waterfall([ - (cb) => this.ipfsCtl.block.put(blorb, cb), + (cb) => this.ipfsd.api.block.put(blorb, cb), (block, cb) => { store = block.cid.toBaseEncodedString() - this.ipfsCtl.block.get(store, cb) + this.ipfsd.api.block.get(store, cb) }, (_block, cb) => { retrieve = _block.data @@ -38,9 +38,9 @@ module.exports = () => { }) it('should have started the daemon and returned an api with host/port', function () { - expect(this.ipfsCtl).to.have.property('id') - expect(this.ipfsCtl).to.have.property('apiHost') - expect(this.ipfsCtl).to.have.property('apiPort') + expect(this.ipfsd.api).to.have.property('id') + expect(this.ipfsd.api).to.have.property('apiHost') + expect(this.ipfsd.api).to.have.property('apiPort') }) it('should be able to store objects', () => { diff --git a/test/api.js b/test/api.js index 5d0bfa71..92982ce4 100644 --- a/test/api.js +++ b/test/api.js @@ -7,7 +7,6 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) -const ipfsApi = require('ipfs-api') const multiaddr = require('multiaddr') const os = require('os') const path = require('path') @@ -15,29 +14,36 @@ const path = require('path') const isNode = require('detect-node') const isWindows = os.platform() === 'win32' -module.exports = (ipfsdController, isJs) => { +module.exports = (df, type) => { return () => { - const API_PORT = isJs ? '5002' : '5001' - const GW_PORT = isJs ? '9090' : '8080' + const API_PORT = type === 'js' ? '5002' : '5001' + const GW_PORT = type === 'js' ? '9090' : '8080' + + const config = { + Addresses: { + API: `/ip4/127.0.0.1/tcp/${API_PORT}`, + Gateway: `/ip4/127.0.0.1/tcp/${GW_PORT}` + } + } describe('ipfs-api version', () => { - let ipfsCtl - let ipfsCtrl + let ipfsd + let api before(function (done) { this.timeout(20 * 1000) - ipfsdController.spawn({ start: false }, (err, ipfsd) => { + df.spawn({ start: false, config }, (err, daemon) => { expect(err).to.not.exist() - ipfsCtrl = ipfsd.ctrl - ipfsCtrl.startDaemon((err, ignore) => { + ipfsd = daemon + ipfsd.start((err, res) => { expect(err).to.not.exist() - ipfsCtl = ipfsApi(ipfsCtrl.apiAddr) + api = res done() }) }) }) - after((done) => ipfsCtrl.stopDaemon(done)) + after((done) => ipfsd.stop(done)) // skip on windows for now // https://github.com/ipfs/js-ipfsd-ctl/pull/155#issuecomment-326970190 @@ -47,7 +53,7 @@ module.exports = (ipfsdController, isJs) => { } it('uses the correct ipfs-api', (done) => { - ipfsCtl.util.addFromFs(path.join(__dirname, 'fixtures/'), { + api.util.addFromFs(path.join(__dirname, 'fixtures/'), { recursive: true }, (err, res) => { expect(err).to.not.exist() @@ -87,41 +93,40 @@ module.exports = (ipfsdController, isJs) => { describe('validate api', () => { it('starts the daemon and returns valid API and gateway addresses', function (done) { this.timeout(20 * 1000) - ipfsdController.spawn({ isJs, config: null }, (err, ipfsd) => { + df.spawn({ type, config }, (err, res) => { expect(err).to.not.exist() - const ipfsCtl = ipfsd.ctl - const ipfsCtrl = ipfsd.ctrl + const ipfsd = res // Check for props in daemon - expect(ipfsCtrl).to.have.property('apiAddr') - expect(ipfsCtrl).to.have.property('gatewayAddr') - expect(ipfsCtrl.apiAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(ipfsCtrl.apiAddr)).to.equal(true) - expect(ipfsCtrl.gatewayAddr).to.not.equal(null) - expect(multiaddr.isMultiaddr(ipfsCtrl.gatewayAddr)).to.equal(true) + expect(ipfsd).to.have.property('apiAddr') + expect(ipfsd).to.have.property('gatewayAddr') + expect(ipfsd.apiAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(ipfsd.apiAddr)).to.equal(true) + expect(ipfsd.gatewayAddr).to.not.equal(null) + expect(multiaddr.isMultiaddr(ipfsd.gatewayAddr)).to.equal(true) // Check for props in ipfs-api instance - expect(ipfsCtl).to.have.property('apiHost') - expect(ipfsCtl).to.have.property('apiPort') - expect(ipfsCtl).to.have.property('gatewayHost') - expect(ipfsCtl).to.have.property('gatewayPort') - expect(ipfsCtl.apiHost).to.equal('127.0.0.1') - expect(ipfsCtl.apiPort).to.equal(API_PORT) - expect(ipfsCtl.gatewayHost).to.equal('127.0.0.1') - expect(ipfsCtl.gatewayPort).to.equal(GW_PORT) - - ipfsCtrl.stopDaemon(done) + expect(ipfsd.api).to.have.property('apiHost') + expect(ipfsd.api).to.have.property('apiPort') + expect(ipfsd.api).to.have.property('gatewayHost') + expect(ipfsd.api).to.have.property('gatewayPort') + expect(ipfsd.api.apiHost).to.equal('127.0.0.1') + expect(ipfsd.api.apiPort).to.equal(API_PORT) + expect(ipfsd.api.gatewayHost).to.equal('127.0.0.1') + expect(ipfsd.api.gatewayPort).to.equal(GW_PORT) + + ipfsd.stop(done) }) }) it('allows passing flags', function (done) { // skip in js, since js-ipfs doesn't fail on unrecognized args, it prints the help instead - if (isJs) { + if (type) { this.skip() } else { - ipfsdController.spawn({ start: false }, (err, ipfsd) => { + df.spawn({ start: false }, (err, ipfsd) => { expect(err).to.not.exist() - ipfsd.ctrl.startDaemon(['--should-not-exist'], (err) => { + ipfsd.start(['--should-not-exist'], (err) => { expect(err).to.exist() expect(err.message) .to.match(/Unrecognized option 'should-not-exist'/) diff --git a/test/daemon.js b/test/daemon.js index d708db2e..365e5b36 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -18,11 +18,11 @@ describe('ipfsd-ctl', () => { describe('Go daemon', () => { daemon(df, 'go')() - api(df, 'go') + api(df, 'go')() }) describe('Js daemon', () => { daemon(df, 'js')() - api(df, 'js') + api(df, 'js')() }) }) diff --git a/test/spawning.js b/test/spawning.js index 59777922..e19d7e70 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -24,53 +24,51 @@ module.exports = (df, type) => { ? `js-ipfs version: ${require('ipfs/package.json').version}` : 'ipfs version 0.4.13' - it('prints the version', function (done) { - if (!isNode) { - this.skip() - } - df.version({ type }, (err, version) => { - expect(err).to.not.exist() - expect(version).to.be.eql(VERSION_STRING) - done() + describe('daemon spawning', () => { + it('prints the version', function (done) { + if (!isNode) { + this.skip() + } + df.version({ type }, (err, version) => { + expect(err).to.not.exist() + expect(version).to.be.eql(VERSION_STRING) + done() + }) }) - }) - describe('daemon spawning', () => { describe('spawn a bare node', function () { - this.ipfsCtrl = null - this.ipfsCtl = null + this.ipfsd = null after(function (done) { this.timeout(20 * 1000) - this.ipfsCtrl.stopDaemon(done) + this.ipfsd.stop(done) }) it('create node', function (done) { df.spawn({ type, init: false, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() - expect(ipfsd.ctrl).to.exist() - expect(ipfsd.ctl).to.not.exist() - this.ipfsCtrl = ipfsd.ctrl + expect(ipfsd).to.exist() + expect(ipfsd.api).to.not.exist() + this.ipfsd = ipfsd done() }) }) it('init node', function (done) { this.timeout(20 * 1000) - this.ipfsCtrl.init((err) => { + this.ipfsd.init((err) => { expect(err).to.not.exist() - expect(this.ipfsCtrl.initialized).to.be.ok() + expect(this.ipfsd.initialized).to.be.ok() done() }) }) it('start node', function (done) { this.timeout(30 * 1000) - this.ipfsCtrl.startDaemon((err, ipfs) => { - this.ipfsCtl = ipfs + this.ipfsd.start((err, api) => { expect(err).to.not.exist() - expect(this.ipfsCtl).to.exist() - expect(this.ipfsCtl.id).to.exist() + expect(api).to.exist() + expect(api.id).to.exist() done() }) }) @@ -79,32 +77,30 @@ module.exports = (df, type) => { }) describe('spawn an initialized node', function () { - this.ipfsCtrl = null - this.ipfsCtl = null + this.ipfsd = null after(function (done) { this.timeout(20 * 1000) - this.ipfsCtrl.stopDaemon(done) + this.ipfsd.stop(done) }) it('create node and init', function (done) { this.timeout(30 * 1000) df.spawn({ type, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() - expect(ipfsd.ctrl).to.exist() - expect(ipfsd.ctl).to.not.exist() - this.ipfsCtrl = ipfsd.ctrl + expect(ipfsd).to.exist() + expect(ipfsd.api).to.not.exist() + this.ipfsd = ipfsd done() }) }) it('start node', function (done) { this.timeout(30 * 1000) - this.ipfsCtrl.startDaemon((err, ipfs) => { - this.ipfsCtl = ipfs + this.ipfsd.start((err, api) => { expect(err).to.not.exist() - expect(this.ipfsCtl).to.exist() - expect(this.ipfsCtl.id).to.exist() + expect(api).to.exist() + expect(api.id).to.exist() done() }) }) @@ -113,23 +109,21 @@ module.exports = (df, type) => { }) describe('spawn a node and attach api', () => { - this.ipfsCtrl = null - this.ipfsCtl = null + this.ipfsd = null after(function (done) { this.timeout(20 * 1000) - this.ipfsCtrl.stopDaemon(done) + this.ipfsd.stop(done) }) it('create init and start node', function (done) { this.timeout(20 * 1000) df.spawn({ type }, (err, ipfsd) => { expect(err).to.not.exist() - expect(ipfsd.ctrl).to.exist() - expect(ipfsd.ctl).to.exist() - expect(ipfsd.ctl.id).to.exist() - this.ipfsCtrl = ipfsd.ctrl - this.ipfsCtl = ipfsd.ctl + expect(ipfsd).to.exist() + expect(ipfsd.api).to.exist() + expect(ipfsd.api.id).to.exist() + this.ipfsd = ipfsd done() }) }) @@ -161,19 +155,19 @@ module.exports = (df, type) => { type } - let ipfsCtrl + let ipfsd async.waterfall([ (cb) => df.spawn(options, cb), - (ipfsd, cb) => { - ipfsCtrl = ipfsd.ctrl - ipfsCtrl.getConfig('Addresses.API', (err, res) => { + (res, cb) => { + ipfsd = res + ipfsd.getConfig('Addresses.API', (err, res) => { expect(err).to.not.exist() expect(res).to.be.eql(addr) cb() }) }, (cb) => { - ipfsCtrl.getConfig('Addresses.Swarm', (err, res) => { + ipfsd.getConfig('Addresses.Swarm', (err, res) => { expect(err).to.not.exist() expect(JSON.parse(res)).to.deep.eql([swarmAddr1, swarmAddr2]) cb() @@ -181,29 +175,29 @@ module.exports = (df, type) => { } ], (err) => { expect(err).to.not.exist() - ipfsCtrl.stopDaemon(done) + ipfsd.stop(done) }) }) }) describe('change config of a disposable node', () => { - let ipfsCtrl + let ipfsd before(function (done) { this.timeout(20 * 1000) - df.spawn({ type }, (err, ipfsd) => { + df.spawn({ type }, (err, res) => { if (err) { return done(err) } - ipfsCtrl = ipfsd.ctrl + ipfsd = res done() }) }) - after((done) => ipfsCtrl.stopDaemon(done)) + after((done) => ipfsd.stop(done)) it('Should return a config value', (done) => { - ipfsCtrl.getConfig('Bootstrap', (err, config) => { + ipfsd.getConfig('Bootstrap', (err, config) => { expect(err).to.not.exist() expect(config).to.exist() done() @@ -211,7 +205,7 @@ module.exports = (df, type) => { }) it('Should return the whole config', (done) => { - ipfsCtrl.getConfig((err, config) => { + ipfsd.getConfig((err, config) => { expect(err).to.not.exist() expect(config).to.exist() done() @@ -220,8 +214,8 @@ module.exports = (df, type) => { it('Should set a config value', (done) => { async.series([ - (cb) => ipfsCtrl.setConfig('Bootstrap', 'null', cb), - (cb) => ipfsCtrl.getConfig('Bootstrap', cb) + (cb) => ipfsd.setConfig('Bootstrap', 'null', cb), + (cb) => ipfsd.getConfig('Bootstrap', cb) ], (err, res) => { expect(err).to.not.exist() expect(res[1]).to.be.eql('null') @@ -233,7 +227,7 @@ module.exports = (df, type) => { if (type) { this.skip() // js doesn't fail on invalid config } else { - ipfsCtrl.setConfig('Bootstrap', 'true', (err) => { + ipfsd.setConfig('Bootstrap', 'true', (err) => { expect(err.message).to.match(/failed to set config value/) done() }) diff --git a/test/start-stop.js b/test/start-stop.js index 80ad339c..6e5bfb11 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -18,41 +18,41 @@ const df = DaemonFactory.create() module.exports = (type) => { return () => { describe('starting and stopping', () => { - let ipfsCtrl + let ipfsd describe(`create and init a node (ctlr)`, function () { this.timeout(20 * 1000) before((done) => { - df.spawn({ type, init: true, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ type, init: true, start: false, disposable: true }, (err, daemon) => { expect(err).to.not.exist() - expect(ipfsd.ctrl).to.exist() + expect(daemon).to.exist() - ipfsCtrl = ipfsd.ctrl + ipfsd = daemon done() }) }) it('should return a node', () => { - expect(ipfsCtrl).to.exist() + expect(ipfsd).to.exist() }) it('daemon should not be running', () => { - expect(ipfsCtrl.daemonPid()).to.not.exist() + expect(ipfsd.pid()).to.not.exist() }) }) let pid describe('starting', () => { - let ipfsCtl + let api before(function (done) { this.timeout(20 * 1000) - ipfsCtrl.startDaemon((err, ipfs) => { + ipfsd.start((err, ipfs) => { expect(err).to.not.exist() - pid = ipfsCtrl.daemonPid() - ipfsCtl = ipfs + pid = ipfsd.pid() + api = ipfs // actually running? done = once(done) @@ -61,7 +61,7 @@ module.exports = (type) => { }) it('should be running', () => { - expect(ipfsCtl.id).to.exist() + expect(api.id).to.exist() }) }) @@ -69,7 +69,7 @@ module.exports = (type) => { let stopped = false before((done) => { - ipfsCtrl.stopDaemon((err) => { + ipfsd.stop((err) => { expect(err).to.not.exist() stopped = true }) @@ -90,10 +90,10 @@ module.exports = (type) => { it('should be stopped', function () { this.timeout(30 * 1000) // shutdown grace period is already 10500 - expect(ipfsCtrl.daemonPid()).to.not.exist() + expect(ipfsd.pid()).to.not.exist() expect(stopped).to.equal(true) - expect(fs.existsSync(path.join(ipfsCtrl.path, 'repo.lock'))).to.not.be.ok() - expect(fs.existsSync(path.join(ipfsCtrl.path, 'api'))).to.not.be.ok() + expect(fs.existsSync(path.join(ipfsd.path, 'repo.lock'))).to.not.be.ok() + expect(fs.existsSync(path.join(ipfsd.path, 'api'))).to.not.be.ok() }) }) }) From 1d81a0a1cdd1105c07333118056669e0057e5885 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 19 Dec 2017 14:52:41 -0600 Subject: [PATCH 49/85] docs: small fixes to readme --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d854e222..db7fe851 100644 --- a/README.md +++ b/README.md @@ -146,21 +146,20 @@ module.exports = { `spawn([options], callback)` -- `options` - is an optional object with various options and ipfs config parameters +- `options` - is an optional object the following properties - `type` string (default 'go') - indicates which type of node to spawn - current valid values are `js` and `go` - `init` bool (default true) - should the node be initialized - `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable - - `disposable` bool - a new repo is created and initialized for each invocation + - `disposable` bool - a new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits - `args` - array of cmd line arguments to be passed to ipfs daemon - `config` - ipfs configuration options - `callback` - is a function with the signature `cb(err, ipfsd)` where: - `err` - is the error set if spawning the node is unsuccessful - - `ipfsd` - is an object with two properties: - - `ctl` - an [ipfs-api](https://github.com/ipfs/js-ipfs-api) instance attached to the newly created ipfs node - - `ctrl` - an instance of a daemon controller object + - `ipfsd` - is the daemon controller instance: + - `api` - a property of `ipfsd`, an instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api) attached to the newly created ipfs node ### IPFS Client (api) @@ -236,7 +235,7 @@ First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process has - `function()` callback - Called once the process is killed -#### `daemonPid ()` +#### `pid ()` > Get the pid of the `ipfs daemon` process. From 689ec23b07e906085e21920b65a73fbac2f45377 Mon Sep 17 00:00:00 2001 From: Henrique Dias Date: Wed, 20 Dec 2017 16:32:28 +0000 Subject: [PATCH 50/85] fix: init on different path --- src/daemon.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/daemon.js b/src/daemon.js index 0f7f4d6e..89164c26 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -46,7 +46,6 @@ class Node { this.subprocess = null this.initialized = fs.existsSync(path) this.clean = true - this.env = this.path ? Object.assign({}, process.env, { IPFS_PATH: this.path }) : process.env this._apiAddr = null this._gatewayAddr = null this._started = false @@ -93,6 +92,15 @@ class Node { return this._started } + /** + * Is the environment + * + * @return {object} + */ + get env () { + return this.path ? Object.assign({}, process.env, { IPFS_PATH: this.path }) : process.env + } + _run (args, opts, callback) { return exec(this.exec, args, opts, callback) } From 79a7194d6d5d8c54a009da386ad7230c1f83f16b Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Wed, 20 Dec 2017 11:23:28 -0600 Subject: [PATCH 51/85] chore: cleaning up unused deps --- package.json | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 40fb4c99..42b65f59 100644 --- a/package.json +++ b/package.json @@ -64,22 +64,17 @@ "license": "MIT", "dependencies": { "async": "^2.6.0", - "debug": "^3.1.0", + "boom": "^7.1.1", "detect-node": "^2.0.3", - "eslint-config-standard-jsx": "^4.0.2", "go-ipfs-dep": "0.4.13", "hapi": "^16.6.2", "hat": "0.0.3", - "http-browserify": "^1.7.0", "ipfs": "^0.27.5", "ipfs-api": "^17.2.0", - "lodash.clonewith": "^4.5.0", + "joi": "^13.0.2", "lodash.defaultsdeep": "^4.6.0", - "lodash.get": "^4.4.2", - "lodash.mapvalues": "^4.6.0", "multiaddr": "^3.0.1", "once": "^1.4.0", - "qs": "^6.5.1", "readable-stream": "^2.3.3", "rimraf": "^2.6.2", "safe-json-parse": "^4.0.0", @@ -95,10 +90,7 @@ "dirty-chai": "^2.0.1", "is-running": "1.0.5", "mkdirp": "^0.5.1", - "multihashes": "~0.4.12", - "pre-commit": "^1.2.2", - "safe-buffer": "^5.1.1", - "supertest": "^3.0.0" + "pre-commit": "^1.2.2" }, "repository": { "type": "git", From 66879712e188a54c2a2eab2b0f0438a7a7306a30 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 21 Dec 2017 10:32:00 -0600 Subject: [PATCH 52/85] chore: update deps --- package.json | 2 +- src/daemon.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 42b65f59..de23eef9 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "hapi": "^16.6.2", "hat": "0.0.3", "ipfs": "^0.27.5", - "ipfs-api": "^17.2.0", + "ipfs-api": "^17.2.5", "joi": "^13.0.2", "lodash.defaultsdeep": "^4.6.0", "multiaddr": "^3.0.1", diff --git a/src/daemon.js b/src/daemon.js index 89164c26..c99c805a 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -94,7 +94,7 @@ class Node { /** * Is the environment - * + * * @return {object} */ get env () { From 2df79ec1eb5b3047fb9662f40121334a16e896c4 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 22 Dec 2017 13:31:54 -0600 Subject: [PATCH 53/85] feat: several changes docs: updated readme fix: removed unused replaceConfig method test: added tests chore: moved js and go ipfs deps to optionalDependencies --- README.md | 7 ++- package.json | 10 ++-- src/daemon.js | 19 +------ src/local.js | 12 +++-- src/remote/client.js | 11 ---- src/remote/routes.js | 19 +------ test/spawning.js | 56 ++++++++++++++++---- test/start-stop.js | 119 ++++++++++++++++++++++++++++++++++++++++++- 8 files changed, 188 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index db7fe851..60b37810 100644 --- a/README.md +++ b/README.md @@ -124,6 +124,10 @@ module.exports = { } ``` +## Disposable vs non Disposable nodes + +`ipfsd-ctl` can create two types of node controllers, `disposable` and `non-disposable`. A disposable node will be created on a temporary repo which will be optionally initialized and started (the default), as well cleaned up on process exit. A non-disposable node on the other hand, 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. + ## API ### Daemon Factory @@ -152,9 +156,10 @@ module.exports = { - `init` bool (default true) - should the node be initialized - `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable - - `disposable` bool - a new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits + - `disposable` bool (default false) - a new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits - `args` - array of cmd line arguments to be passed to ipfs daemon - `config` - ipfs configuration options + - `exec` - path to the desired IPFS executable to spawn, otherwise `ipfsd-ctl` will try to locate the correct one based on the `type` - `callback` - is a function with the signature `cb(err, ipfsd)` where: - `err` - is the error set if spawning the node is unsuccessful diff --git a/package.json b/package.json index de23eef9..3962d503 100644 --- a/package.json +++ b/package.json @@ -66,10 +66,7 @@ "async": "^2.6.0", "boom": "^7.1.1", "detect-node": "^2.0.3", - "go-ipfs-dep": "0.4.13", - "hapi": "^16.6.2", "hat": "0.0.3", - "ipfs": "^0.27.5", "ipfs-api": "^17.2.5", "joi": "^13.0.2", "lodash.defaultsdeep": "^4.6.0", @@ -84,7 +81,14 @@ "superagent": "^3.8.2", "truthy": "0.0.1" }, + "optionalDependencies": { + "ipfs": "^0.27.5", + "go-ipfs-dep": "0.4.13" + }, "devDependencies": { + "hapi": "^16.6.2", + "ipfs": "^0.27.5", + "go-ipfs-dep": "0.4.13", "aegir": "^12.2.0", "chai": "^4.1.2", "dirty-chai": "^2.0.1", diff --git a/src/daemon.js b/src/daemon.js index c99c805a..c2213897 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -42,7 +42,7 @@ class Node { const tmpDir = tempDir(opts.type === 'js') this.path = this.opts.disposable ? tmpDir : (this.opts.repoPath || tmpDir) this.disposable = this.opts.disposable - this.exec = this.opts.executable || process.env.IPFS_EXEC || findIpfsExecutable(this.opts.type, rootPath) + this.exec = this.opts.exec || process.env.IPFS_EXEC || findIpfsExecutable(this.opts.type, rootPath) this.subprocess = null this.initialized = fs.existsSync(path) this.clean = true @@ -156,7 +156,7 @@ class Node { * @returns {undefined} */ cleanup (callback) { - if (this.clean || !this.disposable) { + if (this.clean) { return callback() } @@ -334,21 +334,6 @@ class Node { setConfigValue(this, key, value, callback) } - /** - * Replace the configuration with a given file - * - * @param {string} file - path to the new config file - * @param {function(Error)} callback - * @returns {undefined} - */ - replaceConf (file, callback) { - this._run( - ['config', 'replace', file], - { env: this.env }, - callback - ) - } - /** * Get the version of ipfs * diff --git a/src/local.js b/src/local.js index 2516218f..e1246be2 100644 --- a/src/local.js +++ b/src/local.js @@ -55,7 +55,7 @@ const IpfsDaemonController = { * - `disposable` bool - a new repo is created and initialized for each invocation * - `config` - ipfs configuration options * - `args` - array of cmd line arguments to be passed to ipfs daemon - * - `executable` - path to the desired IPFS executable to spawn + * - `exec` - path to the desired IPFS executable to spawn * * @param {Object} [opts={}] - various config options and ipfs config parameters * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` @@ -77,9 +77,11 @@ const IpfsDaemonController = { delete defaultConfig['Addresses.Gateway'] options.init = false - options.repoPath = options.repoPath || (process.env.IPFS_PATH || - join(process.env.HOME || - process.env.USERPROFILE, options.isJs ? '.jsipfs' : '.ipfs')) + options.start = false + + const defaultRepo = join(process.env.HOME || process.env.USERPROFILE, + options.isJs ? '.jsipfs' : '.ipfs') + options.repoPath = options.repoPath || (process.env.IPFS_PATH || defaultRepo) } options.config = flatten(opts.config) @@ -90,7 +92,7 @@ const IpfsDaemonController = { waterfall([ (cb) => options.init ? node.init(cb) : cb(null, node), (node, cb) => options.start ? node.start(options.args, cb) : cb(null, null) - ], (err, api) => { + ], (err) => { if (err) { return callback(err) } diff --git a/src/remote/client.js b/src/remote/client.js index 2493e392..ac241a68 100644 --- a/src/remote/client.js +++ b/src/remote/client.js @@ -273,17 +273,6 @@ const createRemoteFactory = (host, port, secure) => { cb(null) }) } - - /** - * Replace the configuration with a given file - * - * @param {string} file - path to the new config file - * @param {function(Error)} cb - * @returns {undefined} - */ - replaceConf (file, cb) { - cb(new Error('not implemented')) - } } return { diff --git a/src/remote/routes.js b/src/remote/routes.js index 210465fe..e229c382 100644 --- a/src/remote/routes.js +++ b/src/remote/routes.js @@ -230,20 +230,9 @@ module.exports = (server) => { path: '/config', handler: (request, reply) => { const id = request.query.id - const replace = request.query.replace const key = request.payload.key const val = request.payload.value - if (replace) { - return nodes[id].replaceConf((err) => { - if (err) { - return reply(boom.internal(err)) - } - - reply().code(200) - }) - } - nodes[id].setConfig(key, val, (err) => { if (err) { return reply(boom.internal(err)) @@ -252,12 +241,6 @@ module.exports = (server) => { reply().code(200) }) }, - config: defaults({}, { - validate: { - query: { - replace: Joi.boolean().optional() - } - } - }, config) + config }) } diff --git a/test/spawning.js b/test/spawning.js index e19d7e70..91d66f8e 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -8,16 +8,12 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) -const path = require('path') -const os = require('os') +const fs = require('fs') +const tempDir = require('../src/utils').tempDir const isNode = require('detect-node') const addRetrieveTests = require('./add-retrive') -function tempDir (js) { - return path.join(os.tmpdir(), `${js ? 'jsipfs' : 'ipfs'}_${String(Math.random()).substr(2)}`) -} - module.exports = (df, type) => { return () => { const VERSION_STRING = type === 'js' @@ -132,7 +128,6 @@ module.exports = (df, type) => { }) describe('spawn a node and pass init options', () => { - const repoPath = tempDir(type === 'js') const addr = '/ip4/127.0.0.1/tcp/5678' const swarmAddr1 = '/ip4/127.0.0.1/tcp/35555/ws' const swarmAddr2 = '/ip4/127.0.0.1/tcp/35666' @@ -150,8 +145,6 @@ module.exports = (df, type) => { this.timeout(20 * 1000) const options = { config, - repoPath, - init: true, type } @@ -180,6 +173,51 @@ module.exports = (df, type) => { }) }) + describe('spawn a node on custom repo path', function () { + if (!isNode) { + return + } + + this.ipfsd = null + it('allows passing custom repo path to spawn', function (done) { + this.timeout(20 * 1000) + + const repoPath = tempDir(type) + + const config = { + Addresses: { + Swarm: [ + '/ip4/127.0.0.1/tcp/0/ws', + '/ip4/127.0.0.1/tcp/0' + ], + API: '/ip4/127.0.0.1/tcp/0' + } + } + + async.series([ + (cb) => df.spawn({ type, repoPath, disposable: false, config }, (err, node) => { + expect(err).to.not.exist() + this.ipfsd = node + cb() + }), + (cb) => this.ipfsd.init(cb), + (cb) => this.ipfsd.start(cb) + ], (err) => { + expect(err).to.not.exist() + expect(fs.existsSync(repoPath)).to.be.ok() + done() + }) + }) + + addRetrieveTests() + + after(function (done) { + this.ipfsd.stop(() => { + this.ipfsd.cleanup(done) + }) + }) + }) + describe('change config of a disposable node', () => { let ipfsd diff --git a/test/start-stop.js b/test/start-stop.js index 6e5bfb11..30be226f 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -7,11 +7,15 @@ const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) +const async = require('async') const fs = require('fs') const once = require('once') const path = require('path') const exec = require('../src/exec') +const findIpfsExecutable = require('../src/utils').findIpfsExecutable +const tempDir = require('../src/utils').tempDir + const DaemonFactory = require('../src') const df = DaemonFactory.create() @@ -20,7 +24,7 @@ module.exports = (type) => { describe('starting and stopping', () => { let ipfsd - describe(`create and init a node (ctlr)`, function () { + describe(`create and init a node (ipfsd)`, function () { this.timeout(20 * 1000) before((done) => { df.spawn({ type, init: true, start: false, disposable: true }, (err, daemon) => { @@ -97,5 +101,118 @@ module.exports = (type) => { }) }) }) + + describe('starting and stopping on custom exec path', () => { + let ipfsd + + describe(`create and init a node (ipfsd) on custom exec path`, function () { + this.timeout(20 * 1000) + const exec = findIpfsExecutable(type) + before((done) => { + df.spawn({ type, exec }, (err, daemon) => { + expect(err).to.not.exist() + expect(daemon).to.exist() + + ipfsd = daemon + done() + }) + }) + + after((done) => ipfsd.stop(done)) + + it('should return a node', () => { + expect(ipfsd).to.exist() + }) + + it('ipfsd.exec should match exec', () => { + expect(ipfsd.exec).to.equal(exec) + }) + }) + + describe(`should fail on invalid exec path`, function () { + this.timeout(20 * 1000) + const exec = '/invalid/exec/ipfs' + before((done) => { + df.spawn({ + type, + init: false, + start: false, + exec + }, (err, daemon) => { + expect(err).to.not.exist() + expect(daemon).to.exist() + + ipfsd = daemon + done() + }) + }) + + it('should fail on init', (done) => { + ipfsd.init((err, node) => { + expect(err).to.exist() + expect(node).to.not.exist() + done() + }) + }) + }) + }) + + describe('starting and stopping multiple times', () => { + let ipfsd + + describe(`create and init a node (ipfsd)`, function () { + this.timeout(20 * 1000) + before((done) => { + async.series([ + (cb) => df.spawn({ + type, + init: false, + start: false, + disposable: false, + repoPath: tempDir(type) + }, (err, daemon) => { + expect(err).to.not.exist() + expect(daemon).to.exist() + + ipfsd = daemon + cb() + }), + (cb) => ipfsd.init(cb), + (cb) => ipfsd.start(cb) + ], done) + }) + + it('should return a node', () => { + expect(ipfsd).to.exist() + }) + + it('daemon should not be running', () => { + expect(ipfsd.pid()).to.exist() + }) + + it('should stop', (done) => { + ipfsd.stop((err) => { + expect(err).to.not.exist() + expect(ipfsd.pid()).to.not.exist() + done() + }) + }) + + it('should start', (done) => { + ipfsd.start((err) => { + expect(err).to.not.exist() + expect(ipfsd.pid()).to.exist() + done() + }) + }) + + it('should stop and cleanup', (done) => { + ipfsd.stop((err) => { + expect(err).to.not.exist() + ipfsd.cleanup(done) + }) + }) + }) + }) } } From 5f894319446c919766664a8a9ab532e536622e70 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 22 Dec 2017 17:38:13 -0600 Subject: [PATCH 54/85] chore: remove ipfs deps from optionalDependencies --- package.json | 5 +---- src/daemon.js | 2 +- src/local.js | 15 +++++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 3962d503..9ae4535a 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "hat": "0.0.3", "ipfs-api": "^17.2.5", "joi": "^13.0.2", + "lodash.clone": "^4.5.0", "lodash.defaultsdeep": "^4.6.0", "multiaddr": "^3.0.1", "once": "^1.4.0", @@ -81,10 +82,6 @@ "superagent": "^3.8.2", "truthy": "0.0.1" }, - "optionalDependencies": { - "ipfs": "^0.27.5", - "go-ipfs-dep": "0.4.13" - }, "devDependencies": { "hapi": "^16.6.2", "ipfs": "^0.27.5", diff --git a/src/daemon.js b/src/daemon.js index c2213897..f334069c 100644 --- a/src/daemon.js +++ b/src/daemon.js @@ -256,7 +256,7 @@ class Node { /** * Kill the `ipfs daemon` process. * - * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent + * First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent * if the process hasn't exited yet. * * @param {function()} callback - Called when the process was killed. diff --git a/src/local.js b/src/local.js index e1246be2..68e73d5a 100644 --- a/src/local.js +++ b/src/local.js @@ -1,6 +1,7 @@ 'use strict' const defaults = require('lodash.defaultsdeep') +const clone = require('lodash.clone') const waterfall = require('async/waterfall') const join = require('path').join const flatten = require('./utils').flatten @@ -71,10 +72,12 @@ const IpfsDaemonController = { options = defaults({}, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) + options.config = flatten(opts.config) if (!options.disposable) { - delete defaultConfig['Addresses.Swarm'] - delete defaultConfig['Addresses.API'] - delete defaultConfig['Addresses.Gateway'] + const nonDisposableConfig = clone(defaultConfig) + delete nonDisposableConfig['Addresses.Swarm'] + delete nonDisposableConfig['Addresses.API'] + delete nonDisposableConfig['Addresses.Gateway'] options.init = false options.start = false @@ -82,11 +85,11 @@ const IpfsDaemonController = { const defaultRepo = 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) + } else { + options.config = defaults({}, options.config, {}, defaultConfig) } - options.config = flatten(opts.config) - options.config = defaults({}, options.config, defaultConfig) - const node = new Node(options) waterfall([ From 591f9d9b68536c558d831734ba250570babd1e4d Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 22 Dec 2017 18:00:39 -0600 Subject: [PATCH 55/85] chore: add daemons as peerdeps --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index 9ae4535a..dd016bbc 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,10 @@ "superagent": "^3.8.2", "truthy": "0.0.1" }, + "peerDependencies": { + "ipfs": "^0.27.5", + "go-ipfs-dep": "0.4.13" + }, "devDependencies": { "hapi": "^16.6.2", "ipfs": "^0.27.5", From 9bf6bd55ad0587eea5b8631c6ee9e83ba28ed9a9 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 22 Dec 2017 18:56:55 -0600 Subject: [PATCH 56/85] chore: gire remove peer deps --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index dd016bbc..9ae4535a 100644 --- a/package.json +++ b/package.json @@ -82,10 +82,6 @@ "superagent": "^3.8.2", "truthy": "0.0.1" }, - "peerDependencies": { - "ipfs": "^0.27.5", - "go-ipfs-dep": "0.4.13" - }, "devDependencies": { "hapi": "^16.6.2", "ipfs": "^0.27.5", From 272403811e2b9394282d56bce9806d727074fa81 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 22 Dec 2017 19:02:21 -0600 Subject: [PATCH 57/85] docs: clarifying that ipfsd-ctl no longer bundles executables --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 60b37810..013d112c 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,15 @@ module.exports = { `ipfsd-ctl` can create two types of node controllers, `disposable` and `non-disposable`. A disposable node will be created on a temporary repo which will be optionally initialized and started (the default), as well cleaned up on process exit. A non-disposable node on the other hand, 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. +## IPFS executables + +`ipfsd-ctl` no longer installs go-ipfs nor js-ipfs dependencies, instead it expects them to be provided by the parent project. In order to be able to use both go and js daemons, please make sure that your project includes this two packages as dependencies. + +- `ipfs` - the js-ipfs implementation +- `go-ipfs-dep` - the packaged go-ipfs implementation + +It is also possible to provide an alternative executable when using `spawn` by setting the `exec` option to the path of the executable. + ## API ### Daemon Factory From 67e01088c72c1cd1f23c2b5cc229316158bbb7cb Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Sat, 23 Dec 2017 13:09:04 -0600 Subject: [PATCH 58/85] docs: small clarification --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 013d112c..f973bad9 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ module.exports = { ## IPFS executables -`ipfsd-ctl` no longer installs go-ipfs nor js-ipfs dependencies, instead it expects them to be provided by the parent project. In order to be able to use both go and js daemons, please make sure that your project includes this two packages as dependencies. +`ipfsd-ctl` no longer installs go-ipfs nor js-ipfs dependencies, instead it expects them to be provided by the parent project. In order to be able to use both go and js daemons, please make sure that your project includes these two npm packages as dependencies. - `ipfs` - the js-ipfs implementation - `go-ipfs-dep` - the packaged go-ipfs implementation From 902b828c2ebbb7accd879a48ad5b79656e94285d Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Sat, 23 Dec 2017 13:41:06 -0600 Subject: [PATCH 59/85] docs: removing replaceConfig from readme --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index f973bad9..4796f9af 100644 --- a/README.md +++ b/README.md @@ -273,14 +273,6 @@ If no `key` is passed, the whole config is returned as an object. - `key` - the key to set - `value` - the value to set the key to - `function(Error)` callback - - -#### `replaceConf (file, callback)` -> Replace the configuration with a given file - -- `file` - path to the new config file -- `function(Error)` callback - #### `version (callback)` From 9cf21f145b7628f5f8e678b5201964ccb69828bb Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Sun, 24 Dec 2017 12:21:52 -0600 Subject: [PATCH 60/85] fix: hapi should be a `dependency` not a dev dep --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9ae4535a..20a03e4c 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "boom": "^7.1.1", "detect-node": "^2.0.3", "hat": "0.0.3", + "hapi": "^16.6.2", "ipfs-api": "^17.2.5", "joi": "^13.0.2", "lodash.clone": "^4.5.0", @@ -83,7 +84,6 @@ "truthy": "0.0.1" }, "devDependencies": { - "hapi": "^16.6.2", "ipfs": "^0.27.5", "go-ipfs-dep": "0.4.13", "aegir": "^12.2.0", From f70ab83fd1585f8ce8def0eeaffd630ad737b853 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 26 Dec 2017 10:44:22 -0600 Subject: [PATCH 61/85] feat: rework IpfsDaemonController as a class --- src/local.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/local.js b/src/local.js index 68e73d5a..69c6df67 100644 --- a/src/local.js +++ b/src/local.js @@ -32,7 +32,7 @@ const defaultConfig = { * * @namespace IpfsDaemonController */ -const IpfsDaemonController = { +class IpfsDaemonController { /** * Get the version of the currently used go-ipfs binary. * @@ -41,9 +41,9 @@ const IpfsDaemonController = { * @param {function(Error, string)} callback * @returns {undefined} */ - version (opts, callback) { + static version (opts, callback) { (new Node(opts)).version(callback) - }, + } /** * Spawn an IPFS node, either js-ipfs or go-ipfs @@ -62,7 +62,7 @@ const IpfsDaemonController = { * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` * @return {undefined} */ - spawn (opts, callback) { + static spawn (opts, callback) { if (typeof opts === 'function') { callback = opts opts = defaultOptions From d415a8a572d9764cd1fe3af4bd3f87b03d58e118 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 26 Dec 2017 19:25:22 -0600 Subject: [PATCH 62/85] feat: add ability to start in-proc nodes --- package.json | 5 +- src/{local.js => daemon-ctrl.js} | 51 +++-- src/{daemon.js => daemon-node.js} | 2 + src/in-proc-node.js | 259 ++++++++++++++++++++++++++ src/index.js | 4 +- src/{remote => remote-node}/client.js | 16 +- src/{remote => remote-node}/index.js | 0 src/{remote => remote-node}/routes.js | 18 +- src/{remote => remote-node}/server.js | 0 src/utils/clean.js | 15 ++ src/utils/create-repo-browser.js | 28 +++ src/utils/create-repo-nodejs.js | 29 +++ src/{utils.js => utils/index.js} | 17 +- test/daemon.js | 5 + test/exec.js | 2 +- test/npm-installs.js | 8 +- test/spawning.js | 34 ++-- 17 files changed, 424 insertions(+), 69 deletions(-) rename src/{local.js => daemon-ctrl.js} (77%) rename src/{daemon.js => daemon-node.js} (98%) create mode 100644 src/in-proc-node.js rename src/{remote => remote-node}/client.js (93%) rename src/{remote => remote-node}/index.js (100%) rename src/{remote => remote-node}/routes.js (92%) rename src/{remote => remote-node}/server.js (100%) create mode 100644 src/utils/clean.js create mode 100644 src/utils/create-repo-browser.js create mode 100644 src/utils/create-repo-nodejs.js rename src/{utils.js => utils/index.js} (88%) diff --git a/package.json b/package.json index 20a03e4c..d5b09d07 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,9 @@ "coverage-publish": "aegir coverage -u" }, "browser": { - "./src/daemon.js": false, - "./src/remote/routes.js": false, + "./src/utils/create-repo-nodejs.js": "./src/utils/create-repo-browser.js", + "./src/daemon-node.js": false, + "./src/remote-node/routes.js": false, "./src/exec.js": false, "hapi": false, "glob": false, diff --git a/src/local.js b/src/daemon-ctrl.js similarity index 77% rename from src/local.js rename to src/daemon-ctrl.js index 69c6df67..1cce61d7 100644 --- a/src/local.js +++ b/src/daemon-ctrl.js @@ -4,9 +4,9 @@ const defaults = require('lodash.defaultsdeep') const clone = require('lodash.clone') const waterfall = require('async/waterfall') const join = require('path').join -const flatten = require('./utils').flatten -const Node = require('./daemon') +const Node = require('./daemon-node') +const ProcNode = require('./in-proc-node') const defaultOptions = { type: 'go', @@ -16,23 +16,29 @@ const defaultOptions = { } const defaultConfig = { - 'API.HTTPHeaders.Access-Control-Allow-Origin': ['*'], - 'API.HTTPHeaders.Access-Control-Allow-Methods': [ - 'PUT', - 'POST', - 'GET' - ], - 'Addresses.Swarm': [`/ip4/127.0.0.1/tcp/0`], - 'Addresses.API': `/ip4/127.0.0.1/tcp/0`, - 'Addresses.Gateway': `/ip4/127.0.0.1/tcp/0` + API: { + HTTPHeaders: { + 'Access-Control-Allow-Origin': ['*'], + 'Access-Control-Allow-Methods': [ + 'PUT', + 'POST', + 'GET' + ] + } + }, + Addresses: { + Swarm: [`/ip4/127.0.0.1/tcp/0`], + API: `/ip4/127.0.0.1/tcp/0`, + Gateway: `/ip4/127.0.0.1/tcp/0` + } } /** * Control go-ipfs nodes directly from JavaScript. * - * @namespace IpfsDaemonController + * @namespace DaemonController */ -class IpfsDaemonController { +class DaemonController { /** * Get the version of the currently used go-ipfs binary. * @@ -71,13 +77,9 @@ class IpfsDaemonController { let options = {} options = defaults({}, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) - - options.config = flatten(opts.config) if (!options.disposable) { const nonDisposableConfig = clone(defaultConfig) - delete nonDisposableConfig['Addresses.Swarm'] - delete nonDisposableConfig['Addresses.API'] - delete nonDisposableConfig['Addresses.Gateway'] + delete nonDisposableConfig['Addresses'] options.init = false options.start = false @@ -90,7 +92,16 @@ class IpfsDaemonController { options.config = defaults({}, options.config, {}, defaultConfig) } - const node = new Node(options) + let node + if (options.type === 'proc') { + if (typeof options.exec !== 'function') { + return callback(new Error(`'type' proc requires 'exec' to be a coderef`)) + } + + node = new ProcNode(options) + } else { + node = new Node(options) + } waterfall([ (cb) => options.init ? node.init(cb) : cb(null, node), @@ -105,4 +116,4 @@ class IpfsDaemonController { } } -module.exports = IpfsDaemonController +module.exports = DaemonController diff --git a/src/daemon.js b/src/daemon-node.js similarity index 98% rename from src/daemon.js rename to src/daemon-node.js index f334069c..ed39dda5 100644 --- a/src/daemon.js +++ b/src/daemon-node.js @@ -10,6 +10,7 @@ const path = require('path') const once = require('once') const truthy = require('truthy') const utils = require('./utils') +const flatten = require('./utils').flatten const tryJsonParse = utils.tryJsonParse const parseConfig = utils.parseConfig @@ -38,6 +39,7 @@ class Node { const type = truthy(process.env.IPFS_TYPE) this.opts = opts || { type: type || 'go' } + this.opts.config = flatten(this.opts.config) const tmpDir = tempDir(opts.type === 'js') this.path = this.opts.disposable ? tmpDir : (this.opts.repoPath || tmpDir) diff --git a/src/in-proc-node.js b/src/in-proc-node.js new file mode 100644 index 00000000..9dbb7a17 --- /dev/null +++ b/src/in-proc-node.js @@ -0,0 +1,259 @@ +'use strict' + +const createRepo = require('./utils').createRepo +const multiaddr = require('multiaddr') +const flatten = require('./utils').flatten +const async = require('async') + +/** + * Controll a go-ipfs or js-ipfs node. + */ +class Node { + /** + * Create a new node. + * + * @param {Object} [opts] + * @param {Object} [opts.env={}] - Additional environment settings, passed to executing shell. + * @returns {Node} + */ + constructor (opts) { + this.opts = opts || {} + + const IPFS = this.opts.exec + + this.path = this.opts.repoPath + this.repo = createRepo(this.path) + this.disposable = this.opts.disposable + this.clean = true + this._apiAddr = null + this._gatewayAddr = null + this._started = false + this.initialized = false + this.api = null + + this.exec = new IPFS({ + repo: this.repo, + init: false, + start: false + }) + } + + /** + * Get the address of connected IPFS API. + * + * @returns {Multiaddr} + */ + get apiAddr () { + return this._apiAddr + } + + /** + * Get the address of connected IPFS HTTP Gateway. + * + * @returns {Multiaddr} + */ + get gatewayAddr () { + return this._gatewayAddr + } + + /** + * Get the current repo path + * + * @return {string} + */ + get repoPath () { + return this.path + } + + /** + * Is the node started + * + * @return {boolean} + */ + get started () { + return this._started + } + + /** + * Is the environment + * + * @return {object} + */ + get env () { + throw new Error('Not implemented!') + } + + /** + * Initialize a repo. + * + * @param {Object} [initOpts={}] + * @param {number} [initOpts.keysize=2048] - The bit size of the identiy key. + * @param {string} [initOpts.directory=IPFS_PATH] - The location of the repo. + * @param {function (Error, Node)} callback + * @returns {undefined} + */ + init (initOpts, callback) { + if (!callback) { + callback = initOpts + initOpts = {} + } + + initOpts.bits = initOpts.keysize || 2048 + this.exec.init(initOpts, (err) => { + if (err) { + return callback(err) + } + + const conf = flatten(this.opts.config) + async.eachOf(conf, (val, key, cb) => { + this.setConfig(key, val, cb) + }, (err) => { + if (err) { + return callback(err) + } + + this.initialized = true + callback(null, this) + }) + }) + } + + /** + * Delete the repo that was being used. + * If the node was marked as `disposable` this will be called + * automatically when the process is exited. + * + * @param {function(Error)} callback + * @returns {undefined} + */ + cleanup (callback) { + if (this.clean) { + return callback() + } + + this.repo.teardown(callback) + } + + /** + * Start the daemon. + * + * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. + * @param {function(Error, IpfsApi)} callback + * @returns {undefined} + */ + start (flags, callback) { + if (typeof flags === 'function') { + callback = flags + flags = undefined // not used + } + + this.exec.start((err) => { + if (err) { + return callback(err) + } + + this._started = true + this.api = this.exec + this.exec.config.get((err, conf) => { + if (err) { + return callback(err) + } + + this._apiAddr = conf.Addresses.API + this._gatewayAddr = conf.Addresses.Gateway + + this.api.apiHost = multiaddr(conf.Addresses.API).nodeAddress().host + this.api.apiPort = multiaddr(conf.Addresses.API).nodeAddress().port + + callback(null, this.api) + }) + }) + } + + /** + * Stop the daemon. + * + * @param {function(Error)} callback + * @returns {undefined} + */ + stop (callback) { + callback = callback || function noop () {} + + if (!this.exec) { + return callback() + } + + this.exec.stop((err) => { + if (err) { + return callback(err) + } + + this._started = false + this.cleanup(callback) + }) + } + + /** + * Kill the `ipfs daemon` process. + * + * First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent + * if the process hasn't exited yet. + * + * @param {function()} callback - Called when the process was killed. + * @returns {undefined} + */ + killProcess (callback) { + this.stop(callback) + } + + /** + * Get the pid of the `ipfs daemon` process. + * + * @returns {number} + */ + pid () { + throw new Error('not implemented') + } + + /** + * Call `ipfs config` + * + * If no `key` is passed, the whole config is returned as an object. + * + * @param {string} [key] - A specific config to retrieve. + * @param {function(Error, (Object|string))} callback + * @returns {undefined} + */ + getConfig (key, callback) { + if (typeof key === 'function') { + callback = key + key = undefined + } + + this.exec.config.get(key, callback) + } + + /** + * Set a config value. + * + * @param {string} key + * @param {string} value + * @param {function(Error)} callback + * @returns {undefined} + */ + setConfig (key, value, callback) { + this.exec.config.set(key, value, callback) + } + + /** + * Get the version of ipfs + * + * @param {function(Error, string)} callback + * @returns {undefined} + */ + version (callback) { + this.exec.version(callback) + } +} + +module.exports = Node diff --git a/src/index.js b/src/index.js index 8887b41e..45ef4888 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ 'use strict' -const localController = require('./local') -const remote = require('./remote') +const localController = require('./daemon-ctrl') +const remote = require('./remote-node') const isNode = require('detect-node') const defaults = require('lodash.defaultsdeep') diff --git a/src/remote/client.js b/src/remote-node/client.js similarity index 93% rename from src/remote/client.js rename to src/remote-node/client.js index ac241a68..394025ac 100644 --- a/src/remote/client.js +++ b/src/remote-node/client.js @@ -107,7 +107,7 @@ const createRemoteFactory = (host, port, secure) => { .send({ initOpts }) .end((err, res) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } this.initialized = res.body.initialized @@ -149,7 +149,7 @@ const createRemoteFactory = (host, port, secure) => { .send({ flags }) .end((err, res) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } this.started = true @@ -174,7 +174,7 @@ const createRemoteFactory = (host, port, secure) => { .query({ id: this._id }) .end((err) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } this.started = false @@ -197,7 +197,7 @@ const createRemoteFactory = (host, port, secure) => { .query({ id: this._id }) .end((err) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } this.started = false @@ -217,7 +217,7 @@ const createRemoteFactory = (host, port, secure) => { .query({ id: this._id }) .end((err, res) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } cb(null, res.body.pid) @@ -246,7 +246,7 @@ const createRemoteFactory = (host, port, secure) => { .query(qr) .end((err, res) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } cb(null, res.body.config) @@ -267,7 +267,7 @@ const createRemoteFactory = (host, port, secure) => { .query({ id: this._id }) .end((err) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } cb(null) @@ -289,7 +289,7 @@ const createRemoteFactory = (host, port, secure) => { .query({ id: this._id }) .end((err, res) => { if (err) { - return cb(err) + return cb(new Error(err.response.body.message)) } const apiAddr = res.body.api ? res.body.api.apiAddr : '' diff --git a/src/remote/index.js b/src/remote-node/index.js similarity index 100% rename from src/remote/index.js rename to src/remote-node/index.js diff --git a/src/remote/routes.js b/src/remote-node/routes.js similarity index 92% rename from src/remote/routes.js rename to src/remote-node/routes.js index e229c382..9b53bfef 100644 --- a/src/remote/routes.js +++ b/src/remote-node/routes.js @@ -1,6 +1,6 @@ 'use strict' -const ipfsFactory = require('../local') +const ipfsFactory = require('../daemon-ctrl') const hat = require('hat') const boom = require('boom') const Joi = require('joi') @@ -27,7 +27,7 @@ module.exports = (server) => { const payload = request.payload || {} ipfsFactory.spawn(payload.opts, (err, ipfsd) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } const id = hat() @@ -82,7 +82,7 @@ module.exports = (server) => { const payload = request.payload || {} nodes[id].init(payload.initOpts, (err, node) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } reply({ initialized: node.initialized }) @@ -103,7 +103,7 @@ module.exports = (server) => { const id = request.query.id nodes[id].cleanup((err) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } reply().code(200) @@ -124,7 +124,7 @@ module.exports = (server) => { const flags = payload.flags || [] nodes[id].start(flags, (err) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } reply({ @@ -148,7 +148,7 @@ module.exports = (server) => { const id = request.query.id nodes[id].stop((err) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } reply().code(200) @@ -170,7 +170,7 @@ module.exports = (server) => { const id = request.query.id nodes[id].killProcess((err) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } reply().code(200) @@ -207,7 +207,7 @@ module.exports = (server) => { const key = request.query.key nodes[id].getConfig(key, (err, config) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } reply({ config }) @@ -235,7 +235,7 @@ module.exports = (server) => { nodes[id].setConfig(key, val, (err) => { if (err) { - return reply(boom.internal(err)) + return reply(boom.badRequest(err)) } reply().code(200) diff --git a/src/remote/server.js b/src/remote-node/server.js similarity index 100% rename from src/remote/server.js rename to src/remote-node/server.js diff --git a/src/utils/clean.js b/src/utils/clean.js new file mode 100644 index 00000000..13752b59 --- /dev/null +++ b/src/utils/clean.js @@ -0,0 +1,15 @@ +'use strict' + +const rimraf = require('rimraf') +const fs = require('fs') + +module.exports = (dir) => { + try { + fs.accessSync(dir) + } catch (err) { + // Does not exist so all good + return + } + + rimraf.sync(dir) +} diff --git a/src/utils/create-repo-browser.js b/src/utils/create-repo-browser.js new file mode 100644 index 00000000..18b220b7 --- /dev/null +++ b/src/utils/create-repo-browser.js @@ -0,0 +1,28 @@ +/* global self */ +'use strict' + +const IPFSRepo = require('ipfs-repo') +const hat = require('hat') + +const idb = self.indexedDB || + self.mozIndexedDB || + self.webkitIndexedDB || + self.msIndexedDB + +function createTempRepo (repoPath) { + repoPath = repoPath || '/ipfs-' + hat() + + const repo = new IPFSRepo(repoPath) + + repo.teardown = (done) => { + repo.close(() => { + idb.deleteDatabase(repoPath) + idb.deleteDatabase(repoPath + '/blocks') + done() + }) + } + + return repo +} + +module.exports = createTempRepo diff --git a/src/utils/create-repo-nodejs.js b/src/utils/create-repo-nodejs.js new file mode 100644 index 00000000..d4a6f87e --- /dev/null +++ b/src/utils/create-repo-nodejs.js @@ -0,0 +1,29 @@ +'use strict' + +const IPFSRepo = require('ipfs-repo') +const clean = require('./clean') +const os = require('os') +const path = require('path') +const hat = require('hat') +const series = require('async/series') + +function createTempRepo (repoPath) { + repoPath = repoPath || path.join(os.tmpdir(), '/ipfs-test-' + hat()) + + const repo = new IPFSRepo(repoPath) + + repo.teardown = (done) => { + series([ + // ignore err, might have been closed already + (cb) => repo.close(() => cb()), + (cb) => { + clean(repoPath) + cb() + } + ], done) + } + + return repo +} + +module.exports = createTempRepo diff --git a/src/utils.js b/src/utils/index.js similarity index 88% rename from src/utils.js rename to src/utils/index.js index 09f76dc5..7131d97a 100644 --- a/src/utils.js +++ b/src/utils/index.js @@ -5,29 +5,32 @@ const fs = require('fs') const hat = require('hat') const os = require('os') const path = require('path') -const exec = require('./exec') +const exec = require('../exec') const safeParse = require('safe-json-parse/callback') +const createRepo = require('./create-repo-nodejs') const join = path.join const isWindows = os.platform() === 'win32' +exports.createRepo = createRepo + // taken from https://github.com/hughsk/flat exports.flatten = (target) => { let output = {} const step = (object, prev) => { object = object || {} Object.keys(object).forEach(function (key) { - var value = object[key] - var isarray = Array.isArray(value) - var type = Object.prototype.toString.call(value) - var isbuffer = Buffer.isBuffer(value) - var isobject = ( + let value = object[key] + let isarray = Array.isArray(value) + let type = Object.prototype.toString.call(value) + let isbuffer = Buffer.isBuffer(value) + let isobject = ( type === '[object Object]' || type === '[object Array]' ) - var newKey = prev + let newKey = prev ? prev + '.' + key : key diff --git a/test/daemon.js b/test/daemon.js index 365e5b36..e5164b56 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -4,6 +4,7 @@ const daemon = require('./spawning') const api = require('./api') const DaemonFactory = require('../src') +const IPFS = require('ipfs') describe('ipfsd-ctl', () => { const df = DaemonFactory.create() @@ -25,4 +26,8 @@ describe('ipfsd-ctl', () => { daemon(df, 'js')() api(df, 'js')() }) + + describe('In-process daemon', () => { + daemon(DaemonFactory.create({ remote: false }), 'proc', IPFS)() + }) }) diff --git a/test/exec.js b/test/exec.js index c597c843..c49a6028 100644 --- a/test/exec.js +++ b/test/exec.js @@ -32,7 +32,7 @@ function psExpect (pid, expect, grace, callback) { function isRunningGrep (pattern, callback) { const cmd = 'ps aux' - cp.exec(cmd, (err, stdout, stderr) => { + cp.exec(cmd, { maxBuffer: 1024 * 500 }, (err, stdout, stderr) => { if (err) { return callback(err) } diff --git a/test/npm-installs.js b/test/npm-installs.js index b92cf6ac..c3bcbd67 100644 --- a/test/npm-installs.js +++ b/test/npm-installs.js @@ -36,8 +36,8 @@ module.exports = (type) => { expect(err).to.not.exist() fs.writeFileSync(path.join(npm3Path, appName)) - delete require.cache[require.resolve('../src/daemon.js')] - const Daemon = require('../src/daemon.js') + delete require.cache[require.resolve('../src/daemon-node.js')] + const Daemon = require('../src/daemon-node.js') const node = new Daemon({ type }) expect(node.exec) @@ -57,8 +57,8 @@ module.exports = (type) => { expect(err).to.not.exist() fs.writeFileSync(path.join(npm2Path, appName)) - delete require.cache[require.resolve('../src/daemon.js')] - const Daemon = require('../src/daemon.js') + delete require.cache[require.resolve('../src/daemon-node.js')] + const Daemon = require('../src/daemon-node.js') const node = new Daemon({ type }) expect(node.exec) diff --git a/test/spawning.js b/test/spawning.js index 91d66f8e..ab7e41fa 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -14,7 +14,7 @@ const isNode = require('detect-node') const addRetrieveTests = require('./add-retrive') -module.exports = (df, type) => { +module.exports = (df, type, exec) => { return () => { const VERSION_STRING = type === 'js' ? `js-ipfs version: ${require('ipfs/package.json').version}` @@ -22,10 +22,10 @@ module.exports = (df, type) => { describe('daemon spawning', () => { it('prints the version', function (done) { - if (!isNode) { + if (!isNode || type === 'proc') { this.skip() } - df.version({ type }, (err, version) => { + df.version({ type, exec }, (err, version) => { expect(err).to.not.exist() expect(version).to.be.eql(VERSION_STRING) done() @@ -41,7 +41,7 @@ module.exports = (df, type) => { }) it('create node', function (done) { - df.spawn({ type, init: false, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ type, exec, init: false, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() expect(ipfsd.api).to.not.exist() @@ -82,7 +82,7 @@ module.exports = (df, type) => { it('create node and init', function (done) { this.timeout(30 * 1000) - df.spawn({ type, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ type, exec, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() expect(ipfsd.api).to.not.exist() @@ -114,7 +114,7 @@ module.exports = (df, type) => { it('create init and start node', function (done) { this.timeout(20 * 1000) - df.spawn({ type }, (err, ipfsd) => { + df.spawn({ type, exec }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() expect(ipfsd.api).to.exist() @@ -129,13 +129,11 @@ module.exports = (df, type) => { describe('spawn a node and pass init options', () => { const addr = '/ip4/127.0.0.1/tcp/5678' - const swarmAddr1 = '/ip4/127.0.0.1/tcp/35555/ws' - const swarmAddr2 = '/ip4/127.0.0.1/tcp/35666' + const swarmAddr1 = '/ip4/127.0.0.1/tcp/35666' const config = { Addresses: { Swarm: [ - swarmAddr1, - swarmAddr2 + swarmAddr1 ], API: addr } @@ -145,7 +143,8 @@ module.exports = (df, type) => { this.timeout(20 * 1000) const options = { config, - type + type, + exec } let ipfsd @@ -162,7 +161,10 @@ module.exports = (df, type) => { (cb) => { ipfsd.getConfig('Addresses.Swarm', (err, res) => { expect(err).to.not.exist() - expect(JSON.parse(res)).to.deep.eql([swarmAddr1, swarmAddr2]) + if (typeof res === 'string') { + res = JSON.parse(res) + } + expect(res).to.deep.eql([swarmAddr1]) cb() }) } @@ -195,7 +197,7 @@ module.exports = (df, type) => { } async.series([ - (cb) => df.spawn({ type, repoPath, disposable: false, config }, (err, node) => { + (cb) => df.spawn({ type, exec, repoPath, disposable: false, config }, (err, node) => { expect(err).to.not.exist() this.ipfsd = node cb() @@ -223,7 +225,7 @@ module.exports = (df, type) => { before(function (done) { this.timeout(20 * 1000) - df.spawn({ type }, (err, res) => { + df.spawn({ type, exec }, (err, res) => { if (err) { return done(err) } @@ -262,11 +264,11 @@ module.exports = (df, type) => { }) it('should give an error if setting an invalid config value', function (done) { - if (type) { + if (type !== 'go') { this.skip() // js doesn't fail on invalid config } else { ipfsd.setConfig('Bootstrap', 'true', (err) => { - expect(err.message).to.match(/failed to set config value/) + expect(err.message).to.match(/(?:Error: )?failed to set config value/mgi) done() }) } From 8886cd80464c21c228702ffaae91b3590bc92010 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 28 Dec 2017 14:08:59 -0600 Subject: [PATCH 63/85] test: add custom args test --- src/in-proc-node.js | 14 ++++++++++++-- test/spawning.js | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/in-proc-node.js b/src/in-proc-node.js index 9dbb7a17..748dd78a 100644 --- a/src/in-proc-node.js +++ b/src/in-proc-node.js @@ -21,6 +21,7 @@ class Node { const IPFS = this.opts.exec + this.opts.args = this.opts.args || [] this.path = this.opts.repoPath this.repo = createRepo(this.path) this.disposable = this.opts.disposable @@ -31,10 +32,15 @@ class Node { this.initialized = false this.api = null + this.opts.EXPERIMENTAL = { pubsub: false, sharding: false } + this.opts.EXPERIMENTAL.pubsub = (this.opts.args.indexOf('--enable-pubsub-experiment') > -1) + this.opts.EXPERIMENTAL.sharding = (this.opts.args.indexOf('--enable-sharding-experiment') > -1) this.exec = new IPFS({ repo: this.repo, init: false, - start: false + start: false, + EXPERIMENTAL: this.opts.EXPERIMENTAL, + libp2p: this.opts.libp2p }) } @@ -189,7 +195,11 @@ class Node { } this._started = false - this.cleanup(callback) + if (this.disposable) { + return this.cleanup(callback) + } + + return callback() }) } diff --git a/test/spawning.js b/test/spawning.js index ab7e41fa..402c40cf 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -11,6 +11,7 @@ chai.use(dirtyChai) const fs = require('fs') const tempDir = require('../src/utils').tempDir const isNode = require('detect-node') +const hat = require('hat') const addRetrieveTests = require('./add-retrive') @@ -220,6 +221,41 @@ module.exports = (df, type, exec) => { }) }) + describe('spawn a node with custom arguments', function () { + if (!isNode && type !== 'proc') { + return + } + + this.ipfsd = null + this.timeout(50 * 1000) + const topic = `test-topic-${hat()}` + + before(function (done) { + df.spawn({ type, exec, args: ['--enable-pubsub-experiment'] }, (err, node) => { + expect(err).to.not.exist() + this.ipfsd = node + done() + }) + }) + + after(function (done) { this.ipfsd.stop(done) }) + + it('should start with pubsub enabled', function (done) { + const handler = (msg) => { + expect(msg.data.toString()).to.equal('hi') + expect(msg).to.have.property('seqno') + expect(Buffer.isBuffer(msg.seqno)).to.eql(true) + expect(msg).to.have.property('topicIDs').eql([topic]) + done() + } + + this.ipfsd.api.pubsub.subscribe(topic, handler, (err) => { + expect(err).to.not.exist() + this.ipfsd.api.pubsub.publish(topic, Buffer.from('hi')) + }) + }) + }) + describe('change config of a disposable node', () => { let ipfsd From 8630ae9c7725218232f71a6080f0dfd76c982a63 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 28 Dec 2017 15:57:06 -0600 Subject: [PATCH 64/85] docs: adding explanation for different daemon types --- README.md | 24 +++++++++++++++---- examples/id/id.js | 21 ++++++++++++++-- examples/local-disposable/local-disposable.js | 20 +++++++++++++++- examples/local/local.js | 16 +++++++------ 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4796f9af..b6fa45b9 100644 --- a/README.md +++ b/README.md @@ -128,15 +128,29 @@ module.exports = { `ipfsd-ctl` can create two types of node controllers, `disposable` and `non-disposable`. A disposable node will be created on a temporary repo which will be optionally initialized and started (the default), as well cleaned up on process exit. A non-disposable node on the other hand, 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. -## IPFS executables +## IPFS executable types + +`ipfsd-ctl` allows spawning different types of executables, such as: + +> `go` + +Invoking `df.spawn({type: 'go', exec: ''})` will spawn a `go-ipfs` node. + +> `js` + +Invoking `df.spawn({type: 'js', exec: ''})` will spawn a `js-ipfs` node. + +> `proc` + +Invoking `df.spawn({type: 'proc', exec: ''})` will spawn an `in-process-ipfs` node using the provided ipfs coderef. Note that, `exec` is require if `type: 'proc'` is used. + +### IPFS executables `ipfsd-ctl` no longer installs go-ipfs nor js-ipfs dependencies, instead it expects them to be provided by the parent project. In order to be able to use both go and js daemons, please make sure that your project includes these two npm packages as dependencies. - `ipfs` - the js-ipfs implementation - `go-ipfs-dep` - the packaged go-ipfs implementation -It is also possible to provide an alternative executable when using `spawn` by setting the `exec` option to the path of the executable. - ## API ### Daemon Factory @@ -161,14 +175,14 @@ It is also possible to provide an alternative executable when using `spawn` by s - `options` - is an optional object the following properties - `type` string (default 'go') - indicates which type of node to spawn - - current valid values are `js` and `go` + - current valid values are `js`, `go` and `proc` - `init` bool (default true) - should the node be initialized - `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable - `disposable` bool (default false) - a new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits - `args` - array of cmd line arguments to be passed to ipfs daemon - `config` - ipfs configuration options - - `exec` - path to the desired IPFS executable to spawn, otherwise `ipfsd-ctl` will try to locate the correct one based on the `type` + - `exec` - path to the desired IPFS executable to spawn, otherwise `ipfsd-ctl` will try to locate the correct one based on the `type`. In the case of `proc` type, exec is required and expects an IPFS coderef - `callback` - is a function with the signature `cb(err, ipfsd)` where: - `err` - is the error set if spawning the node is unsuccessful diff --git a/examples/id/id.js b/examples/id/id.js index c23da1d2..55f89e76 100644 --- a/examples/id/id.js +++ b/examples/id/id.js @@ -1,8 +1,10 @@ /* eslint no-console: 0 */ 'use strict' +const IPFS = require('ipfs') + const DaemonFactory = require('ipfsd-ctl') -const df = DaemonFactory.create() +const df = DaemonFactory.create({ remote: false }) df.spawn((err, ipfsd) => { if (err) { @@ -19,7 +21,7 @@ df.spawn((err, ipfsd) => { }) }) -df.spawn((err, ipfsd) => { +df.spawn({ type: 'js' }, (err, ipfsd) => { if (err) { throw err } @@ -33,3 +35,18 @@ df.spawn((err, ipfsd) => { ipfsd.stop() }) }) + +df.spawn({ type: 'proc', exec: IPFS }, (err, ipfsd) => { + if (err) { + throw err + } + + ipfsd.api.id((err, id) => { + if (err) { + throw err + } + console.log('bob') + console.log(id) + ipfsd.stop(() => process.exit(0)) + }) +}) diff --git a/examples/local-disposable/local-disposable.js b/examples/local-disposable/local-disposable.js index 8202268f..fd5d0172 100644 --- a/examples/local-disposable/local-disposable.js +++ b/examples/local-disposable/local-disposable.js @@ -4,8 +4,10 @@ // Start a disposable node, and get access to the api // print the node id +const IPFS = require('ipfs') + const DaemonFactory = require('ipfsd-ctl') -const df = DaemonFactory.create() +const df = DaemonFactory.create({ remote: false }) // start a go daemon df.spawn((err, ipfsd) => { @@ -40,3 +42,19 @@ df.spawn({ type: 'js' }, (err, ipfsd) => { ipfsd.stop() }) }) + +df.spawn({ type: 'proc', exec: IPFS }, (err, ipfsd) => { + if (err) { + throw err + } + + ipfsd.api.id((err, id) => { + if (err) { + throw err + } + + console.log('js-ipfs') + console.log(id) + ipfsd.stop(() => process.exit(0)) + }) +}) diff --git a/examples/local/local.js b/examples/local/local.js index 929ffe43..10a29bb7 100644 --- a/examples/local/local.js +++ b/examples/local/local.js @@ -1,8 +1,10 @@ /* eslint no-console: 0 */ 'use strict' +const IPFS = require('ipfs') + const DaemonFactory = require('ipfsd-ctl') -const df = DaemonFactory.create() +const df = DaemonFactory.create({ remote: false }) // opens an api connection to local running go-ipfs node df.spawn({ disposable: true }, (err, ipfsd) => { @@ -10,19 +12,19 @@ df.spawn({ disposable: true }, (err, ipfsd) => { throw err } - ipfsd.api.id((err, id) => { + ipfsd.api.id(function (err, id) { if (err) { throw err } - console.log('go-ipfs') + console.log('js-ipfs') console.log(id) ipfsd.stop() }) }) -// opens an api connection to local running js-ipfs node -df.spawn({ type: 'js', disposable: true }, (err, ipfsd) => { +// creates an in-process running js-ipfs node +df.spawn({ type: 'proc', disposable: true, exec: IPFS }, (err, ipfsd) => { if (err) { throw err } @@ -32,8 +34,8 @@ df.spawn({ type: 'js', disposable: true }, (err, ipfsd) => { throw err } - console.log('js-ipfs') + console.log('in-proc-ipfs') console.log(id) - ipfsd.stop() + ipfsd.stop(() => process.exit(0)) }) }) From adc8d25c3e9047036c99d9246212221d2d463e50 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 28 Dec 2017 17:02:38 -0600 Subject: [PATCH 65/85] docs: small clarification --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b6fa45b9..e1ed59c5 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Invoking `df.spawn({type: 'js', exec: ' `proc` -Invoking `df.spawn({type: 'proc', exec: ''})` will spawn an `in-process-ipfs` node using the provided ipfs coderef. Note that, `exec` is require if `type: 'proc'` is used. +Invoking `df.spawn({type: 'proc', exec: ''})` will spawn an `in-process-ipfs` node using the provided code reference that implements the core IPFS API. Note that, `exec` is required if `type: 'proc'` is used. ### IPFS executables From 9e543cae05b0e81fe8a33e635f75117e2f213d6f Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 28 Dec 2017 17:04:34 -0600 Subject: [PATCH 66/85] docs: small clarification --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1ed59c5..327edcf4 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ Invoking `df.spawn({type: 'js', exec: ' `proc` -Invoking `df.spawn({type: 'proc', exec: ''})` will spawn an `in-process-ipfs` node using the provided code reference that implements the core IPFS API. Note that, `exec` is required if `type: 'proc'` is used. +Invoking `df.spawn({type: 'proc', exec: require('ipfs')})` will spawn an `in-process-ipfs` node using the provided code reference that implements the core IPFS API. Note that, `exec` is required if `type: 'proc'` is used. ### IPFS executables From a90c19ae74e560c82cd91d6b4fbdb7fd863062c7 Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 29 Dec 2017 09:56:45 +0000 Subject: [PATCH 67/85] Update circle.yml --- circle.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 2c57d119..58355193 100644 --- a/circle.yml +++ b/circle.yml @@ -2,6 +2,10 @@ machine: node: version: stable + +test: + post: + - npm run coverage -- --upload --providers coveralls dependencies: pre: @@ -12,4 +16,4 @@ dependencies: - sudo apt-get install -f - sudo apt-get install --only-upgrade lsb-base - sudo dpkg -i google-chrome.deb - - google-chrome --version \ No newline at end of file + - google-chrome --version From da9e17bdaa809deda7abdf961f47d5d0e0323519 Mon Sep 17 00:00:00 2001 From: David Dias Date: Fri, 29 Dec 2017 10:26:31 +0000 Subject: [PATCH 68/85] bump timeout --- test/spawning.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/spawning.js b/test/spawning.js index 402c40cf..a30468a7 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -141,11 +141,12 @@ module.exports = (df, type, exec) => { } it('allows passing ipfs config options to spawn', function (done) { - this.timeout(20 * 1000) + this.timeout(60 * 1000) + const options = { - config, - type, - exec + config: config, + type: type, + exec: exec } let ipfsd From 53bfd15e369761d808f33ddfeeeea8676fbf034c Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Sat, 30 Dec 2017 00:47:04 -0600 Subject: [PATCH 69/85] wip: tests and readme --- README.md | 39 +-- package.json | 14 +- src/daemon-ctrl.js | 4 +- src/daemon-node.js | 8 +- src/in-proc-node.js | 13 +- src/remote-node/client.js | 15 +- src/remote-node/routes.js | 20 +- src/remote-node/server.js | 10 +- src/utils/clean.js | 15 -- src/utils/create-repo-nodejs.js | 14 +- src/utils/index.js | 14 +- test/browser.js | 1 + test/node.js | 3 + test/remote/client.js | 455 ++++++++++++++++++++++++++++++++ test/remote/routes.js | 349 ++++++++++++++++++++++++ test/remote/server.js | 30 +++ test/spawning.js | 1 - test/start-stop.js | 53 ++-- test/utils.js | 28 +- 19 files changed, 981 insertions(+), 105 deletions(-) delete mode 100644 src/utils/clean.js create mode 100644 test/remote/client.js create mode 100644 test/remote/routes.js create mode 100644 test/remote/server.js diff --git a/README.md b/README.md index 327edcf4..a9812978 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ npm install --save ipfsd-ctl IPFS daemons are already easy to start and stop, but this module is here to do it from JavaScript itself. -### Local node +### Local daemon (_Spawn from from Node.js_) ```js // Start a disposable node, and get access to the api @@ -68,7 +68,7 @@ df.spawn(function (err, ipfsd) { }) ``` -### Remote node +### Remote node _(Spawn from a Browser or from a remote machine)) ```js // Start a remote disposable node, and get access to the api @@ -97,33 +97,6 @@ server.start((err) => { }) ``` -It's also possible to start the server from `.aegir` `pre` and `post` hooks. - -```js -'use strict' - -const createServer = require('./src').createServer - -const server = createServer() -module.exports = { - karma: { - files: [{ - pattern: 'test/fixtures/**/*', - watched: false, - served: true, - included: false - }], - singleRun: true - }, - hooks: { - browser: { - pre: server.start.bind(server), - post: server.stop.bind(server) - } - } -} -``` - ## Disposable vs non Disposable nodes `ipfsd-ctl` can create two types of node controllers, `disposable` and `non-disposable`. A disposable node will be created on a temporary repo which will be optionally initialized and started (the default), as well cleaned up on process exit. A non-disposable node on the other hand, 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. @@ -155,14 +128,16 @@ Invoking `df.spawn({type: 'proc', exec: require('ipfs')})` will spawn an `in-pro ### Daemon Factory -#### Create a `DaemonFactory` +#### Create a `DaemonFactory` - `const df = DaemonFactory.create([options])` -> `DaemonFactory.create([options])` create a factory that will expose the `df.spawn` method +> `DaemonFactory.create([options])` returns an object that will expose the `df.spawn` method - `options` - an optional object with the following properties - `remote` bool - indicates if the factory should spawn local or remote nodes. By default, local nodes are spawned in Node.js and remote nodes are spawned in Browser environments. - `port` number - the port number to use for the remote factory. It should match the port on which `DaemonFactory.server` was started. Defaults to 9999. +#### Create a DaemonFactory Endpoint - `const server = DaemonFactory.createServer([options]) ` + > `DaemonFactory.createServer` create an instance of the bundled HTTP server used by the remote controller. - exposes `start` and `stop` methods to start and stop the http server. @@ -193,7 +168,7 @@ Invoking `df.spawn({type: 'proc', exec: require('ipfs')})` will spawn an `in-pro > An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) -This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsdCtrl.start()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. +This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsd.start()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. ### IPFS Daemon Controller (ipfsd) diff --git a/package.json b/package.json index d5b09d07..1236afb9 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,8 @@ "async": "^2.6.0", "boom": "^7.1.1", "detect-node": "^2.0.3", - "hat": "0.0.3", "hapi": "^16.6.2", + "hat": "0.0.3", "ipfs-api": "^17.2.5", "joi": "^13.0.2", "lodash.clone": "^4.5.0", @@ -85,14 +85,20 @@ "truthy": "0.0.1" }, "devDependencies": { - "ipfs": "^0.27.5", - "go-ipfs-dep": "0.4.13", "aegir": "^12.2.0", "chai": "^4.1.2", + "detect-port": "^1.2.2", "dirty-chai": "^2.0.1", + "go-ipfs-dep": "0.4.13", + "ipfs": "^0.27.5", + "ipfs-repo": "^0.18.5", "is-running": "1.0.5", "mkdirp": "^0.5.1", - "pre-commit": "^1.2.2" + "pre-commit": "^1.2.2", + "proxyquire": "^1.8.0", + "sinon": "^4.1.3", + "superagent-mocker": "^0.5.2", + "supertest": "^3.0.0" }, "repository": { "type": "git", diff --git a/src/daemon-ctrl.js b/src/daemon-ctrl.js index 1cce61d7..54b835e8 100644 --- a/src/daemon-ctrl.js +++ b/src/daemon-ctrl.js @@ -87,9 +87,9 @@ class DaemonController { const defaultRepo = 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) + options.config = defaults({}, options.config, nonDisposableConfig) } else { - options.config = defaults({}, options.config, {}, defaultConfig) + options.config = defaults({}, options.config, defaultConfig) } let node diff --git a/src/daemon-node.js b/src/daemon-node.js index ed39dda5..4746b979 100644 --- a/src/daemon-node.js +++ b/src/daemon-node.js @@ -275,6 +275,7 @@ class Node { subprocess.once('close', () => { clearTimeout(timeout) this.subprocess = null + this._started = false callback() }) @@ -285,10 +286,11 @@ class Node { /** * Get the pid of the `ipfs daemon` process. * - * @returns {number} + * @param {function()} callback - receives the pid + * @returns {undefined} */ - pid () { - return this.subprocess && this.subprocess.pid + pid (callback) { + callback(this.subprocess && this.subprocess.pid) } /** diff --git a/src/in-proc-node.js b/src/in-proc-node.js index 748dd78a..3cf4767a 100644 --- a/src/in-proc-node.js +++ b/src/in-proc-node.js @@ -4,6 +4,7 @@ const createRepo = require('./utils').createRepo const multiaddr = require('multiaddr') const flatten = require('./utils').flatten const async = require('async') +const defaults = require('lodash.defaultsdeep') /** * Controll a go-ipfs or js-ipfs node. @@ -32,7 +33,17 @@ class Node { this.initialized = false this.api = null - this.opts.EXPERIMENTAL = { pubsub: false, sharding: false } + this.opts.EXPERIMENTAL = defaults({}, opts.EXPERIMENTAL, { + pubsub: false, + sharding: false, + relay: { + enabled: false, + hop: { + enabled: false + } + } + }) + this.opts.EXPERIMENTAL.pubsub = (this.opts.args.indexOf('--enable-pubsub-experiment') > -1) this.opts.EXPERIMENTAL.sharding = (this.opts.args.indexOf('--enable-sharding-experiment') > -1) this.exec = new IPFS({ diff --git a/src/remote-node/client.js b/src/remote-node/client.js index 394025ac..032e4692 100644 --- a/src/remote-node/client.js +++ b/src/remote-node/client.js @@ -107,7 +107,7 @@ const createRemoteFactory = (host, port, secure) => { .send({ initOpts }) .end((err, res) => { if (err) { - return cb(new Error(err.response.body.message)) + return cb(new Error(err.response ? err.response.body.message : err)) } this.initialized = res.body.initialized @@ -140,7 +140,7 @@ const createRemoteFactory = (host, port, secure) => { start (flags, cb) { if (typeof flags === 'function') { cb = flags - flags = {} + flags = [] } request @@ -149,7 +149,7 @@ const createRemoteFactory = (host, port, secure) => { .send({ flags }) .end((err, res) => { if (err) { - return cb(new Error(err.response.body.message)) + return cb(new Error(err.response ? err.response.body.message : err)) } this.started = true @@ -217,7 +217,7 @@ const createRemoteFactory = (host, port, secure) => { .query({ id: this._id }) .end((err, res) => { if (err) { - return cb(new Error(err.response.body.message)) + return cb(new Error(err.response ? err.response.body.message : err)) } cb(null, res.body.pid) @@ -246,7 +246,7 @@ const createRemoteFactory = (host, port, secure) => { .query(qr) .end((err, res) => { if (err) { - return cb(new Error(err.response.body.message)) + return cb(new Error(err.response ? err.response.body.message : err)) } cb(null, res.body.config) @@ -267,7 +267,7 @@ const createRemoteFactory = (host, port, secure) => { .query({ id: this._id }) .end((err) => { if (err) { - return cb(new Error(err.response.body.message)) + return cb(new Error(err.response ? err.response.body.message : err)) } cb(null) @@ -286,10 +286,9 @@ const createRemoteFactory = (host, port, secure) => { request .post(`${baseUrl}/spawn`) .send({ opts }) - .query({ id: this._id }) .end((err, res) => { if (err) { - return cb(new Error(err.response.body.message)) + return cb(new Error(err.response ? err.response.body.message : err)) } const apiAddr = res.body.api ? res.body.api.apiAddr : '' diff --git a/src/remote-node/routes.js b/src/remote-node/routes.js index 9b53bfef..f38d9170 100644 --- a/src/remote-node/routes.js +++ b/src/remote-node/routes.js @@ -1,6 +1,6 @@ 'use strict' -const ipfsFactory = require('../daemon-ctrl') +const DaemonFactory = require('../daemon-ctrl') const hat = require('hat') const boom = require('boom') const Joi = require('joi') @@ -25,7 +25,7 @@ module.exports = (server) => { path: '/spawn', handler: (request, reply) => { const payload = request.payload || {} - ipfsFactory.spawn(payload.opts, (err, ipfsd) => { + DaemonFactory.spawn(payload.opts, (err, ipfsd) => { if (err) { return reply(boom.badRequest(err)) } @@ -53,7 +53,7 @@ module.exports = (server) => { path: '/api-addr', handler: (request, reply) => { const id = request.query.id - reply({ apiAddr: nodes[id].apiAddr() }) + reply({ apiAddr: nodes[id].apiAddr.toString() }) }, config }) @@ -66,7 +66,7 @@ module.exports = (server) => { path: '/getaway-addr', handler: (request, reply) => { const id = request.query.id - reply({ getawayAddr: nodes[id].getawayAddr() }) + reply({ getawayAddr: nodes[id].gatewayAddr.toString() }) }, config }) @@ -189,7 +189,7 @@ module.exports = (server) => { path: '/pid', handler: (request, reply) => { const id = request.query.id - reply({ pid: nodes[id].pid(nodes[id]) }) + reply({ pid: nodes[id].pid }) }, config }) @@ -241,6 +241,14 @@ module.exports = (server) => { reply().code(200) }) }, - config + config: defaults({}, { + validate: { + payload: { + key: Joi.string(), + value: Joi.any() + } + } + }, config) + }) } diff --git a/src/remote-node/server.js b/src/remote-node/server.js index 49035fe1..6703fcf9 100644 --- a/src/remote-node/server.js +++ b/src/remote-node/server.js @@ -6,7 +6,7 @@ const routes = require('./routes') class Server { constructor (port) { this.server = null - this.port = port || 9999 + this.port = typeof port === 'undefined' ? 9999 : port } start (cb) { @@ -16,7 +16,13 @@ class Server { this.server.connection({ port: this.port, host: 'localhost', routes: { cors: true } }) routes(this.server) - this.server.start(cb) + this.server.start((err) => { + if (err) { + return cb(err) + } + + cb(null, this.server) + }) } stop (cb) { diff --git a/src/utils/clean.js b/src/utils/clean.js deleted file mode 100644 index 13752b59..00000000 --- a/src/utils/clean.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -const rimraf = require('rimraf') -const fs = require('fs') - -module.exports = (dir) => { - try { - fs.accessSync(dir) - } catch (err) { - // Does not exist so all good - return - } - - rimraf.sync(dir) -} diff --git a/src/utils/create-repo-nodejs.js b/src/utils/create-repo-nodejs.js index d4a6f87e..79d91fe3 100644 --- a/src/utils/create-repo-nodejs.js +++ b/src/utils/create-repo-nodejs.js @@ -1,11 +1,23 @@ 'use strict' const IPFSRepo = require('ipfs-repo') -const clean = require('./clean') const os = require('os') const path = require('path') const hat = require('hat') const series = require('async/series') +const rimraf = require('rimraf') +const fs = require('fs') + +const clean = (dir) => { + try { + fs.accessSync(dir) + } catch (err) { + // Does not exist so all good + return + } + + rimraf.sync(dir) +} function createTempRepo (repoPath) { repoPath = repoPath || path.join(os.tmpdir(), '/ipfs-test-' + hat()) diff --git a/src/utils/index.js b/src/utils/index.js index 7131d97a..4b329876 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -17,20 +17,20 @@ exports.createRepo = createRepo // taken from https://github.com/hughsk/flat exports.flatten = (target) => { - let output = {} + const output = {} const step = (object, prev) => { object = object || {} Object.keys(object).forEach(function (key) { - let value = object[key] - let isarray = Array.isArray(value) - let type = Object.prototype.toString.call(value) - let isbuffer = Buffer.isBuffer(value) - let isobject = ( + const value = object[key] + const isarray = Array.isArray(value) + const type = Object.prototype.toString.call(value) + const isbuffer = Buffer.isBuffer(value) + const isobject = ( type === '[object Object]' || type === '[object Array]' ) - let newKey = prev + const newKey = prev ? prev + '.' + key : key diff --git a/test/browser.js b/test/browser.js index 960fc61e..622c4703 100644 --- a/test/browser.js +++ b/test/browser.js @@ -2,3 +2,4 @@ 'use strict' require('./daemon') +require('./remote/client') diff --git a/test/node.js b/test/node.js index b0ceeca4..af53ce47 100644 --- a/test/node.js +++ b/test/node.js @@ -5,6 +5,9 @@ require('./daemon') require('./exec') require('./utils') +require('./remote/routes') +require('./remote/client') +require('./remote/server') const startStop = require('./start-stop') const install = require('./npm-installs') diff --git a/test/remote/client.js b/test/remote/client.js new file mode 100644 index 00000000..0c5990f5 --- /dev/null +++ b/test/remote/client.js @@ -0,0 +1,455 @@ +/* eslint max-nested-callbacks: ["error", 6] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const hat = require('hat') + +const boom = require('boom') +const proxyquire = require('proxyquire') +const superagent = require('superagent') +const mock = require('superagent-mocker')(superagent) + +const clientFactory = proxyquire('../../src/remote-node/client', { + superagent: () => { + return superagent + } +}) + +describe('client', () => { + const client = clientFactory() + + let node = null + describe('.spawn', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/spawn', (req) => { + expect(req.body.opts.opt1).to.equal('hello!') + return { + body: { + id: hat(), + api: { + apiAddr: '/ip4/127.0.0.1/tcp/5001', + gatewayAddr: '/ip4/127.0.0.1/tcp/8080' + } + } + } + }) + + client.spawn({ opt1: 'hello!' }, (err, ipfsd) => { + expect(err).to.not.exist() + expect(ipfsd).to.exist() + expect(ipfsd.apiAddr.toString()).to.equal('/ip4/127.0.0.1/tcp/5001') + expect(ipfsd.gatewayAddr.toString()).to.equal('/ip4/127.0.0.1/tcp/8080') + node = ipfsd + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/spawn', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + client.spawn((err, ipfsd) => { + expect(err).to.exist() + expect(ipfsd).to.not.exist() + done() + }) + }) + }) + }) + + describe('.init', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/init', (req) => { + expect(req.query.id).to.exist() + expect(req.body.initOpts.initOpt1).to.equal('hello!') + + return { + body: { + initialized: true + } + } + }) + + node.init({ initOpt1: 'hello!' }, (err, res) => { + expect(err).to.not.exist() + expect(res.initialized).to.be.ok() + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/init', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.init((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) + + describe('.cleanup', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/cleanup', (req) => { + expect(req.query.id).to.exist() + }) + + node.cleanup((err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle invalid request', (done) => { + mock.post('http://localhost:9999/cleanup', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.init((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) + + describe('.start', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/start', (req) => { + expect(req.query.id).to.exist() + expect(req.body.flags).to.exist() + expect(req.body.flags[0]).to.equal('--enable-pubsub-experiment') + + return { + body: { + api: { + apiAddr: '/ip4/127.0.0.1/tcp/5001', + gatewayAddr: '/ip4/127.0.0.1/tcp/8080' + } + } + } + }) + + node.start(['--enable-pubsub-experiment'], (err, api) => { + expect(err).to.not.exist() + expect(api).to.exist() + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle invalid request', (done) => { + mock.post('http://localhost:9999/start', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.start((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) + + describe('.stop', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/stop', (req) => { + expect(req.query.id).to.exist() + }) + + node.stop((err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle invalid request', (done) => { + mock.post('http://localhost:9999/stop', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.stop((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) + + describe('.killProcess', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.post('http://localhost:9999/kill', (req) => { + expect(req.query.id).to.exist() + }) + + node.killProcess((err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle invalid request', (done) => { + mock.post('http://localhost:9999/kill', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.killProcess((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) + + describe('.pid', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.get('http://localhost:9999/pid', (req) => { + expect(req.query.id).to.exist() + return { + body: { + pid: 1 + } + } + }) + + node.pid((err, res) => { + expect(err).to.not.exist() + expect(res).to.equal(1) + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle invalid request', (done) => { + mock.get('http://localhost:9999/pid', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.pid((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) + + describe('.getConfig', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.get('http://localhost:9999/config', (req) => { + expect(req.query.id).to.exist() + expect(req.query.key).to.equal('foo') + return { + body: { + config: { + foo: 'bar' + } + } + } + }) + + node.getConfig('foo', (err, res) => { + expect(err).to.not.exist() + expect(res.foo).to.equal('bar') + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle invalid request', (done) => { + mock.get('http://localhost:9999/config', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.getConfig((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) + + describe('.setConfig', () => { + describe('handle valid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle valid request', (done) => { + mock.put('http://localhost:9999/config', (req) => { + expect(req.query.id).to.exist() + expect(req.body.key).to.equal('foo') + expect(req.body.value).to.equal('bar') + }) + + node.setConfig('foo', 'bar', (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + describe('handle invalid', () => { + after(() => { + mock.clearRoutes() + }) + + it('should handle invalid request', (done) => { + mock.put('http://localhost:9999/config', () => { + const badReq = boom.badRequest() + return { + status: badReq.output.statusCode, + body: { + message: badReq.message + } + } + }) + + node.setConfig('foo', 'bar', (err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) +}) diff --git a/test/remote/routes.js b/test/remote/routes.js new file mode 100644 index 00000000..e3629d1b --- /dev/null +++ b/test/remote/routes.js @@ -0,0 +1,349 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const proxyquire = require('proxyquire') +const multiaddr = require('multiaddr') + +const Hapi = require('hapi') +const routes = proxyquire('../../src/remote-node/routes', { + '../daemon-ctrl': { + spawn (ops, cb) { + const node = {} + node.apiAddr = multiaddr('/ip4/127.0.0.1/tcp/5001') + node.gatewayAddr = multiaddr('/ip4/127.0.0.1/tcp/8080') + node.started = false + + node.init = (opts, cb) => { + cb(null, node) + } + + node.cleanup = (cb) => { + cb() + } + + node.start = (_, cb) => { + node.started = true + + const api = {} + api.apiHost = node.apiAddr.nodeAddress().address + api.apiPort = node.apiAddr.nodeAddress().port + + api.gatewayHost = node.gatewayAddr.nodeAddress().address + api.gatewayPort = node.gatewayAddr.nodeAddress().port + + node.api = api + cb(null, api) + } + + node.stop = (cb) => { + node.killProcess(cb) + } + node.killProcess = (cb) => { + node.started = false + cb() + } + + node.pid = (cb) => { + cb(null, 1) + } + + node.getConfig = (key, cb) => { + cb(null, { foo: 'bar' }) + } + + node.setConfig = (key, val, cb) => { + cb() + } + + node.start(null, () => { + cb(null, node) + }) + } + } +}) + +describe('routes', () => { + let id + const server = new Hapi.Server() + before(() => { + server.connection() + routes(server) + }) + + after((done) => server.stop(done)) + + describe('POST /spawn', () => { + it('should return 200', (done) => { + server.inject({ + method: 'POST', + url: '/spawn', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result.id).to.exist() + expect(res.result.api.apiAddr).to.exist() + expect(res.result.api.gatewayAddr).to.exist() + + id = res.result.id + done() + }) + }) + }) + + describe('GET /api-addr', () => { + it('should return 200', (done) => { + server.inject({ + method: 'GET', + url: `/api-addr?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result.apiAddr).to.exist() + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'GET', + url: '/api-addr', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('GET /getaway-addr', () => { + it('should return 200', (done) => { + server.inject({ + method: 'GET', + url: `/getaway-addr?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result.getawayAddr).to.exist() + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'GET', + url: '/getaway-addr', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('POST /init', () => { + it('should return 200', (done) => { + server.inject({ + method: 'POST', + url: `/init?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'POST', + url: '/init', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('POST /cleanup', () => { + it('should return 200', (done) => { + server.inject({ + method: 'POST', + url: `/cleanup?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'POST', + url: '/cleanup', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('POST /start', () => { + it('should return 200', (done) => { + server.inject({ + method: 'POST', + url: `/start?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'POST', + url: '/start', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('POST /stop', () => { + it('should return 200', (done) => { + server.inject({ + method: 'POST', + url: `/stop?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'POST', + url: '/stop', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('POST /kill', () => { + it('should return 200', (done) => { + server.inject({ + method: 'POST', + url: `/kill?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'POST', + url: '/kill', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('GET /pid', () => { + it('should return 200', (done) => { + server.inject({ + method: 'GET', + url: `/pid?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'GET', + url: '/pid', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('GET /config', () => { + it('should return 200', (done) => { + server.inject({ + method: 'GET', + url: `/config?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { id } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'GET', + url: '/config', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + describe('PUT /config', () => { + it('should return 200', (done) => { + server.inject({ + method: 'PUT', + url: `/config?id=${id}`, + headers: { 'content-type': 'application/json' }, + payload: { key: 'foo', value: 'bar' } + }, (res) => { + expect(res.statusCode).to.equal(200) + done() + }) + }) + + it('should return 400', (done) => { + server.inject({ + method: 'PUT', + url: '/config', + headers: { 'content-type': 'application/json' } + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) +}) diff --git a/test/remote/server.js b/test/remote/server.js new file mode 100644 index 00000000..db98b904 --- /dev/null +++ b/test/remote/server.js @@ -0,0 +1,30 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const Server = require('../../src/remote-node/server') +const portUsed = require('detect-port') + +describe('server', () => { + let server + before((done) => { + server = new Server() + server.start(done) + }) + + it('should start', (done) => { + portUsed(9999, (err, port) => { + expect(err).to.not.exist() + expect(port !== 9999).to.be.ok() + done() + }) + }) + + it('should stop', (done) => { + server.stop(done) + }) +}) diff --git a/test/spawning.js b/test/spawning.js index a30468a7..5e70eb9c 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -142,7 +142,6 @@ module.exports = (df, type, exec) => { it('allows passing ipfs config options to spawn', function (done) { this.timeout(60 * 1000) - const options = { config: config, type: type, diff --git a/test/start-stop.js b/test/start-stop.js index 30be226f..45c2ccb5 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -40,13 +40,15 @@ module.exports = (type) => { expect(ipfsd).to.exist() }) - it('daemon should not be running', () => { - expect(ipfsd.pid()).to.not.exist() + it('daemon should not be running', (done) => { + ipfsd.pid((pid) => { + expect(pid).to.not.exist() + done() + }) }) }) let pid - describe('starting', () => { let api @@ -55,12 +57,14 @@ module.exports = (type) => { ipfsd.start((err, ipfs) => { expect(err).to.not.exist() - pid = ipfsd.pid() - api = ipfs + ipfsd.pid((_pid) => { + pid = _pid + api = ipfs - // actually running? - done = once(done) - exec('kill', ['-0', pid], { cleanup: true }, () => done()) + // actually running? + done = once(done) + exec('kill', ['-0', pid], { cleanup: true }, () => done()) + }) }) }) @@ -91,13 +95,15 @@ module.exports = (type) => { }, 100) }) - it('should be stopped', function () { + it('should be stopped', function (done) { this.timeout(30 * 1000) // shutdown grace period is already 10500 - - expect(ipfsd.pid()).to.not.exist() - expect(stopped).to.equal(true) - expect(fs.existsSync(path.join(ipfsd.path, 'repo.lock'))).to.not.be.ok() - expect(fs.existsSync(path.join(ipfsd.path, 'api'))).to.not.be.ok() + ipfsd.pid((pid) => { + expect(pid).to.not.exist() + expect(stopped).to.equal(true) + expect(fs.existsSync(path.join(ipfsd.path, 'repo.lock'))).to.not.be.ok() + expect(fs.existsSync(path.join(ipfsd.path, 'api'))).to.not.be.ok() + done() + }) }) }) }) @@ -186,23 +192,30 @@ module.exports = (type) => { expect(ipfsd).to.exist() }) - it('daemon should not be running', () => { - expect(ipfsd.pid()).to.exist() + it('daemon should not be running', (done) => { + ipfsd.pid((pid) => { + expect(pid).to.exist() + done() + }) }) it('should stop', (done) => { ipfsd.stop((err) => { expect(err).to.not.exist() - expect(ipfsd.pid()).to.not.exist() - done() + ipfsd.pid((pid) => { + expect(pid).to.not.exist() + done() + }) }) }) it('should start', (done) => { ipfsd.start((err) => { expect(err).to.not.exist() - expect(ipfsd.pid()).to.exist() - done() + ipfsd.pid((pid) => { + expect(pid).to.exist() + done() + }) }) }) diff --git a/test/utils.js b/test/utils.js index 4b2bc5b8..fe8b9db2 100644 --- a/test/utils.js +++ b/test/utils.js @@ -12,9 +12,12 @@ const utils = require('../src/utils') const flatten = utils.flatten const tempDir = utils.tempDir const findIpfsExecutable = utils.findIpfsExecutable +const createRepo = utils.createRepo + +const IPFSRepo = require('ipfs-repo') describe('utils', () => { - describe('flatten config', () => { + describe('.flatten', () => { it('should flatten', () => { expect(flatten({ a: { b: { c: [1, 2, 3] } } })).to.deep.equal({ 'a.b.c': [1, 2, 3] }) }) @@ -28,7 +31,7 @@ describe('utils', () => { }) }) - describe('tmp dir', () => { + describe('.tempDir', () => { it('should create tmp directory path for go-ipfs', () => { const tmpDir = tempDir() expect(tmpDir).to.exist() @@ -42,7 +45,7 @@ describe('utils', () => { }) }) - describe('find executable', () => { + describe('.findIpfsExecutable', () => { it('should find go executable', () => { const execPath = findIpfsExecutable('go', __dirname) expect(execPath).to.exist() @@ -57,4 +60,23 @@ describe('utils', () => { expect(fs.existsSync(execPath)).to.be.ok() }) }) + + describe('.createRepo', () => { + let repo = null + let repoPath = tempDir() + it('should create repo', () => { + repo = createRepo(repoPath) + expect(repo).to.exist() + expect(repo).to.be.instanceOf(IPFSRepo) + expect(fs.existsSync(repoPath)).to.be.ok() + }) + + it('should cleanup repo', (done) => { + repo.teardown((err) => { + expect(err).to.not.exist() + expect(!fs.existsSync(repoPath)).to.be.ok() + done() + }) + }) + }) }) From cb7c9577ad4d6e6d334f7aa63250181642857557 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Sun, 31 Dec 2017 18:10:25 -0600 Subject: [PATCH 70/85] wip: rework DaemonFactory to accept type --- src/daemon-ctrl.js | 17 +- src/index.js | 10 +- src/remote-node/client.js | 515 +++++++++++++++++++------------------- src/remote-node/index.js | 4 +- src/remote-node/routes.js | 5 +- test/api.js | 2 +- test/browser.js | 1 - test/daemon.js | 8 +- test/remote/client.js | 4 +- test/spawning.js | 15 +- test/start-stop.js | 6 +- 11 files changed, 302 insertions(+), 285 deletions(-) diff --git a/src/daemon-ctrl.js b/src/daemon-ctrl.js index 54b835e8..e7a19eca 100644 --- a/src/daemon-ctrl.js +++ b/src/daemon-ctrl.js @@ -39,6 +39,10 @@ const defaultConfig = { * @namespace DaemonController */ class DaemonController { + constructor (type) { + this.type = type || 'go' + } + /** * Get the version of the currently used go-ipfs binary. * @@ -47,15 +51,17 @@ class DaemonController { * @param {function(Error, string)} callback * @returns {undefined} */ - static version (opts, callback) { - (new Node(opts)).version(callback) + version (opts, callback) { + opts = opts || {} + opts.type = this.type + const node = new Node(opts) + node.version(callback) } /** * Spawn an IPFS node, either js-ipfs or go-ipfs * * Options are: - * - `type` string (default 'go') - the type of the daemon to spawn, can be either 'go' or 'js' * - `init` bool - should the node be initialized * - `start` bool - should the node be started * - `repoPath` string - the repository path to use for this node, ignored if node is disposable @@ -68,7 +74,7 @@ class DaemonController { * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` * @return {undefined} */ - static spawn (opts, callback) { + spawn (opts, callback) { if (typeof opts === 'function') { callback = opts opts = defaultOptions @@ -93,7 +99,8 @@ class DaemonController { } let node - if (options.type === 'proc') { + options.type = this.type + if (this.type === 'proc') { if (typeof options.exec !== 'function') { return callback(new Error(`'type' proc requires 'exec' to be a coderef`)) } diff --git a/src/index.js b/src/index.js index 45ef4888..154acaa4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ 'use strict' -const localController = require('./daemon-ctrl') +const LocalController = require('./daemon-ctrl') const remote = require('./remote-node') const isNode = require('detect-node') const defaults = require('lodash.defaultsdeep') @@ -9,11 +9,15 @@ class DaemonFactory { static create (opts) { const options = defaults({}, opts, { remote: !isNode }) + if (options.remote && options.type === 'proc') { + options.remote = false + } + if (options.remote) { - return remote.remoteController(options.port) + return new remote.RemoteController(options) } - return localController + return new LocalController(options.type) } static createServer (port) { diff --git a/src/remote-node/client.js b/src/remote-node/client.js index 032e4692..c807bfcd 100644 --- a/src/remote-node/client.js +++ b/src/remote-node/client.js @@ -20,289 +20,298 @@ function createApi (apiAddr, gwAddr) { return api } -const createRemoteFactory = (host, port, secure) => { - // create a client +class Node { + constructor (baseUrl, id, apiAddr, gwAddrs) { + this.baseUrl = baseUrl + this._id = id + this._apiAddr = multiaddr(apiAddr) + this._gwAddr = multiaddr(gwAddrs) + this.initialized = false + this.started = false + this.api = createApi(apiAddr, gwAddrs) + } - if (!host) { - host = 'localhost' + /** + * Get the address of connected IPFS API. + * + * @returns {Multiaddr} + */ + get apiAddr () { + return this._apiAddr } - if (!port) { - port = 9999 + /** + * Set the address of connected IPFS API. + * + * @param {Multiaddr} addr + * @returns {void} + */ + set apiAddr (addr) { + this._apiAddr = addr } - if (typeof host === 'number') { - port = host - host = 'localhost' + /** + * Get the address of connected IPFS HTTP Gateway. + * + * @returns {Multiaddr} + */ + get gatewayAddr () { + return this._gwAddr } - const baseUrl = `${secure ? 'https://' : 'http://'}${host}:${port}` + /** + * Set the address of connected IPFS Gateway. + * + * @param {Multiaddr} addr + * @returns {void} + */ + set gatewayAddr (addr) { + this._gwAddr = addr + } - class Node { - constructor (id, apiAddr, gwAddrs) { - this._id = id - this._apiAddr = multiaddr(apiAddr) - this._gwAddr = multiaddr(gwAddrs) - this.initialized = false - this.started = false - this.api = createApi(apiAddr, gwAddrs) + /** + * Initialize a repo. + * + * @param {Object} [initOpts={}] + * @param {number} [initOpts.keysize=2048] - The bit size of the identiy key. + * @param {string} [initOpts.directory=IPFS_PATH] - The location of the repo. + * @param {function (Error, Node)} cb + * @returns {undefined} + */ + init (initOpts, cb) { + if (typeof initOpts === 'function') { + cb = initOpts + initOpts = {} } - /** - * Get the address of connected IPFS API. - * - * @returns {Multiaddr} - */ - get apiAddr () { - return this._apiAddr - } + request + .post(`${this.baseUrl}/init`) + .query({ id: this._id }) + .send({ initOpts }) + .end((err, res) => { + if (err) { + return cb(new Error(err.response ? err.response.body.message : err)) + } + + this.initialized = res.body.initialized + cb(null, res.body) + }) + } - /** - * Set the address of connected IPFS API. - * - * @param {Multiaddr} addr - * @returns {void} - */ - set apiAddr (addr) { - this._apiAddr = addr - } + /** + * Delete the repo that was being used. + * If the node was marked as `disposable` this will be called + * automatically when the process is exited. + * + * @param {function(Error)} cb + * @returns {undefined} + */ + cleanup (cb) { + request + .post(`${this.baseUrl}/cleanup`) + .query({ id: this._id }) + .end((err) => { cb(err) }) + } - /** - * Get the address of connected IPFS HTTP Gateway. - * - * @returns {Multiaddr} - */ - get gatewayAddr () { - return this._gwAddr + /** + * Start the daemon. + * + * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. + * @param {function(Error, IpfsApi)} cb + * @returns {undefined} + */ + start (flags, cb) { + if (typeof flags === 'function') { + cb = flags + flags = [] } - /** - * Set the address of connected IPFS Gateway. - * - * @param {Multiaddr} addr - * @returns {void} - */ - set gatewayAddr (addr) { - this._gwAddr = addr - } + request + .post(`${this.baseUrl}/start`) + .query({ id: this._id }) + .send({ flags }) + .end((err, res) => { + if (err) { + return cb(new Error(err.response ? err.response.body.message : err)) + } - /** - * Initialize a repo. - * - * @param {Object} [initOpts={}] - * @param {number} [initOpts.keysize=2048] - The bit size of the identiy key. - * @param {string} [initOpts.directory=IPFS_PATH] - The location of the repo. - * @param {function (Error, Node)} cb - * @returns {undefined} - */ - init (initOpts, cb) { - if (typeof initOpts === 'function') { - cb = initOpts - initOpts = {} - } - - request - .post(`${baseUrl}/init`) - .query({ id: this._id }) - .send({ initOpts }) - .end((err, res) => { - if (err) { - return cb(new Error(err.response ? err.response.body.message : err)) - } - - this.initialized = res.body.initialized - cb(null, res.body) - }) - } + this.started = true - /** - * Delete the repo that was being used. - * If the node was marked as `disposable` this will be called - * automatically when the process is exited. - * - * @param {function(Error)} cb - * @returns {undefined} - */ - cleanup (cb) { - request - .post(`${baseUrl}/cleanup`) - .query({ id: this._id }) - .end((err) => { cb(err) }) - } + const apiAddr = res.body.api ? res.body.api.apiAddr : '' + const gatewayAddr = res.body.api ? res.body.api.gatewayAddr : '' - /** - * Start the daemon. - * - * @param {Array} [flags=[]] - Flags to be passed to the `ipfs daemon` command. - * @param {function(Error, IpfsApi)} cb - * @returns {undefined} - */ - start (flags, cb) { - if (typeof flags === 'function') { - cb = flags - flags = [] - } - - request - .post(`${baseUrl}/start`) - .query({ id: this._id }) - .send({ flags }) - .end((err, res) => { - if (err) { - return cb(new Error(err.response ? err.response.body.message : err)) - } - - this.started = true - - const apiAddr = res.body.api ? res.body.api.apiAddr : '' - const gatewayAddr = res.body.api ? res.body.api.gatewayAddr : '' - - this.api = createApi(apiAddr, gatewayAddr) - return cb(null, this.api) - }) - } + this.api = createApi(apiAddr, gatewayAddr) + return cb(null, this.api) + }) + } + + /** + * Stop the daemon. + * + * @param {function(Error)} cb + * @returns {undefined} + */ + stop (cb) { + request + .post(`${this.baseUrl}/stop`) + .query({ id: this._id }) + .end((err) => { + if (err) { + return cb(new Error(err.response.body.message)) + } + + this.started = false + cb(null) + }) + } - /** - * Stop the daemon. - * - * @param {function(Error)} cb - * @returns {undefined} - */ - stop (cb) { - request - .post(`${baseUrl}/stop`) - .query({ id: this._id }) - .end((err) => { - if (err) { - return cb(new Error(err.response.body.message)) - } - - this.started = false - cb(null) - }) + /** + * Kill the `ipfs daemon` process. + * + * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent + * if the process hasn't exited yet. + * + * @param {function()} cb - Called when the process was killed. + * @returns {undefined} + */ + killProcess (cb) { + request + .post(`${this.baseUrl}/kill`) + .query({ id: this._id }) + .end((err) => { + if (err) { + return cb(new Error(err.response.body.message)) + } + + this.started = false + cb(null) + }) + } + + /** + * Get the pid of the `ipfs daemon` process. + * + * @param {Function} cb + * @returns {number} + */ + pid (cb) { + request + .get(`${this.baseUrl}/pid`) + .query({ id: this._id }) + .end((err, res) => { + if (err) { + return cb(new Error(err.response ? err.response.body.message : err)) + } + + cb(null, res.body.pid) + }) + } + + /** + * Call `ipfs config` + * + * If no `key` is passed, the whole config is returned as an object. + * + * @param {string} [key] - A specific config to retrieve. + * @param {function(Error, (Object|string))} cb + * @returns {undefined} + */ + getConfig (key, cb) { + if (typeof key === 'function') { + cb = key + key = null } - /** - * Kill the `ipfs daemon` process. - * - * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent - * if the process hasn't exited yet. - * - * @param {function()} cb - Called when the process was killed. - * @returns {undefined} - */ - killProcess (cb) { - request - .post(`${baseUrl}/kill`) - .query({ id: this._id }) - .end((err) => { - if (err) { - return cb(new Error(err.response.body.message)) - } - - this.started = false - cb(null) - }) + const qr = { id: this._id } + qr.key = key || undefined + request + .get(`${this.baseUrl}/config`) + .query(qr) + .end((err, res) => { + if (err) { + return cb(new Error(err.response ? err.response.body.message : err)) + } + + cb(null, res.body.config) + }) + } + + /** + * Set a config value. + * + * @param {string} key + * @param {string} value + * @param {function(Error)} cb + * @returns {undefined} + */ + setConfig (key, value, cb) { + request.put(`${this.baseUrl}/config`) + .send({ key, value }) + .query({ id: this._id }) + .end((err) => { + if (err) { + return cb(new Error(err.response ? err.response.body.message : err)) + } + + cb(null) + }) + } +} + +class RemoteFactory { + constructor (opts) { + opts = opts || {} + if (!opts.host) { + opts.host = 'localhost' } - /** - * Get the pid of the `ipfs daemon` process. - * - * @param {Function} cb - * @returns {number} - */ - pid (cb) { - request - .get(`${baseUrl}/pid`) - .query({ id: this._id }) - .end((err, res) => { - if (err) { - return cb(new Error(err.response ? err.response.body.message : err)) - } - - cb(null, res.body.pid) - }) + if (!opts.port) { + opts.port = 9999 } - /** - * Call `ipfs config` - * - * If no `key` is passed, the whole config is returned as an object. - * - * @param {string} [key] - A specific config to retrieve. - * @param {function(Error, (Object|string))} cb - * @returns {undefined} - */ - getConfig (key, cb) { - if (typeof key === 'function') { - cb = key - key = null - } - - const qr = { id: this._id } - qr.key = key || undefined - request - .get(`${baseUrl}/config`) - .query(qr) - .end((err, res) => { - if (err) { - return cb(new Error(err.response ? err.response.body.message : err)) - } - - cb(null, res.body.config) - }) + if (typeof opts.host === 'number') { + opts.port = opts.host + opts.host = 'localhost' } - /** - * Set a config value. - * - * @param {string} key - * @param {string} value - * @param {function(Error)} cb - * @returns {undefined} - */ - setConfig (key, value, cb) { - request.put(`${baseUrl}/config`) - .send({ key, value }) - .query({ id: this._id }) - .end((err) => { - if (err) { - return cb(new Error(err.response ? err.response.body.message : err)) - } - - cb(null) - }) + this.port = opts.port + this.host = opts.host + this.type = opts.type || 'go' + + if (this.type === 'proc') { + throw new Error(`'proc' is not allowed in remote mode`) } + + this.baseUrl = `${opts.secure ? 'https://' : 'http://'}${this.host}:${this.port}` } - return { - spawn: (opts, cb) => { - if (typeof opts === 'function') { - cb = opts - opts = {} - } - - opts = opts || {} - request - .post(`${baseUrl}/spawn`) - .send({ opts }) - .end((err, res) => { - if (err) { - return cb(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 node = new Node( - res.body.id, - apiAddr, - gatewayAddr) - - cb(null, node) - }) + spawn (opts, cb) { + if (typeof opts === 'function') { + cb = opts + opts = {} } + + opts = opts || {} + request + .post(`${this.baseUrl}/spawn`) + .send({ opts, type: this.type }) + .end((err, res) => { + if (err) { + return cb(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 node = new Node( + this.baseUrl, + res.body.id, + apiAddr, + gatewayAddr) + + cb(null, node) + }) } } -module.exports = createRemoteFactory +module.exports = RemoteFactory diff --git a/src/remote-node/index.js b/src/remote-node/index.js index 93f4d0aa..65705cec 100644 --- a/src/remote-node/index.js +++ b/src/remote-node/index.js @@ -1,9 +1,9 @@ 'use strict' const Server = require('./server') -const remoteController = require('./client') +const RemoteController = require('./client') module.exports = { Server, - remoteController + RemoteController } diff --git a/src/remote-node/routes.js b/src/remote-node/routes.js index f38d9170..2ca40467 100644 --- a/src/remote-node/routes.js +++ b/src/remote-node/routes.js @@ -1,6 +1,6 @@ 'use strict' -const DaemonFactory = require('../daemon-ctrl') +const CtrlFactory = require('../daemon-ctrl') const hat = require('hat') const boom = require('boom') const Joi = require('joi') @@ -25,7 +25,8 @@ module.exports = (server) => { path: '/spawn', handler: (request, reply) => { const payload = request.payload || {} - DaemonFactory.spawn(payload.opts, (err, ipfsd) => { + const ctrl = new CtrlFactory(payload.type) + ctrl.spawn(payload.opts, (err, ipfsd) => { if (err) { return reply(boom.badRequest(err)) } diff --git a/test/api.js b/test/api.js index 92982ce4..a90bab3a 100644 --- a/test/api.js +++ b/test/api.js @@ -93,7 +93,7 @@ module.exports = (df, type) => { describe('validate api', () => { it('starts the daemon and returns valid API and gateway addresses', function (done) { this.timeout(20 * 1000) - df.spawn({ type, config }, (err, res) => { + df.spawn({ config }, (err, res) => { expect(err).to.not.exist() const ipfsd = res diff --git a/test/browser.js b/test/browser.js index 622c4703..960fc61e 100644 --- a/test/browser.js +++ b/test/browser.js @@ -2,4 +2,3 @@ 'use strict' require('./daemon') -require('./remote/client') diff --git a/test/daemon.js b/test/daemon.js index e5164b56..bcc36c41 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -7,8 +7,6 @@ const DaemonFactory = require('../src') const IPFS = require('ipfs') describe('ipfsd-ctl', () => { - const df = DaemonFactory.create() - // clean up IPFS env afterEach(() => Object.keys(process.env) .forEach((key) => { @@ -17,17 +15,19 @@ describe('ipfsd-ctl', () => { } })) - describe('Go daemon', () => { + describe.only('Go daemon', () => { + const df = DaemonFactory.create({ type: 'go' }) daemon(df, 'go')() api(df, 'go')() }) describe('Js daemon', () => { + const df = DaemonFactory.create({ type: 'js' }) daemon(df, 'js')() api(df, 'js')() }) describe('In-process daemon', () => { - daemon(DaemonFactory.create({ remote: false }), 'proc', IPFS)() + daemon(DaemonFactory.create({ remote: false, type: 'proc' }), 'proc', IPFS)() }) }) diff --git a/test/remote/client.js b/test/remote/client.js index 0c5990f5..8cea5a7d 100644 --- a/test/remote/client.js +++ b/test/remote/client.js @@ -14,14 +14,14 @@ const proxyquire = require('proxyquire') const superagent = require('superagent') const mock = require('superagent-mocker')(superagent) -const clientFactory = proxyquire('../../src/remote-node/client', { +const ClientFactory = proxyquire('../../src/remote-node/client', { superagent: () => { return superagent } }) describe('client', () => { - const client = clientFactory() + const client = new ClientFactory() let node = null describe('.spawn', () => { diff --git a/test/spawning.js b/test/spawning.js index 5e70eb9c..f6c5427a 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -26,7 +26,7 @@ module.exports = (df, type, exec) => { if (!isNode || type === 'proc') { this.skip() } - df.version({ type, exec }, (err, version) => { + df.version({ exec }, (err, version) => { expect(err).to.not.exist() expect(version).to.be.eql(VERSION_STRING) done() @@ -42,7 +42,7 @@ module.exports = (df, type, exec) => { }) it('create node', function (done) { - df.spawn({ type, exec, init: false, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ exec, init: false, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() expect(ipfsd.api).to.not.exist() @@ -83,7 +83,7 @@ module.exports = (df, type, exec) => { it('create node and init', function (done) { this.timeout(30 * 1000) - df.spawn({ type, exec, start: false, disposable: true }, (err, ipfsd) => { + df.spawn({ exec, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() expect(ipfsd.api).to.not.exist() @@ -115,7 +115,7 @@ module.exports = (df, type, exec) => { it('create init and start node', function (done) { this.timeout(20 * 1000) - df.spawn({ type, exec }, (err, ipfsd) => { + df.spawn({ exec }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() expect(ipfsd.api).to.exist() @@ -144,7 +144,6 @@ module.exports = (df, type, exec) => { this.timeout(60 * 1000) const options = { config: config, - type: type, exec: exec } @@ -198,7 +197,7 @@ module.exports = (df, type, exec) => { } async.series([ - (cb) => df.spawn({ type, exec, repoPath, disposable: false, config }, (err, node) => { + (cb) => df.spawn({ exec, repoPath, disposable: false, config }, (err, node) => { expect(err).to.not.exist() this.ipfsd = node cb() @@ -231,7 +230,7 @@ module.exports = (df, type, exec) => { const topic = `test-topic-${hat()}` before(function (done) { - df.spawn({ type, exec, args: ['--enable-pubsub-experiment'] }, (err, node) => { + df.spawn({ exec, args: ['--enable-pubsub-experiment'] }, (err, node) => { expect(err).to.not.exist() this.ipfsd = node done() @@ -261,7 +260,7 @@ module.exports = (df, type, exec) => { before(function (done) { this.timeout(20 * 1000) - df.spawn({ type, exec }, (err, res) => { + df.spawn({ exec }, (err, res) => { if (err) { return done(err) } diff --git a/test/start-stop.js b/test/start-stop.js index 45c2ccb5..2d91bf8b 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -27,7 +27,7 @@ module.exports = (type) => { describe(`create and init a node (ipfsd)`, function () { this.timeout(20 * 1000) before((done) => { - df.spawn({ type, init: true, start: false, disposable: true }, (err, daemon) => { + df.spawn({ init: true, start: false, disposable: true }, (err, daemon) => { expect(err).to.not.exist() expect(daemon).to.exist() @@ -115,7 +115,7 @@ module.exports = (type) => { this.timeout(20 * 1000) const exec = findIpfsExecutable(type) before((done) => { - df.spawn({ type, exec }, (err, daemon) => { + df.spawn({ exec }, (err, daemon) => { expect(err).to.not.exist() expect(daemon).to.exist() @@ -140,7 +140,6 @@ module.exports = (type) => { const exec = '/invalid/exec/ipfs' before((done) => { df.spawn({ - type, init: false, start: false, exec @@ -171,7 +170,6 @@ module.exports = (type) => { before((done) => { async.series([ (cb) => df.spawn({ - type, init: false, start: false, disposable: false, From 36cf27dc5f4d3544b9cb264043ab3063d68b4a53 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Sun, 31 Dec 2017 18:30:20 -0600 Subject: [PATCH 71/85] test: fixing routes test --- README.md | 39 +++++------ examples/id/id.js | 67 ++++++++++--------- examples/local-disposable/local-disposable.js | 67 ++++++++++--------- examples/local/local.js | 45 +++++++------ test/daemon.js | 2 +- test/remote/routes.js | 2 +- 6 files changed, 118 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index a9812978..7e977820 100644 --- a/README.md +++ b/README.md @@ -101,23 +101,7 @@ server.start((err) => { `ipfsd-ctl` can create two types of node controllers, `disposable` and `non-disposable`. A disposable node will be created on a temporary repo which will be optionally initialized and started (the default), as well cleaned up on process exit. A non-disposable node on the other hand, 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. -## IPFS executable types - -`ipfsd-ctl` allows spawning different types of executables, such as: - -> `go` - -Invoking `df.spawn({type: 'go', exec: ''})` will spawn a `go-ipfs` node. - -> `js` - -Invoking `df.spawn({type: 'js', exec: ''})` will spawn a `js-ipfs` node. - -> `proc` - -Invoking `df.spawn({type: 'proc', exec: require('ipfs')})` will spawn an `in-process-ipfs` node using the provided code reference that implements the core IPFS API. Note that, `exec` is required if `type: 'proc'` is used. - -### IPFS executables +## IPFS executables `ipfsd-ctl` no longer installs go-ipfs nor js-ipfs dependencies, instead it expects them to be provided by the parent project. In order to be able to use both go and js daemons, please make sure that your project includes these two npm packages as dependencies. @@ -135,6 +119,25 @@ Invoking `df.spawn({type: 'proc', exec: require('ipfs')})` will spawn an `in-pro - `options` - an optional object with the following properties - `remote` bool - indicates if the factory should spawn local or remote nodes. By default, local nodes are spawned in Node.js and remote nodes are spawned in Browser environments. - `port` number - the port number to use for the remote factory. It should match the port on which `DaemonFactory.server` was started. Defaults to 9999. + - type - the daemon type to create with this factory. See the section bellow for the supported types + +##### IPFS executable types + +`ipfsd-ctl` allows spawning different types of executables, such as: + +> `go` + +Invoking `df.create({type: 'go'})` will spawn a `go-ipfs` node. + +> `js` + +Invoking `df.create({type: 'js'})` will spawn a `js-ipfs` node. + +> `proc` + +Invoking `df.create({type: 'proc'})` will spawn an `in-process-ipfs` node using the provided code reference that implements the core IPFS API. Note that, `exec` option to `df.spawn()` is required if `type: 'proc'` is used. + + #### Create a DaemonFactory Endpoint - `const server = DaemonFactory.createServer([options]) ` @@ -149,8 +152,6 @@ Invoking `df.spawn({type: 'proc', exec: require('ipfs')})` will spawn an `in-pro `spawn([options], callback)` - `options` - is an optional object the following properties - - `type` string (default 'go') - indicates which type of node to spawn - - current valid values are `js`, `go` and `proc` - `init` bool (default true) - should the node be initialized - `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable diff --git a/examples/id/id.js b/examples/id/id.js index 55f89e76..c3a89ba7 100644 --- a/examples/id/id.js +++ b/examples/id/id.js @@ -4,49 +4,54 @@ const IPFS = require('ipfs') const DaemonFactory = require('ipfsd-ctl') -const df = DaemonFactory.create({ remote: false }) -df.spawn((err, ipfsd) => { - if (err) { - throw err - } - - ipfsd.api.id((err, id) => { +DaemonFactory + .create({ type: 'go' }) + .spawn((err, ipfsd) => { if (err) { throw err } - console.log('alice') - console.log(id) - ipfsd.stop() - }) -}) -df.spawn({ type: 'js' }, (err, ipfsd) => { - if (err) { - throw err - } + ipfsd.api.id((err, id) => { + if (err) { + throw err + } + console.log('alice') + console.log(id) + ipfsd.stop() + }) + }) - ipfsd.api.id((err, id) => { +DaemonFactory + .create({ type: 'js' }) + .spawn((err, ipfsd) => { if (err) { throw err } - console.log('bob') - console.log(id) - ipfsd.stop() - }) -}) -df.spawn({ type: 'proc', exec: IPFS }, (err, ipfsd) => { - if (err) { - throw err - } + ipfsd.api.id((err, id) => { + if (err) { + throw err + } + console.log('bob') + console.log(id) + ipfsd.stop() + }) + }) - ipfsd.api.id((err, id) => { +DaemonFactory + .create({ type: 'proc' }) + .spawn({ exec: IPFS }, (err, ipfsd) => { if (err) { throw err } - console.log('bob') - console.log(id) - ipfsd.stop(() => process.exit(0)) + + ipfsd.api.id((err, id) => { + if (err) { + throw err + } + console.log('bob') + console.log(id) + ipfsd.stop(() => process.exit(0)) + }) }) -}) diff --git a/examples/local-disposable/local-disposable.js b/examples/local-disposable/local-disposable.js index fd5d0172..6950e0c4 100644 --- a/examples/local-disposable/local-disposable.js +++ b/examples/local-disposable/local-disposable.js @@ -7,54 +7,59 @@ const IPFS = require('ipfs') const DaemonFactory = require('ipfsd-ctl') -const df = DaemonFactory.create({ remote: false }) // start a go daemon -df.spawn((err, ipfsd) => { - if (err) { - throw err - } - - ipfsd.api.id((err, id) => { +DaemonFactory + .create({ type: 'go' }) + .spawn((err, ipfsd) => { if (err) { throw err } - console.log('go-ipfs') - console.log(id) - ipfsd.stop() + ipfsd.api.id((err, id) => { + if (err) { + throw err + } + + console.log('go-ipfs') + console.log(id) + ipfsd.stop() + }) }) -}) // start a js daemon -df.spawn({ type: 'js' }, (err, ipfsd) => { - if (err) { - throw err - } - - ipfsd.api.id((err, id) => { +DaemonFactory + .create({ type: 'js' }) + .spawn((err, ipfsd) => { if (err) { throw err } - console.log('js-ipfs') - console.log(id) - ipfsd.stop() - }) -}) + ipfsd.api.id((err, id) => { + if (err) { + throw err + } -df.spawn({ type: 'proc', exec: IPFS }, (err, ipfsd) => { - if (err) { - throw err - } + console.log('js-ipfs') + console.log(id) + ipfsd.stop() + }) + }) - ipfsd.api.id((err, id) => { +DaemonFactory + .create({ type: 'proc' }) + .spawn({ exec: IPFS }, (err, ipfsd) => { if (err) { throw err } - console.log('js-ipfs') - console.log(id) - ipfsd.stop(() => process.exit(0)) + ipfsd.api.id((err, id) => { + if (err) { + throw err + } + + console.log('js-ipfs') + console.log(id) + ipfsd.stop(() => process.exit(0)) + }) }) -}) diff --git a/examples/local/local.js b/examples/local/local.js index 10a29bb7..a27833e1 100644 --- a/examples/local/local.js +++ b/examples/local/local.js @@ -4,38 +4,41 @@ const IPFS = require('ipfs') const DaemonFactory = require('ipfsd-ctl') -const df = DaemonFactory.create({ remote: false }) // opens an api connection to local running go-ipfs node -df.spawn({ disposable: true }, (err, ipfsd) => { - if (err) { - throw err - } - - ipfsd.api.id(function (err, id) { +DaemonFactory + .create({ type: 'go' }) + .spawn({ disposable: true }, (err, ipfsd) => { if (err) { throw err } - console.log('js-ipfs') - console.log(id) - ipfsd.stop() + ipfsd.api.id(function (err, id) { + if (err) { + throw err + } + + console.log('js-ipfs') + console.log(id) + ipfsd.stop() + }) }) -}) // creates an in-process running js-ipfs node -df.spawn({ type: 'proc', disposable: true, exec: IPFS }, (err, ipfsd) => { - if (err) { - throw err - } - - ipfsd.api.id(function (err, id) { +DaemonFactory + .create({ type: 'proc' }) + .spawn({ disposable: true, exec: IPFS }, (err, ipfsd) => { if (err) { throw err } - console.log('in-proc-ipfs') - console.log(id) - ipfsd.stop(() => process.exit(0)) + ipfsd.api.id(function (err, id) { + if (err) { + throw err + } + + console.log('in-proc-ipfs') + console.log(id) + ipfsd.stop(() => process.exit(0)) + }) }) -}) diff --git a/test/daemon.js b/test/daemon.js index bcc36c41..0ddac6ad 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -15,7 +15,7 @@ describe('ipfsd-ctl', () => { } })) - describe.only('Go daemon', () => { + describe('Go daemon', () => { const df = DaemonFactory.create({ type: 'go' }) daemon(df, 'go')() api(df, 'go')() diff --git a/test/remote/routes.js b/test/remote/routes.js index e3629d1b..ab2a88f8 100644 --- a/test/remote/routes.js +++ b/test/remote/routes.js @@ -11,7 +11,7 @@ const multiaddr = require('multiaddr') const Hapi = require('hapi') const routes = proxyquire('../../src/remote-node/routes', { - '../daemon-ctrl': { + '../daemon-ctrl': class { spawn (ops, cb) { const node = {} node.apiAddr = multiaddr('/ip4/127.0.0.1/tcp/5001') From a7728d89dcbe5ba9ba74fb727d18024b11df5d45 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 2 Jan 2018 09:19:47 -0600 Subject: [PATCH 72/85] docs: updating readme --- README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 7e977820..b53605cd 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Invoking `df.create({type: 'proc'})` will spawn an `in-process-ipfs` node using #### Create a DaemonFactory Endpoint - `const server = DaemonFactory.createServer([options]) ` -> `DaemonFactory.createServer` create an instance of the bundled HTTP server used by the remote controller. +> `DaemonFactory.createServer` create an instance of the bundled REST API used by the remote controller. - exposes `start` and `stop` methods to start and stop the http server. @@ -163,14 +163,7 @@ Invoking `df.create({type: 'proc'})` will spawn an `in-process-ipfs` node using - `callback` - is a function with the signature `cb(err, ipfsd)` where: - `err` - is the error set if spawning the node is unsuccessful - `ipfsd` - is the daemon controller instance: - - `api` - a property of `ipfsd`, an instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api) attached to the newly created ipfs node - -### IPFS Client (api) - -> An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) - -This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsd.start()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. - + - `api` - a property of `ipfsd`, an instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api) attached to the newly created ipfs node ### IPFS Daemon Controller (ipfsd) @@ -206,7 +199,7 @@ This instance is returned for each successfully started IPFS daemon, when either - `initOpts` (optional) - options object with the following entries - `keysize` (default 2048) - The bit size of the identiy key. - - `directory` (default IPFS_PATH) - The location of the repo. + - `directory` (default IPFS_PATH if defined, or ~/.ipfs for go-ipfs and ~/.jsipfs for js-ipfs) - The location of the repo. - `function (Error, Node)` callback - receives an instance of this Node on success or an instance of `Error` on failure @@ -253,16 +246,17 @@ First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process has If no `key` is passed, the whole config is returned as an object. - `key` (optional) - A specific config to retrieve. -- `function(Error, (Object|string)` callback - function that reseives an object or string on success or an `Error` instance on failure +- `function(Error, (Object|string)` callback - function that receives an object or string on success or an `Error` instance on failure #### `setConfig (key, value, callback)` > Set a config value. -- `key` - the key to set -- `value` - the value to set the key to -- `function(Error)` callback +- `key` - the key of the config entry to change/set +- `value` - the config value to change/set +- `function(Error)` callback - function that receives an `Error` instance on failure + #### `version (callback)` @@ -270,6 +264,12 @@ If no `key` is passed, the whole config is returned as an object. - `function(Error, string)` callback +### IPFS Client (`ipfsd.api`) + +> An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) + +This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsd.start()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. + ### Packaging `ipfsd-ctl` can be packaged in Electron applications, but the ipfs binary From 5b1caae0f27eb61fc8e57d7423ac13c15136a12f Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 2 Jan 2018 09:23:08 -0600 Subject: [PATCH 73/85] fix: use const --- src/daemon-ctrl.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/daemon-ctrl.js b/src/daemon-ctrl.js index e7a19eca..4460431c 100644 --- a/src/daemon-ctrl.js +++ b/src/daemon-ctrl.js @@ -80,8 +80,7 @@ class DaemonController { opts = defaultOptions } - let options = {} - options = defaults({}, opts, defaultOptions) + const options = defaults({}, opts, defaultOptions) options.init = (typeof options.init !== 'undefined' ? options.init : true) if (!options.disposable) { const nonDisposableConfig = clone(defaultConfig) From 177b1e50482315d5b7722f8f556e8d47c09494d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Tue, 2 Jan 2018 09:44:06 -0600 Subject: [PATCH 74/85] timeouts --- test/api.js | 4 ++-- test/spawning.js | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/api.js b/test/api.js index a90bab3a..87f57a66 100644 --- a/test/api.js +++ b/test/api.js @@ -31,7 +31,7 @@ module.exports = (df, type) => { let api before(function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) df.spawn({ start: false, config }, (err, daemon) => { expect(err).to.not.exist() ipfsd = daemon @@ -92,7 +92,7 @@ module.exports = (df, type) => { describe('validate api', () => { it('starts the daemon and returns valid API and gateway addresses', function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) df.spawn({ config }, (err, res) => { expect(err).to.not.exist() const ipfsd = res diff --git a/test/spawning.js b/test/spawning.js index f6c5427a..f97ae7d3 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -37,7 +37,7 @@ module.exports = (df, type, exec) => { this.ipfsd = null after(function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) this.ipfsd.stop(done) }) @@ -52,7 +52,7 @@ module.exports = (df, type, exec) => { }) it('init node', function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) this.ipfsd.init((err) => { expect(err).to.not.exist() expect(this.ipfsd.initialized).to.be.ok() @@ -61,7 +61,7 @@ module.exports = (df, type, exec) => { }) it('start node', function (done) { - this.timeout(30 * 1000) + this.timeout(50 * 1000) this.ipfsd.start((err, api) => { expect(err).to.not.exist() expect(api).to.exist() @@ -77,12 +77,12 @@ module.exports = (df, type, exec) => { this.ipfsd = null after(function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) this.ipfsd.stop(done) }) it('create node and init', function (done) { - this.timeout(30 * 1000) + this.timeout(50 * 1000) df.spawn({ exec, start: false, disposable: true }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() @@ -93,7 +93,7 @@ module.exports = (df, type, exec) => { }) it('start node', function (done) { - this.timeout(30 * 1000) + this.timeout(50 * 1000) this.ipfsd.start((err, api) => { expect(err).to.not.exist() expect(api).to.exist() @@ -109,12 +109,12 @@ module.exports = (df, type, exec) => { this.ipfsd = null after(function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) this.ipfsd.stop(done) }) it('create init and start node', function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) df.spawn({ exec }, (err, ipfsd) => { expect(err).to.not.exist() expect(ipfsd).to.exist() @@ -182,7 +182,7 @@ module.exports = (df, type, exec) => { this.ipfsd = null it('allows passing custom repo path to spawn', function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) const repoPath = tempDir(type) @@ -259,7 +259,7 @@ module.exports = (df, type, exec) => { let ipfsd before(function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) df.spawn({ exec }, (err, res) => { if (err) { return done(err) From fcd1728cdfcc7edeac124e678583ada45c6aa3e2 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Thu, 4 Jan 2018 15:18:01 -0600 Subject: [PATCH 75/85] fix: dont run exec under coverage --- package.json | 2 +- test/exec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1236afb9..42fe31ce 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.js", "scripts": { "lint": "aegir lint", - "coverage": "aegir coverage --timeout 50000", + "coverage": "COVERAGE=true aegir coverage --timeout 50000", "test": "aegir test -t node -t browser --no-cors", "test:node": "aegir test -t node", "test:browser": "aegir test -t browser --no-cors", diff --git a/test/exec.js b/test/exec.js index c49a6028..c07274fb 100644 --- a/test/exec.js +++ b/test/exec.js @@ -65,7 +65,8 @@ function makeCheck (n, done) { // I'm leaving it enabled for now. This does need a different approach for windows though. describe('exec', () => { // TODO: skip on windows for now - if (isWindows) { + // TODO: running under coverage messes up the process hierarchies + if (isWindows || process.env['COVERAGE']) { return } From 19201d990d183fead6b27dd4b60664da995aec16 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 5 Jan 2018 14:47:38 -0600 Subject: [PATCH 76/85] fix: exlectron example packaging --- examples/electron-asar/package.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/electron-asar/package.json b/examples/electron-asar/package.json index 5c48c77e..1d014f1b 100644 --- a/examples/electron-asar/package.json +++ b/examples/electron-asar/package.json @@ -3,7 +3,9 @@ "private": true, "main": "./app.js", "dependencies": { - "ipfsd-ctl": "file:../.." + "ipfsd-ctl": "file:../..", + "go-ipfs-dep": "0.4.13", + "ipfs-repo": "^0.18.5" }, "devDependencies": { "electron": "^1.7.6", From 1a78f5f13fcc3caf2759615e989b1e5344363aa0 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 5 Jan 2018 12:59:22 -0800 Subject: [PATCH 77/85] wip: windows support --- package.json | 1 + src/daemon-node.js | 17 ++++++----------- src/utils/index.js | 18 +++++++++++++++--- test/spawning.js | 3 ++- test/start-stop.js | 34 +++++++++++++++------------------- test/utils.js | 5 +++-- 6 files changed, 42 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 42fe31ce..c333d184 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "stream-http": "^2.7.2", "subcomandante": "^1.0.5", "superagent": "^3.8.2", + "taskkill": "^2.0.0", "truthy": "0.0.1" }, "devDependencies": { diff --git a/src/daemon-node.js b/src/daemon-node.js index 4746b979..7909cd1f 100644 --- a/src/daemon-node.js +++ b/src/daemon-node.js @@ -18,8 +18,7 @@ const tempDir = utils.tempDir const findIpfsExecutable = utils.findIpfsExecutable const setConfigValue = utils.setConfigValue const configureNode = utils.configureNode - -const exec = require('./exec') +const run = utils.run const GRACE_PERIOD = 10500 // amount of ms to wait before sigkill @@ -103,10 +102,6 @@ class Node { return this.path ? Object.assign({}, process.env, { IPFS_PATH: this.path }) : process.env } - _run (args, opts, callback) { - return exec(this.exec, args, opts, callback) - } - /** * Initialize a repo. * @@ -128,7 +123,7 @@ class Node { this.path = initOpts.directory } - this._run(['init', '-b', keySize], { env: this.env }, (err, result) => { + run(this, ['init', '-b', keySize], { env: this.env }, (err, result) => { if (err) { return callback(err) } @@ -191,8 +186,7 @@ class Node { } let output = '' - - this.subprocess = this._run(args, { env: this.env }, { + this.subprocess = run(this, args, { env: this.env }, { error: (err) => { // Only look at the last error const input = String(err) @@ -312,7 +306,8 @@ class Node { } async.waterfall([ - (cb) => this._run( + (cb) => run( + this, ['config', key], { env: this.env }, cb @@ -345,7 +340,7 @@ class Node { * @returns {undefined} */ version (callback) { - this._run(['version'], { env: this.env }, callback) + run(this, ['version'], { env: this.env }, callback) } } diff --git a/src/utils/index.js b/src/utils/index.js index 4b329876..63a462c2 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -10,7 +10,6 @@ const safeParse = require('safe-json-parse/callback') const createRepo = require('./create-repo-nodejs') const join = path.join - const isWindows = os.platform() === 'win32' exports.createRepo = createRepo @@ -88,9 +87,22 @@ exports.findIpfsExecutable = (type, rootPath) => { throw new Error('Cannot find the IPFS executable') } +function run (node, args, opts, callback) { + let executable = node.exec + if (isWindows && node.opts.type !== 'go') { + args = args || [] + args.unshift(node.exec) + executable = process.execPath + } + + return exec(executable, args, opts, callback) +} + +exports.run = run + function setConfigValue (node, key, value, callback) { - exec( - node.exec, + run( + node, ['config', key, value, '--json'], { env: node.env }, callback diff --git a/test/spawning.js b/test/spawning.js index f97ae7d3..483b6367 100644 --- a/test/spawning.js +++ b/test/spawning.js @@ -287,7 +287,8 @@ module.exports = (df, type, exec) => { }) }) - it('Should set a config value', (done) => { + it('Should set a config value', function (done) { + this.timeout(30 * 1000) async.series([ (cb) => ipfsd.setConfig('Bootstrap', 'null', cb), (cb) => ipfsd.getConfig('Bootstrap', cb) diff --git a/test/start-stop.js b/test/start-stop.js index 2d91bf8b..bbf75ed9 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -9,10 +9,11 @@ chai.use(dirtyChai) const async = require('async') const fs = require('fs') -const once = require('once') const path = require('path') -const exec = require('../src/exec') +const os = require('os') +const isrunning = require('is-running') +const isWindows = os.platform() === 'win32' const findIpfsExecutable = require('../src/utils').findIpfsExecutable const tempDir = require('../src/utils').tempDir @@ -22,6 +23,10 @@ const df = DaemonFactory.create() module.exports = (type) => { return () => { describe('starting and stopping', () => { + if (isWindows) { + return + } + let ipfsd describe(`create and init a node (ipfsd)`, function () { @@ -53,7 +58,7 @@ module.exports = (type) => { let api before(function (done) { - this.timeout(20 * 1000) + this.timeout(50 * 1000) ipfsd.start((err, ipfs) => { expect(err).to.not.exist() @@ -62,8 +67,8 @@ module.exports = (type) => { api = ipfs // actually running? - done = once(done) - exec('kill', ['-0', pid], { cleanup: true }, () => done()) + expect(isrunning(pid)).to.be.ok() + done() }) }) }) @@ -76,23 +81,14 @@ module.exports = (type) => { describe('stopping', () => { let stopped = false - before((done) => { + before(function (done) { + this.timeout(20 * 1000) ipfsd.stop((err) => { expect(err).to.not.exist() + expect(isrunning(pid)).to.not.be.ok() stopped = true + done() }) - - // make sure it's not still running - const poll = setInterval(() => { - exec('kill', ['-0', pid], { cleanup: true }, { - error () { - clearInterval(poll) - done() - // so it does not get called again - done = () => {} - } - }) - }, 100) }) it('should be stopped', function (done) { @@ -137,7 +133,7 @@ module.exports = (type) => { describe(`should fail on invalid exec path`, function () { this.timeout(20 * 1000) - const exec = '/invalid/exec/ipfs' + const exec = path.join('/invalid/exec/ipfs') before((done) => { df.spawn({ init: false, diff --git a/test/utils.js b/test/utils.js index fe8b9db2..f19c89b6 100644 --- a/test/utils.js +++ b/test/utils.js @@ -8,6 +8,7 @@ const expect = chai.expect chai.use(dirtyChai) const fs = require('fs') +const path = require('path') const utils = require('../src/utils') const flatten = utils.flatten const tempDir = utils.tempDir @@ -49,14 +50,14 @@ describe('utils', () => { it('should find go executable', () => { const execPath = findIpfsExecutable('go', __dirname) expect(execPath).to.exist() - expect(execPath).to.include('go-ipfs-dep/go-ipfs/ipfs') + expect(execPath).to.include(path.join('go-ipfs-dep', 'go-ipfs', 'ipfs')) expect(fs.existsSync(execPath)).to.be.ok() }) it('should find go executable', () => { const execPath = findIpfsExecutable('js', __dirname) expect(execPath).to.exist() - expect(execPath).to.include('ipfs/src/cli/bin.js') + expect(execPath).to.include(path.join('ipfs', 'src', 'cli', 'bin.js')) expect(fs.existsSync(execPath)).to.be.ok() }) }) From 94c966fe04db4ede46bf20acdfdabceb44444830 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 5 Jan 2018 15:36:56 -0600 Subject: [PATCH 78/85] test: fix start/stop test on mac/linux --- package.json | 1 - test/start-stop.js | 13 ++++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c333d184..42fe31ce 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ "stream-http": "^2.7.2", "subcomandante": "^1.0.5", "superagent": "^3.8.2", - "taskkill": "^2.0.0", "truthy": "0.0.1" }, "devDependencies": { diff --git a/test/start-stop.js b/test/start-stop.js index bbf75ed9..932c2ff0 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -85,9 +85,16 @@ module.exports = (type) => { this.timeout(20 * 1000) ipfsd.stop((err) => { expect(err).to.not.exist() - expect(isrunning(pid)).to.not.be.ok() - stopped = true - done() + let tries = 5 + const interval = setInterval(() => { + const running = isrunning(pid) + if (!running || tries-- <= 0) { + clearInterval(interval) + expect(running).to.not.be.ok() + stopped = true + done() + } + }, 200) }) }) From ef28e1768294bf428eb704a7d9f238e975b90f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 5 Jan 2018 14:22:19 -0800 Subject: [PATCH 79/85] fix: windows tests --- test/start-stop.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/start-stop.js b/test/start-stop.js index 932c2ff0..d9f9e321 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -18,10 +18,10 @@ const findIpfsExecutable = require('../src/utils').findIpfsExecutable const tempDir = require('../src/utils').tempDir const DaemonFactory = require('../src') -const df = DaemonFactory.create() module.exports = (type) => { return () => { + const df = DaemonFactory.create({type}) describe('starting and stopping', () => { if (isWindows) { return @@ -140,7 +140,7 @@ module.exports = (type) => { describe(`should fail on invalid exec path`, function () { this.timeout(20 * 1000) - const exec = path.join('/invalid/exec/ipfs') + const exec = path.join('invalid', 'exec', 'ipfs') before((done) => { df.spawn({ init: false, @@ -177,7 +177,7 @@ module.exports = (type) => { start: false, disposable: false, repoPath: tempDir(type) - }, (err, daemon) => { + }, (err, daemon) => { expect(err).to.not.exist() expect(daemon).to.exist() From b4522b70833699e602e49177c6cb5421393c8617 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 5 Jan 2018 16:55:44 -0600 Subject: [PATCH 80/85] fix: timeouts --- test/start-stop.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/start-stop.js b/test/start-stop.js index d9f9e321..89b5b40e 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -30,7 +30,7 @@ module.exports = (type) => { let ipfsd describe(`create and init a node (ipfsd)`, function () { - this.timeout(20 * 1000) + this.timeout(50 * 1000) before((done) => { df.spawn({ init: true, start: false, disposable: true }, (err, daemon) => { expect(err).to.not.exist() @@ -115,7 +115,7 @@ module.exports = (type) => { let ipfsd describe(`create and init a node (ipfsd) on custom exec path`, function () { - this.timeout(20 * 1000) + this.timeout(50 * 1000) const exec = findIpfsExecutable(type) before((done) => { df.spawn({ exec }, (err, daemon) => { @@ -169,7 +169,7 @@ module.exports = (type) => { let ipfsd describe(`create and init a node (ipfsd)`, function () { - this.timeout(20 * 1000) + this.timeout(50 * 1000) before((done) => { async.series([ (cb) => df.spawn({ From 85557c97b5275828146da62e24a3be9cbcac9040 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 5 Jan 2018 17:03:37 -0600 Subject: [PATCH 81/85] lint --- test/start-stop.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/start-stop.js b/test/start-stop.js index 89b5b40e..f716d44f 100644 --- a/test/start-stop.js +++ b/test/start-stop.js @@ -21,7 +21,7 @@ const DaemonFactory = require('../src') module.exports = (type) => { return () => { - const df = DaemonFactory.create({type}) + const df = DaemonFactory.create({ type }) describe('starting and stopping', () => { if (isWindows) { return @@ -177,7 +177,7 @@ module.exports = (type) => { start: false, disposable: false, repoPath: tempDir(type) - }, (err, daemon) => { + }, (err, daemon) => { expect(err).to.not.exist() expect(daemon).to.exist() From c60decef8396d2b7797d328fbac4590d080b6b5a Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Mon, 8 Jan 2018 11:57:47 -0600 Subject: [PATCH 82/85] feat: allow to set the executable for DaemonController --- src/daemon-ctrl.js | 20 +++++++++++++++++--- src/index.js | 2 +- test/daemon.js | 8 +------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/daemon-ctrl.js b/src/daemon-ctrl.js index 4460431c..6afa653e 100644 --- a/src/daemon-ctrl.js +++ b/src/daemon-ctrl.js @@ -39,8 +39,21 @@ const defaultConfig = { * @namespace DaemonController */ class DaemonController { - constructor (type) { - this.type = type || 'go' + /** + * Create a DaemonController instance + * + * @param {Object} opts + * - `type` string - one of 'go', 'js' or 'proc', + * the type of the daemon to spawn + * - `exec` string (optional) - the path of the daemon + * executable or IPFS class in the case of `proc` + * + * @return {*} + */ + constructor (opts) { + opts = opts || {} + this.type = opts.type || 'go' + this.exec = opts.exec } /** @@ -84,7 +97,7 @@ class DaemonController { options.init = (typeof options.init !== 'undefined' ? options.init : true) if (!options.disposable) { const nonDisposableConfig = clone(defaultConfig) - delete nonDisposableConfig['Addresses'] + delete nonDisposableConfig.Addresses options.init = false options.start = false @@ -99,6 +112,7 @@ class DaemonController { let node options.type = this.type + options.exec = options.exec || this.exec if (this.type === 'proc') { if (typeof options.exec !== 'function') { return callback(new Error(`'type' proc requires 'exec' to be a coderef`)) diff --git a/src/index.js b/src/index.js index 154acaa4..10d2898f 100644 --- a/src/index.js +++ b/src/index.js @@ -17,7 +17,7 @@ class DaemonFactory { return new remote.RemoteController(options) } - return new LocalController(options.type) + return new LocalController(options) } static createServer (port) { diff --git a/test/daemon.js b/test/daemon.js index 0ddac6ad..bfc79301 100644 --- a/test/daemon.js +++ b/test/daemon.js @@ -21,13 +21,7 @@ describe('ipfsd-ctl', () => { api(df, 'go')() }) - describe('Js daemon', () => { - const df = DaemonFactory.create({ type: 'js' }) - daemon(df, 'js')() - api(df, 'js')() - }) - describe('In-process daemon', () => { - daemon(DaemonFactory.create({ remote: false, type: 'proc' }), 'proc', IPFS)() + daemon(DaemonFactory.create({ remote: false, type: 'proc', exec: IPFS }), 'proc')() }) }) From dbe6b371bc42febe5f83db8db88c1de51cf16dd4 Mon Sep 17 00:00:00 2001 From: David Dias Date: Tue, 9 Jan 2018 17:00:51 +0000 Subject: [PATCH 83/85] docs: review the readme (#181) * docs: review the readme * Update README.md * doc: updated `init()` docs --- README.md | 181 ++++++++++++++++++++++++------------------------------ 1 file changed, 79 insertions(+), 102 deletions(-) diff --git a/README.md b/README.md index b53605cd..cefa9c2f 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Appveyor CI](https://ci.appveyor.com/api/projects/status/4p9r12ch0jtthnha?svg=true)](https://ci.appveyor.com/project/wubalubadubdub/js-ipfsd-ctl-a9ywu) [![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) -> Control an ipfs node daemon using either Node.js or the browser +> Control an IPFS daemon using JavaScript in Node.js or in the Browser. ``` +-----+ @@ -51,7 +51,7 @@ npm install --save ipfsd-ctl IPFS daemons are already easy to start and stop, but this module is here to do it from JavaScript itself. -### Local daemon (_Spawn from from Node.js_) +### Spawn an IPFS daemon from Node.js ```js // Start a disposable node, and get access to the api @@ -61,14 +61,18 @@ const DaemonFactory = require('ipfsd-ctl') const df = DaemonFactory.create() df.spawn(function (err, ipfsd) { + if (err) { throw err } + ipfsd.api.id(function (err, id) { + if (err) { throw err } + console.log(id) ipfsd.stop() }) }) ``` -### Remote node _(Spawn from a Browser or from a remote machine)) +### Spawn an IPFS daemon from the Browser using the provided remote endpoint ```js // Start a remote disposable node, and get access to the api @@ -78,18 +82,17 @@ const DaemonFactory = require('ipfsd-ctl') const port = 9999 const server = DaemonFactory.createServer(port) -const df = DaemonFactory.create({ remote: true, port }) +const df = DaemonFactory.create({ remote: true, port: port }) + server.start((err) => { - if (err) { - throw err - } + if (err) { throw err } df.spawn((err, ipfsd) => { - if (err) { - throw err - } + if (err) { throw err } ipfsd.api.id(function (err, id) { + if (err) { throw err } + console.log(id) ipfsd.stop(server.stop) }) @@ -110,174 +113,148 @@ server.start((err) => { ## API -### Daemon Factory +### Daemon Factory Class -#### Create a `DaemonFactory` - `const df = DaemonFactory.create([options])` +#### `DaemonFactory` - `const df = DaemonFactory.create([options])` -> `DaemonFactory.create([options])` returns an object that will expose the `df.spawn` method +`DaemonFactory.create([options])` returns an object that will expose the `df.spawn` method - `options` - an optional object with the following properties - `remote` bool - indicates if the factory should spawn local or remote nodes. By default, local nodes are spawned in Node.js and remote nodes are spawned in Browser environments. - `port` number - the port number to use for the remote factory. It should match the port on which `DaemonFactory.server` was started. Defaults to 9999. - - type - the daemon type to create with this factory. See the section bellow for the supported types + - `type` - the daemon type to create with this factory. See the section bellow for the supported types + - `exec` - path to the desired IPFS executable to spawn, otherwise `ipfsd-ctl` will try to locate the correct one based on the `type`. In the case of `proc` type, exec is required and expects an IPFS coderef. -##### IPFS executable types - -`ipfsd-ctl` allows spawning different types of executables, such as: - -> `go` - -Invoking `df.create({type: 'go'})` will spawn a `go-ipfs` node. - -> `js` - -Invoking `df.create({type: 'js'})` will spawn a `js-ipfs` node. - -> `proc` - -Invoking `df.create({type: 'proc'})` will spawn an `in-process-ipfs` node using the provided code reference that implements the core IPFS API. Note that, `exec` option to `df.spawn()` is required if `type: 'proc'` is used. +`ipfsd-ctl` allows spawning different IPFS implementations, such as: +- **`go`** - calling `DaemonFactory.create({type: 'go'})` will spawn a `go-ipfs` daemon. +- **`js`** - calling `DaemonFactory.create({type: 'js'})` will spawn a `js-ipfs` daemon. +- **`proc`** - calling `DaemonFactory.create({type: 'proc', exec: require('ipfs') })` will spawn an `in process js-ipfs node` using the provided code reference that implements the core IPFS API. Note that, `exec` option to `df.spawn()` is required if `type: 'proc'` is used. +#### DaemonFactory endpoint for remote spawning - `const server = DaemonFactory.createServer([options]) ` -#### Create a DaemonFactory Endpoint - `const server = DaemonFactory.createServer([options]) ` - -> `DaemonFactory.createServer` create an instance of the bundled REST API used by the remote controller. +`DaemonFactory.createServer` create an instance of the bundled REST API used by the remote controller. -- exposes `start` and `stop` methods to start and stop the http server. +- exposes `start` and `stop` methods to start and stop the http server endpoint. #### Spawn a new daemon with `df.spawn` -> Spawn either a js-ipfs or go-ipfs node +Spawn either a js-ipfs or go-ipfs daemon -`spawn([options], callback)` +`df.spawn([options], callback)` -- `options` - is an optional object the following properties +`options` is an optional object the following properties: - `init` bool (default true) - should the node be initialized - `start` bool (default true) - should the node be started - `repoPath` string - the repository path to use for this node, ignored if node is disposable - `disposable` bool (default false) - a new repo is created and initialized for each invocation, as well as cleaned up automatically once the process exits - `args` - array of cmd line arguments to be passed to ipfs daemon - `config` - ipfs configuration options - - `exec` - path to the desired IPFS executable to spawn, otherwise `ipfsd-ctl` will try to locate the correct one based on the `type`. In the case of `proc` type, exec is required and expects an IPFS coderef - - - `callback` - is a function with the signature `cb(err, ipfsd)` where: - - `err` - is the error set if spawning the node is unsuccessful - - `ipfsd` - is the daemon controller instance: - - `api` - a property of `ipfsd`, an instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api) attached to the newly created ipfs node - -### IPFS Daemon Controller (ipfsd) - -> The IPFS daemon controller that allows interacting with the spawned IPFS process -#### `apiAddr` (getter) +`callback` - is a function with the signature `function (err, ipfsd)` where: + - `err` - is the error set if spawning the node is unsuccessful + - `ipfsd` - is the daemon controller instance: + - `api` - a property of `ipfsd`, an instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api) attached to the newly created ipfs node -> Get the address (multiaddr) of connected IPFS API. +### IPFS Daemon Controller (`ipfsd`) -- returns multiaddr +The IPFS daemon controller (`ipfsd`) allows you to interact with the spawned IPFS daemon. -#### `gatewayAddr` (getter) +#### `ipfsd.apiAddr` (getter) -> Get the address (multiaddr) of connected IPFS HTTP Gateway. +Get the address (multiaddr) of connected IPFS API. Returns a multiaddr, -- returns multiaddr +#### `ipfsd.gatewayAddr` (getter) -#### `repoPath` (getter) +Get the address (multiaddr) of connected IPFS HTTP Gateway. Returns a multiaddr. -> Get the current repo path. +#### `ipfsd.repoPath` (getter) -- returns string +Get the current repo path. Returns string -#### `started` (getter) +#### `ipfsd.started` (getter) -> Is the node started. - -- returns boolean +Is the node started. Returns a boolean. -#### `init ([initOpts], callback)` +#### `init([initOpts], callback)` -> Initialize a repo. +Initialize a repo. -- `initOpts` (optional) - options object with the following entries +`initOpts` (optional) is an object with the following properties: - `keysize` (default 2048) - The bit size of the identiy key. - `directory` (default IPFS_PATH if defined, or ~/.ipfs for go-ipfs and ~/.jsipfs for js-ipfs) - The location of the repo. - - `function (Error, Node)` callback - receives an instance of this Node on success or an instance of `Error` on failure - - -#### `cleanup (callback)` - -> Delete the repo that was being used. If the node was marked as `disposable` this will be called automatically when the process is exited. + +`callback` is a function with the signature `function (Error, ipfsd)` where `err` is an Error in case something goes wrong and `ipfsd` is the daemon controller instance. -- `function(Error)` callback +#### `ipfsd.cleanup(callback)` -#### `start (flags, callback)` +Delete the repo that was being used. If the node was marked as `disposable` this will be called automatically when the process is exited. -> Start the daemon. +`callback` is a function with the signature `function(err)`. -- `flags` - Flags array to be passed to the `ipfs daemon` command. -- `function(Error, IpfsApi)}` callback - function that receives an instance of `ipfs-api` on success or an instance of `Error` on failure +#### `ipfsd.start(flags, callback)` +Start the daemon. -#### `stop (callback)` +`flags` - Flags array to be passed to the `ipfs daemon` command. -> Stop the daemon. +`callback` is a function with the signature `function(err, ipfsApi)}` that receives an instance of `ipfs-api` on success or an instance of `Error` on failure -- `function(Error)` callback - function that receives an instance of `Error` on failure -#### `killProcess (callback)` +#### `ipfsd.stop(callback)` -> Kill the `ipfs daemon` process. +Stop the daemon. -First `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process hasn't exited yet. +`callback` is a function with the signature `function(err)` callback - function that receives an instance of `Error` on failure -- `function()` callback - Called once the process is killed +#### `ipfsd.killProcess (callback)` +Kill the `ipfs daemon` process. -#### `pid ()` +First a `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process hasn't exited yet. -> Get the pid of the `ipfs daemon` process. +`callback` is a function with the signature `function()` called once the process is killed -- returns the pid number +#### `ipfsd.pid ()` +Get the pid of the `ipfs daemon` process. Returns the pid number -#### `getConfig (key, callback)` +#### `ipfsd.getConfig(key, callback)` -> Call `ipfs config` +Returns the output of an `ipfs config` command. If no `key` is passed, the whole config is returned as an object. -If no `key` is passed, the whole config is returned as an object. +`key` (optional) - A specific config to retrieve. -- `key` (optional) - A specific config to retrieve. -- `function(Error, (Object|string)` callback - function that receives an object or string on success or an `Error` instance on failure +`callback` is a function with the signature `function(err, (Object|string)` that receives an object or string on success or an `Error` instance on failure +#### `ipfsd.setConfig (key, value, callback)` -#### `setConfig (key, value, callback)` +Set a config value. -> Set a config value. +`key` - the key of the config entry to change/set -- `key` - the key of the config entry to change/set -- `value` - the config value to change/set -- `function(Error)` callback - function that receives an `Error` instance on failure +`value` - the config value to change/set +`callback` is a function with the signature `function(err)` callback - function that receives an `Error` instance on failure -#### `version (callback)` +#### `ipfsd.version(callback)` -> Get the version of ipfs +Get the version of ipfs -- `function(Error, string)` callback +`callback` is a function with the signature `function(err, version)` ### IPFS Client (`ipfsd.api`) -> An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) +An instance of [ipfs-api](https://github.com/ipfs/js-ipfs-api#api) that is used to interact with the daemon. This instance is returned for each successfully started IPFS daemon, when either `df.spawn({start: true})` (the default) is called, or `ipfsd.start()` is invoked in the case of nodes that were spawned with `df.spawn({start: false})`. ### Packaging -`ipfsd-ctl` can be packaged in Electron applications, but the ipfs binary -has to be excluded from asar (Electron Archives), +`ipfsd-ctl` can be packaged in Electron applications, but the ipfs binary has to be excluded from asar (Electron Archives), [read more about unpack files from asar](https://electron.atom.io/docs/tutorial/application-packaging/#adding-unpacked-files-in-asar-archive). -`ipfsd-ctl` will try to detect if used from within an `app.asar` archive -and tries to resolve ipfs from `app.asar.unpacked`. The ipfs binary is part of -the `go-ipfs-dep` module. + +`ipfsd-ctl` will try to detect if used from within an `app.asar` archive and tries to resolve ipfs from `app.asar.unpacked`. The ipfs binary is part of the `go-ipfs-dep` module. ```bash electron-packager ./ --asar.unpackDir=node_modules/go-ipfs-dep From aa8d72b646ef687888c42a04224d6e4f45ee79f7 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Mon, 8 Jan 2018 12:26:45 -0600 Subject: [PATCH 84/85] fix: ipfs-repo is not a `devDependency` --- package.json | 2 +- src/daemon-ctrl.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 42fe31ce..f533bb00 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "hapi": "^16.6.2", "hat": "0.0.3", "ipfs-api": "^17.2.5", + "ipfs-repo": "^0.18.5", "joi": "^13.0.2", "lodash.clone": "^4.5.0", "lodash.defaultsdeep": "^4.6.0", @@ -91,7 +92,6 @@ "dirty-chai": "^2.0.1", "go-ipfs-dep": "0.4.13", "ipfs": "^0.27.5", - "ipfs-repo": "^0.18.5", "is-running": "1.0.5", "mkdirp": "^0.5.1", "pre-commit": "^1.2.2", diff --git a/src/daemon-ctrl.js b/src/daemon-ctrl.js index 6afa653e..0ee0b8ad 100644 --- a/src/daemon-ctrl.js +++ b/src/daemon-ctrl.js @@ -81,7 +81,8 @@ class DaemonController { * - `disposable` bool - a new repo is created and initialized for each invocation * - `config` - ipfs configuration options * - `args` - array of cmd line arguments to be passed to ipfs daemon - * - `exec` - path to the desired IPFS executable to spawn + * - `exec` string (optional) - path to the desired IPFS executable to spawn, + * this will override the `exec` set when creating the daemon controller factory instance * * @param {Object} [opts={}] - various config options and ipfs config parameters * @param {Function} callback(err, [`ipfs-api instance`, `Node (ctrl) instance`]) - a callback that receives an array with an `ipfs-instance` attached to the node and a `Node` From c35f6ce0a1704bcf6c868dfac406878670d01ec1 Mon Sep 17 00:00:00 2001 From: Dmitriy Ryajov Date: Fri, 12 Jan 2018 14:34:30 -0600 Subject: [PATCH 85/85] fix: various fixes from @vmx review --- README.md | 24 ++++++++++++------------ src/in-proc-node.js | 2 +- src/index.js | 2 +- src/remote-node/client.js | 10 ++++++---- test/add-retrive.js | 9 --------- test/api.js | 4 ++-- test/remote/routes.js | 1 + 7 files changed, 23 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index cefa9c2f..a1987b16 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ server.start((err) => { - **`js`** - calling `DaemonFactory.create({type: 'js'})` will spawn a `js-ipfs` daemon. - **`proc`** - calling `DaemonFactory.create({type: 'proc', exec: require('ipfs') })` will spawn an `in process js-ipfs node` using the provided code reference that implements the core IPFS API. Note that, `exec` option to `df.spawn()` is required if `type: 'proc'` is used. -#### DaemonFactory endpoint for remote spawning - `const server = DaemonFactory.createServer([options]) ` +#### DaemonFactory endpoint for remote spawning - `const server = `DaemonFactory.createServer([options]) ` `DaemonFactory.createServer` create an instance of the bundled REST API used by the remote controller. @@ -162,7 +162,7 @@ The IPFS daemon controller (`ipfsd`) allows you to interact with the spawned IPF #### `ipfsd.apiAddr` (getter) -Get the address (multiaddr) of connected IPFS API. Returns a multiaddr, +Get the address (multiaddr) of connected IPFS API. Returns a multiaddr #### `ipfsd.gatewayAddr` (getter) @@ -170,7 +170,7 @@ Get the address (multiaddr) of connected IPFS HTTP Gateway. Returns a multiaddr. #### `ipfsd.repoPath` (getter) -Get the current repo path. Returns string +Get the current repo path. Returns string. #### `ipfsd.started` (getter) @@ -181,7 +181,7 @@ Is the node started. Returns a boolean. Initialize a repo. `initOpts` (optional) is an object with the following properties: - - `keysize` (default 2048) - The bit size of the identiy key. + - `keysize` (default 2048) - The bit size of the identity key. - `directory` (default IPFS_PATH if defined, or ~/.ipfs for go-ipfs and ~/.jsipfs for js-ipfs) - The location of the repo. `callback` is a function with the signature `function (Error, ipfsd)` where `err` is an Error in case something goes wrong and `ipfsd` is the daemon controller instance. @@ -198,16 +198,16 @@ Start the daemon. `flags` - Flags array to be passed to the `ipfs daemon` command. -`callback` is a function with the signature `function(err, ipfsApi)}` that receives an instance of `ipfs-api` on success or an instance of `Error` on failure +`callback` is a function with the signature `function(err, ipfsApi)` that receives an instance of `ipfs-api` on success or an instance of `Error` on failure -#### `ipfsd.stop(callback)` +#### `ipfsd.stop([callback])` Stop the daemon. `callback` is a function with the signature `function(err)` callback - function that receives an instance of `Error` on failure -#### `ipfsd.killProcess (callback)` +#### `ipfsd.killProcess([callback])` Kill the `ipfs daemon` process. @@ -215,19 +215,19 @@ First a `SIGTERM` is sent, after 10.5 seconds `SIGKILL` is sent if the process h `callback` is a function with the signature `function()` called once the process is killed -#### `ipfsd.pid ()` +#### `ipfsd.pid()` Get the pid of the `ipfs daemon` process. Returns the pid number -#### `ipfsd.getConfig(key, callback)` +#### `ipfsd.getConfig([key], callback)` Returns the output of an `ipfs config` command. If no `key` is passed, the whole config is returned as an object. `key` (optional) - A specific config to retrieve. -`callback` is a function with the signature `function(err, (Object|string)` that receives an object or string on success or an `Error` instance on failure +`callback` is a function with the signature `function(err, (Object|string))` that receives an object or string on success or an `Error` instance on failure -#### `ipfsd.setConfig (key, value, callback)` +#### `ipfsd.setConfig(key, value, callback)` Set a config value. @@ -251,7 +251,7 @@ This instance is returned for each successfully started IPFS daemon, when either ### Packaging -`ipfsd-ctl` can be packaged in Electron applications, but the ipfs binary has to be excluded from asar (Electron Archives), +`ipfsd-ctl` can be packaged in Electron applications, but the ipfs binary has to be excluded from asar (Electron Archives). [read more about unpack files from asar](https://electron.atom.io/docs/tutorial/application-packaging/#adding-unpacked-files-in-asar-archive). `ipfsd-ctl` will try to detect if used from within an `app.asar` archive and tries to resolve ipfs from `app.asar.unpacked`. The ipfs binary is part of the `go-ipfs-dep` module. diff --git a/src/in-proc-node.js b/src/in-proc-node.js index 3cf4767a..8ba6aa48 100644 --- a/src/in-proc-node.js +++ b/src/in-proc-node.js @@ -190,7 +190,7 @@ class Node { /** * Stop the daemon. * - * @param {function(Error)} callback + * @param {function(Error)} [callback] * @returns {undefined} */ stop (callback) { diff --git a/src/index.js b/src/index.js index 10d2898f..e5065509 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ class DaemonFactory { static create (opts) { const options = defaults({}, opts, { remote: !isNode }) - if (options.remote && options.type === 'proc') { + if (options.type === 'proc') { options.remote = false } diff --git a/src/remote-node/client.js b/src/remote-node/client.js index c807bfcd..fb56b260 100644 --- a/src/remote-node/client.js +++ b/src/remote-node/client.js @@ -148,10 +148,11 @@ class Node { /** * Stop the daemon. * - * @param {function(Error)} cb + * @param {function(Error)} [cb] * @returns {undefined} */ stop (cb) { + cb = cb || (() => {}) request .post(`${this.baseUrl}/stop`) .query({ id: this._id }) @@ -171,10 +172,11 @@ class Node { * First `SIGTERM` is sent, after 7.5 seconds `SIGKILL` is sent * if the process hasn't exited yet. * - * @param {function()} cb - Called when the process was killed. + * @param {function()} [cb] - Called when the process was killed. * @returns {undefined} */ killProcess (cb) { + cb = cb || (() => {}) request .post(`${this.baseUrl}/kill`) .query({ id: this._id }) @@ -219,11 +221,11 @@ class Node { getConfig (key, cb) { if (typeof key === 'function') { cb = key - key = null + key = undefined } const qr = { id: this._id } - qr.key = key || undefined + qr.key = key request .get(`${this.baseUrl}/config`) .query(qr) diff --git a/test/add-retrive.js b/test/add-retrive.js index bd44c903..36f1c84a 100644 --- a/test/add-retrive.js +++ b/test/add-retrive.js @@ -42,14 +42,5 @@ module.exports = () => { expect(this.ipfsd.api).to.have.property('apiHost') expect(this.ipfsd.api).to.have.property('apiPort') }) - - it('should be able to store objects', () => { - expect(store) - .to.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') - }) - - it('should be able to retrieve objects', () => { - expect(retrieve.toString()).to.equal('blorb') - }) }) } diff --git a/test/api.js b/test/api.js index 87f57a66..2f58318b 100644 --- a/test/api.js +++ b/test/api.js @@ -100,9 +100,9 @@ module.exports = (df, type) => { // Check for props in daemon expect(ipfsd).to.have.property('apiAddr') expect(ipfsd).to.have.property('gatewayAddr') - expect(ipfsd.apiAddr).to.not.equal(null) + expect(ipfsd.apiAddr).to.not.be.null() expect(multiaddr.isMultiaddr(ipfsd.apiAddr)).to.equal(true) - expect(ipfsd.gatewayAddr).to.not.equal(null) + expect(ipfsd.gatewayAddr).to.not.be.null() expect(multiaddr.isMultiaddr(ipfsd.gatewayAddr)).to.equal(true) // Check for props in ipfs-api instance diff --git a/test/remote/routes.js b/test/remote/routes.js index ab2a88f8..322a1672 100644 --- a/test/remote/routes.js +++ b/test/remote/routes.js @@ -43,6 +43,7 @@ const routes = proxyquire('../../src/remote-node/routes', { node.stop = (cb) => { node.killProcess(cb) } + node.killProcess = (cb) => { node.started = false cb()