From 7464300dca623dbabfc957d6d18f739f9bd4697d Mon Sep 17 00:00:00 2001 From: Yusef Napora Date: Thu, 12 Jan 2017 16:39:28 -0500 Subject: [PATCH] mcclient get/add/remove commands for manifests --- src/client/api/RestClient.js | 21 +++++++++++ src/client/cli/commands/config/manifest.js | 1 - src/client/cli/commands/manifest.js | 15 ++++++++ src/client/cli/commands/manifest/add.js | 44 ++++++++++++++++++++++ src/client/cli/commands/manifest/get.js | 14 +++++++ src/client/cli/commands/manifest/node.js | 14 +++++++ src/client/cli/commands/manifest/remove.js | 44 ++++++++++++++++++++++ src/common/util.js | 21 ++++++++++- 8 files changed, 171 insertions(+), 3 deletions(-) delete mode 100644 src/client/cli/commands/config/manifest.js create mode 100644 src/client/cli/commands/manifest.js create mode 100644 src/client/cli/commands/manifest/add.js create mode 100644 src/client/cli/commands/manifest/get.js create mode 100644 src/client/cli/commands/manifest/node.js create mode 100644 src/client/cli/commands/manifest/remove.js diff --git a/src/client/api/RestClient.js b/src/client/api/RestClient.js index 81452f1..6f35824 100644 --- a/src/client/api/RestClient.js +++ b/src/client/api/RestClient.js @@ -336,6 +336,27 @@ class RestClient { .then(r => r.json()) } + getNodeManifest (): Promise { + return this.getRequest('manifest/node') + .then(trimTextResponse) + } + + getManifests (peerId: ?string = null): Promise> { + let path = (peerId == null) + ? 'manifest' + : `manifest/${peerId}` + + return this.getRequest(path) + .then(r => new NDJsonResponse(r)) + .then(r => r.values()) + } + + setManifests (...manifests: Array): Promise { + const body = manifests.map(m => JSON.stringify(m)).join('\n') + return this.postRequest('manifest', body, false) + .then(parseBoolResponse) + } + shutdown (): Promise { return this.postRequest('shutdown', '', false) .then(() => true) diff --git a/src/client/cli/commands/config/manifest.js b/src/client/cli/commands/config/manifest.js deleted file mode 100644 index 46e7f7c..0000000 --- a/src/client/cli/commands/config/manifest.js +++ /dev/null @@ -1 +0,0 @@ -// @flow diff --git a/src/client/cli/commands/manifest.js b/src/client/cli/commands/manifest.js new file mode 100644 index 0000000..c5066d0 --- /dev/null +++ b/src/client/cli/commands/manifest.js @@ -0,0 +1,15 @@ +// @flow + +const path = require('path') + +module.exports = { + command: 'manifest ', + describe: 'Commands for setting and retrieving identity manifests. Use "manifest --help" to see subcommands.\n', + builder: (yargs: Function) => { + return yargs + .commandDir(path.join(__dirname, './manifest')) + .help() + .strict() + }, + handler: () => {} +} diff --git a/src/client/cli/commands/manifest/add.js b/src/client/cli/commands/manifest/add.js new file mode 100644 index 0000000..b9d2810 --- /dev/null +++ b/src/client/cli/commands/manifest/add.js @@ -0,0 +1,44 @@ +// @flow + +const fs = require('fs') +const _ = require('lodash') +const RestClient = require('../../../api/RestClient') +const { subcommand, println } = require('../../util') +const { consumeStream } = require('../../../../common/util') + +module.exports = { + command: 'add [filename]', + description: 'Add a signed manifest to the local node. ' + + 'If `filename` is not given, will read from standard input.\n', + handler: subcommand((opts: {client: RestClient, filename?: string}) => { + const {client, filename} = opts + let streamName = 'standard input' + let inputStream = process.stdin + if (filename != null) { + streamName = filename + inputStream = fs.createReadStream(filename) + } + + let manifest: Object + + return consumeStream(inputStream) + .catch(err => { + throw new Error(`Error reading from ${streamName}: ${err.message}`) + }) + .then(contents => { + manifest = JSON.parse(contents) + }) + .then(() => client.getManifests()) + .then(manifests => { + if (_.some(manifests, m => _.isEqual(m, manifest))) { + println('Node already contains manifest, ignoring') + return + } + manifests.push(manifest) + return client.setManifests(...manifests) + .then(() => { + println('Manifest added successfully') + }) + }) + }) +} diff --git a/src/client/cli/commands/manifest/get.js b/src/client/cli/commands/manifest/get.js new file mode 100644 index 0000000..3800bb7 --- /dev/null +++ b/src/client/cli/commands/manifest/get.js @@ -0,0 +1,14 @@ +// @flow + +const RestClient = require('../../../api/RestClient') +const { subcommand, printJSON } = require('../../util') + +module.exports = { + command: 'get [remotePeer]', + description: `Get the signed manifests for the local node or a remote peer.\n`, + handler: subcommand((opts: {client: RestClient, remotePeer?: string}) => { + const {client, remotePeer} = opts + return client.getManifests(remotePeer) + .then(m => printJSON(m)) + }) +} diff --git a/src/client/cli/commands/manifest/node.js b/src/client/cli/commands/manifest/node.js new file mode 100644 index 0000000..d7edfc3 --- /dev/null +++ b/src/client/cli/commands/manifest/node.js @@ -0,0 +1,14 @@ +// @flow + +const RestClient = require('../../../api/RestClient') +const { subcommand, println } = require('../../util') + +module.exports = { + command: 'node', + description: `Get the "node manifest" for the local node, suitable for signing by mcid to produce a manifest.\n`, + handler: subcommand((opts: {client: RestClient}) => { + const {client} = opts + return client.getNodeManifest() + .then(m => println(m)) + }) +} diff --git a/src/client/cli/commands/manifest/remove.js b/src/client/cli/commands/manifest/remove.js new file mode 100644 index 0000000..2a5ac1c --- /dev/null +++ b/src/client/cli/commands/manifest/remove.js @@ -0,0 +1,44 @@ +// @flow + +const fs = require('fs') +const _ = require('lodash') +const RestClient = require('../../../api/RestClient') +const { subcommand, println } = require('../../util') +const { consumeStream } = require('../../../../common/util') + +module.exports = { + command: 'remove [filename]', + description: 'Remove a signed manifest from the local node. ' + + 'If `filename` is not given, will read from standard input.\n', + handler: subcommand((opts: {client: RestClient, filename?: string}) => { + const {client, filename} = opts + let streamName = 'standard input' + let inputStream = process.stdin + if (filename != null) { + streamName = filename + inputStream = fs.createReadStream(filename) + } + + let manifest: Object + + return consumeStream(inputStream) + .catch(err => { + throw new Error(`Error reading from ${streamName}: ${err.message}`) + }) + .then(contents => { + manifest = JSON.parse(contents) + }) + .then(() => client.getManifests()) + .then(manifests => { + const without = _.filter(manifests, m => !_.isEqual(m, manifest)) + if (without.length === manifests.length) { + println('Node does not contain manifest, ignoring') + return + } + return client.setManifests(...without) + .then(() => { + println('Manifest removed successfully') + }) + }) + }) +} diff --git a/src/common/util.js b/src/common/util.js index 5dfac08..5fd4864 100644 --- a/src/common/util.js +++ b/src/common/util.js @@ -1,7 +1,7 @@ // @flow const Multihashing = require('multihashing') -import type { Writable } from 'stream' +import type { Writable, Readable } from 'stream' import type { WriteStream } from 'tty' /** @@ -101,6 +101,22 @@ function printlnErr (output: string) { writeln(output, process.stderr) } +/** + * Read a stream until it ends, returning its contents as a string. + * @param stream + * @returns {Promise} + */ +function consumeStream (stream: Readable): Promise { + return new Promise((resolve, reject) => { + const chunks = [] + stream.on('error', err => reject(err)) + stream.on('data', chunk => { chunks.push(chunk) }) + stream.on('end', () => { + resolve(Buffer.concat(chunks).toString('utf-8')) + }) + }) +} + module.exports = { promiseHash, promiseTimeout, @@ -109,5 +125,6 @@ module.exports = { isB58Multihash, writeln, println, - printlnErr + printlnErr, + consumeStream }