Skip to content

Add possibility to pass config options to local node and down to init() #109

New issue

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

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

Already on GitHub? Sign in to your account

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a12e8c4
Add possibility to pass config options to local node and down to init()
haadcode Sep 13, 2016
3280552
Fix hash check test
haadcode Nov 11, 2016
ab73fcf
fix(shutdown): fixed bugs in stopDaemon
jbenet Sep 16, 2016
b15aeb2
note(shutdown): comment on problematic func name
jbenet Sep 16, 2016
7a00f73
fix: rm unused var (thanks, linter)
jbenet Sep 16, 2016
5168be7
fix(startDaemon): fix the behavior of startDeamon
jbenet Sep 16, 2016
ef39988
feat(startDeamon): allow passing flags to ipfs daemon
jbenet Sep 16, 2016
99f6778
fix: make the linter happy for D#
jbenet Sep 16, 2016
b618b71
updates [email protected]
jbenet Sep 16, 2016
9c7b060
fix(tests): guarded func to avoid it being called twice
jbenet Sep 16, 2016
724bd73
Merge pull request #107 from ipfs/fixes/startDaemon
daviddias Sep 16, 2016
3ae9063
chore: update scripts
daviddias Sep 16, 2016
d65571d
chore: update contributors
daviddias Sep 16, 2016
aad1d0b
chore: release version v0.15.0
daviddias Sep 16, 2016
0e9cb98
feat: upgrade to go-ipfs 0.4.3
dignifiedquire Jun 2, 2016
a66d5f5
fix: ensure setting the config cbs only once
dignifiedquire Sep 29, 2016
a71bbb4
chore: use latest go-ipfs dep
dignifiedquire Sep 29, 2016
5277c5a
Merge pull request #81 from ipfs/upgrade
daviddias Sep 29, 2016
de1ec6c
chore: update contributors
daviddias Sep 29, 2016
a537fd5
update go-ipfs-dep to v0.4.3-2
edsilv Oct 23, 2016
ace94f5
update to v0.4.4
edsilv Oct 24, 2016
8996ef9
chore: release version v0.16.0
daviddias Sep 29, 2016
cb215cc
Merge pull request #112 from edsilv/master
daviddias Oct 29, 2016
bf7620a
chore: update contributors
daviddias Oct 29, 2016
f97df5c
chore: release version v0.17.0
daviddias Oct 29, 2016
1b36b09
chore(package): update aegir to version 9.1.1
greenkeeperio-bot Nov 4, 2016
b2fec54
Merge pull request #120 from ipfs/greenkeeper-aegir-9.1.1
daviddias Nov 10, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ipfsd-ctl",
"version": "0.14.0",
"version": "0.17.0",
"description": "simple controls for an ipfs node",
"main": "lib/index.js",
"jsxnext:main": "src/index.js",
Expand All @@ -10,8 +10,8 @@
"test": "aegir-test --env node",
"build": "aegir-build --env node",
"release": "aegir-release --env node",
"release:minor": "aegir-release --type minor --env node",
"release:major": "aegir-release --type major --env node",
"release-minor": "aegir-release --type minor --env node",
"release-major": "aegir-release --type major --env node",
"coverage-publish": "aegir-coverage publish"
},
"engines": {
Expand All @@ -31,30 +31,34 @@
"Bret Comnes <[email protected]>",
"David Dias <[email protected]>",
"FrauBienenstich <[email protected]>",
"Friedel Ziegelmayer <[email protected]>",
"Harlan T Wood <[email protected]>",
"Juan Benet <[email protected]>",
"Kristoffer Ström <[email protected]>",
"Lars-Magnus Skog <[email protected]>",
"Richard Littauer <[email protected]>",
"Stephen Whitmore <[email protected]>",
"dignifiedquire <dignifiedquire@gmail.com>",
"edsilv <e.silverton@gmail.com>",
"greenkeeperio-bot <[email protected]>",
"haad <[email protected]>",
"haadcode <[email protected]>"
"haadcode <[email protected]>",
"jbenet <[email protected]>"
],
"license": "MIT",
"dependencies": {
"go-ipfs-dep": "0.4.1",
"ipfs-api": "^4.1.0",
"multiaddr": "^2.0.0",
"rimraf": "^2.4.5",
"bl": "^1.1.2",
"go-ipfs-dep": "0.4.4",
"ipfs-api": "^9.0.0",
"multiaddr": "^2.0.3",
"once": "^1.4.0",
"rimraf": "^2.5.4",
"run-series": "^1.1.4",
"shutdown": "^0.2.4",
"subcomandante": "^1.0.5"
},
"devDependencies": {
"aegir": "^8.0.1",
"aegir": "^9.1.1",
"mkdirp": "^0.5.1",
"pre-commit": "^1.1.2"
"pre-commit": "^1.1.3"
},
"repository": {
"type": "git",
Expand Down
8 changes: 6 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ module.exports = {
version (done) {
(new Node()).version(done)
},
local (path, done) {
local (path, opts, done) {
if (typeof opts === 'function') {
done = opts
opts = {}
}
if (!done) {
done = path
path = process.env.IPFS_PATH ||
join(process.env.HOME ||
process.env.USERPROFILE, '.ipfs')
}
process.nextTick(() => {
done(null, new Node(path))
done(null, new Node(path, opts))
})
},
disposableApi (opts, done) {
Expand Down
175 changes: 126 additions & 49 deletions src/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ const rimraf = require('rimraf')
const shutdown = require('shutdown')
const path = require('path')
const join = path.join

const rootPath = process.env.testpath ? process.env.testpath : __dirname
const bl = require('bl')
const once = require('once')

const ipfsDefaultPath = findIpfsExecutable()

const GRACE_PERIOD = 7500 // amount of ms to wait before sigkill

function findIpfsExecutable () {
let npm3Path = path.join(rootPath, '../../', '/go-ipfs-dep/go-ipfs/ipfs')
const rootPath = process.env.testpath ? process.env.testpath : __dirname

let npm2Path = path.join(rootPath, '../', 'node_modules/go-ipfs-dep/go-ipfs/ipfs')
const appRoot = path.join(rootPath, '..')
const depPath = path.join('go-ipfs-dep', 'go-ipfs', 'ipfs')
const npm3Path = path.join(appRoot, '../', depPath)
const npm2Path = path.join(appRoot, 'node_modules', depPath)

try {
fs.statSync(npm3Path)
Expand All @@ -43,8 +46,9 @@ function configureNode (node, conf, done) {

// Consistent error handling
function parseConfig (path, done) {
const file = fs.readFileSync(join(path, 'config'))

try {
const file = fs.readFileSync(join(path, 'config'))
const parsed = JSON.parse(file)
done(null, parsed)
} catch (err) {
Expand All @@ -66,21 +70,22 @@ module.exports = class Node {
}

_run (args, envArg, done) {
let result = ''
run(this.exec, args, envArg)
.on('error', done)
.on('data', (data) => {
result += data
})
.on('end', () => done(null, result.trim()))
.pipe(bl((err, result) => {
if (err) {
return done(err)
}

done(null, result.toString().trim())
}))
}

init (initOpts, done) {
if (!done) {
done = initOpts
initOpts = {}
}
let buf = ''

const keySize = initOpts.keysize || 2048

Expand All @@ -91,24 +96,29 @@ module.exports = class Node {

run(this.exec, ['init', '-b', keySize], {env: this.env})
.on('error', done)
.on('data', (data) => {
buf += data
})
.on('end', () => {
.pipe(bl((err, buf) => {
if (err) return done(err)

configureNode(this, this.opts, (err) => {
if (err) return done(err)
if (err) {
return done(err)
}

this.clean = false
this.initialized = true

done(null, this)
})
})
}))

if (this.disposable) {
shutdown.addHandler('disposable', 1, this.shutdown.bind(this))
}
}

// cleanup tmp files
// TODO: this is a bad name for a function. a user may call this expecting
// something similar to "stopDaemon()". consider changing it. - @jbenet
shutdown (done) {
if (!this.clean && this.disposable) {
rimraf(this.path, (err) => {
Expand All @@ -118,51 +128,117 @@ module.exports = class Node {
}
}

startDaemon (done) {
parseConfig(this.path, (err, conf) => {
startDaemon (flags, done) {
if (typeof flags === 'function' && typeof done === 'undefined') {
done = flags
flags = []
}

const node = this
parseConfig(node.path, (err, conf) => {
if (err) return done(err)

this.subprocess = run(this.exec, ['daemon'], {env: this.env})
.on('error', (err) => {
if (String(err).match('daemon is running')) {
// we're good
done(null, ipfs(conf.Addresses.API))
} else if (String(err).match('non-zero exit code')) {
// ignore when kill -9'd
} else {
done(err)
}
})
.on('data', (data) => {
const match = String(data).trim().match(/API server listening on (.*)/)
if (match) {
this.apiAddr = match[1]
const addr = multiaddr(this.apiAddr).nodeAddress()
const api = ipfs(this.apiAddr)
api.apiHost = addr.address
api.apiPort = addr.port
done(null, api)
}
})
let stdout = ''
let args = ['daemon'].concat(flags || [])

// strategy:
// - run subprocess
// - listen for API addr on stdout (success)
// - or an early exit or error (failure)
node.subprocess = run(node.exec, args, {env: node.env})
node.subprocess.on('error', onErr)
.on('data', onData)

// done2 is called to call done after removing the event listeners
let done2 = (err, val) => {
node.subprocess.removeListener('data', onData)
node.subprocess.removeListener('error', onErr)
if (err) {
node.killProcess(() => {}) // we failed. kill, just to be sure...
}
done(err, val)
done2 = () => {} // in case it gets called twice
}

function onErr (err) {
if (String(err).match('daemon is running')) {
// we're good
done2(null, ipfs(conf.Addresses.API))

// TODO: I don't think this case is OK at all...
// When does the daemon outout "daemon is running" ?? seems old.
// Someone should check on this... - @jbenet
} else if (String(err).match('non-zero exit code')) {
// exited with an error on startup, before we removed listeners
done2(err)
} else {
done2(err)
}
}

function onData (data) {
data = String(data)
stdout += data

if (!data.trim().match(/Daemon is ready/)) {
return // not ready yet, keep waiting.
}

const apiM = stdout.match(/API server listening on (.*)\n/)
if (apiM) {
// found the API server listening. extract the addr.
node.apiAddr = apiM[1]
} else {
// daemon ready but no API server? seems wrong...
done2(new Error('daemon ready without api'))
}

const gatewayM = stdout.match(/Gateway \((readonly|writable)\) server listening on (.*)\n/)
if (gatewayM) {
// found the Gateway server listening. extract the addr.
node.gatewayAddr = gatewayM[1]
}

const addr = multiaddr(node.apiAddr).nodeAddress()
const api = ipfs(node.apiAddr)
api.apiHost = addr.address
api.apiPort = addr.port

// We are happyly listening, so let's not hide other errors
node.subprocess.removeListener('error', onErr)

done2(null, api)
}
})
}

stopDaemon (done) {
if (!done) done = () => {}
if (!this.subprocess) return done(null)
if (!done) {
done = () => {}
}

this.subprocess.kill('SIGTERM')
if (!this.subprocess) {
return done()
}

this.killProcess(done)
}

killProcess (done) {
// need a local var for the closure, as we clear the var.
const subprocess = this.subprocess
const timeout = setTimeout(() => {
this.subprocess.kill('SIGKILL')
done(null)
subprocess.kill('SIGKILL')
done()
}, GRACE_PERIOD)

this.subprocess.on('close', () => {
subprocess.on('close', () => {
clearTimeout(timeout)
done(null)
this.subprocess = null
done()
})

subprocess.kill('SIGTERM')
this.subprocess = null
}

Expand All @@ -180,9 +256,10 @@ module.exports = class Node {
}

setConfig (key, value, done) {
done = once(done)
run(this.exec, ['config', key, value, '--json'], {env: this.env})
.on('error', done)
.on('data', (data) => {})
.on('data', () => {})
.on('end', () => done())
}

Expand Down
1 change: 1 addition & 0 deletions test/fixtures/test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world
24 changes: 23 additions & 1 deletion test/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-env mocha */
/* eslint max-nested-callbacks: ["error", 8] */
'use strict'

const ipfsd = require('../src')
Expand Down Expand Up @@ -364,8 +365,29 @@ describe('ipfs-api version', function () {

const added = res[res.length - 1]
assert(added)
assert.equal(added.Hash, 'Qmafmh1Cw3H1bwdYpaaj5AbCW4LkYyUWaM7Nykpn5NZoYL')
assert.equal(added.Hash, 'Qmd4VbGayymErsAE1E6FYD4SnDgbL8z3P2h9jRQCaBaDSV')
done()
})
})
})

describe('node startDaemon', () => {
it('allows passing flags', (done) => {
ipfsd.disposable((err, node) => {
if (err) throw err
node.startDaemon(['--should-not-exist'], (err, ignore) => {
if (!err) {
throw new Error('should have errored')
}

let errStr = 'Unrecognized option \'should-not-exist\''

if (String(err).indexOf(errStr) >= 0) {
done() // correct error
}

throw err
})
})
})
})