Skip to content
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

mcclient manifest commands #158

Merged
merged 5 commits into from
Jan 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions src/client/api/RestClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,27 @@ class RestClient {
.then(r => r.json())
}

getSelfManifest (): Promise<string> {
return this.getRequest('manifest/self')
.then(trimTextResponse)
}

getManifests (peerId: ?string = null): Promise<Array<Object>> {
let path = (peerId == null)
? 'manifest'
: `manifest/${peerId}`

return this.getRequest(path)
.then(r => new NDJsonResponse(r))
.then(r => r.values())
}

setManifests (...manifests: Array<Object>): Promise<boolean> {
const body = manifests.map(m => JSON.stringify(m)).join('\n')
return this.postRequest('manifest', body, false)
.then(parseBoolResponse)
}

shutdown (): Promise<boolean> {
return this.postRequest('shutdown', '', false)
.then(() => true)
Expand Down
15 changes: 15 additions & 0 deletions src/client/cli/commands/manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @flow

const path = require('path')

module.exports = {
command: 'manifest <subcommand>',
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: () => {}
}
44 changes: 44 additions & 0 deletions src/client/cli/commands/manifest/add.js
Original file line number Diff line number Diff line change
@@ -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')
})
})
})
}
14 changes: 14 additions & 0 deletions src/client/cli/commands/manifest/get.js
Original file line number Diff line number Diff line change
@@ -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))
})
}
44 changes: 44 additions & 0 deletions src/client/cli/commands/manifest/remove.js
Original file line number Diff line number Diff line change
@@ -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')
})
})
})
}
15 changes: 15 additions & 0 deletions src/client/cli/commands/manifest/self.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @flow

const RestClient = require('../../../api/RestClient')
const { subcommand, println } = require('../../util')

module.exports = {
command: 'self',
description: `Get the unsigned "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.getSelfManifest()
.then(m => println(m))
})
}
54 changes: 54 additions & 0 deletions src/client/cli/commands/manifest/set.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// @flow

const fs = require('fs')
const RestClient = require('../../../api/RestClient')
const { subcommand, println } = require('../../util')
const { consumeStream } = require('../../../../common/util')

module.exports = {
command: 'set [filename]',
description: `Set the signed manifests for the local node, replacing any existing manifests. ` +
'If `filename is not given, will read from stdin`\n',
builder: {
ndjson: {
type: 'boolean',
description: 'If present, input should be newline-delimited json, one object per line. ' +
'Otherwise, input can be either a single json object, or an array of objects.',
default: 'false'
}
},
handler: subcommand((opts: {client: RestClient, filename?: string, ndjson: boolean}) => {
const {client, filename, ndjson} = opts
let streamName = 'standard input'
let inputStream = process.stdin
if (filename != null) {
streamName = filename
inputStream = fs.createReadStream(filename)
}

return consumeStream(inputStream)
.catch(err => {
throw new Error(`Error reading from ${streamName}: ${err.message}`)
})
.then(contents => {
let manifests = []
if (ndjson) {
manifests = contents.split('\n')
.filter(line => line && line.length > 0)
.map(line => JSON.parse(line))
} else {
const parsed = JSON.parse(contents)
if (Array.isArray(parsed)) {
manifests = parsed
} else {
manifests = [parsed]
}
}
return manifests
})
.then(manifests => client.setManifests(...manifests))
.then(() => {
println('Manifests set successfully')
})
})
}
21 changes: 19 additions & 2 deletions src/common/util.js
Original file line number Diff line number Diff line change
@@ -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'

/**
Expand Down Expand Up @@ -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<string> {
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,
Expand All @@ -109,5 +125,6 @@ module.exports = {
isB58Multihash,
writeln,
println,
printlnErr
printlnErr,
consumeStream
}
17 changes: 15 additions & 2 deletions src/protobuf/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ import type {
EnvelopeStatementMsg,
ArchiveStatementMsg,
StatementBodyMsg,
StatementMsg
StatementMsg,
ManifestMsg,
ManifestBodyMsg,
NodeManifestMsg
} from './types'

type AllProtos = {
Expand Down Expand Up @@ -82,6 +85,11 @@ type AllProtos = {
ArchiveStatement: ProtoCodec<ArchiveStatementMsg>,
StatementBody: ProtoCodec<StatementBodyMsg>,
Statement: ProtoCodec<StatementMsg>
},
manifest: {
Manifest: ProtoCodec<ManifestMsg>,
ManifestBody: ProtoCodec<ManifestBodyMsg>,
NodeManifest: ProtoCodec<NodeManifestMsg>
}
}

Expand All @@ -90,7 +98,7 @@ type AllProtos = {
// we're concatenating all of our protos into one big string
// Stop doing this when import works!
function loadProtos (): AllProtos {
const files = ['dir.proto', 'node.proto', 'stmt.proto']
const files = ['dir.proto', 'node.proto', 'stmt.proto', 'manifest.proto']

// parsing will fail if there's a `syntax` statement that's not the first line of the file
// so we remove them from the concatenated monster and add back as the first line
Expand Down Expand Up @@ -144,6 +152,11 @@ function loadProtos (): AllProtos {
ArchiveStatement: pb.ArchiveStatement,
StatementBody: pb.StatementBody,
Statement: pb.Statement
},
manifest: {
Manifest: pb.Manifest,
ManifestBody: pb.ManifestBody,
NodeManifest: pb.NodeManifest
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/protobuf/manifest.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
syntax = "proto3";
package proto;

message Manifest {
string entity = 1;
string keyId = 2;
ManifestBody body = 3;
int64 timestamp = 4;
bytes signature = 5;
}

message ManifestBody {
oneof body {
NodeManifest node = 1;
}
}

message NodeManifest {
string peer = 1;
string publisher = 2;
}
23 changes: 23 additions & 0 deletions src/protobuf/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,27 @@ export type StatementMsg = {
signature: Buffer,
};

// manifest.proto
// message Manifest {
// string entity = 1;
// string keyId = 2;
// ManifestBody body = 3;
// int64 timestamp = 4;
// bytes signature = 5;
// }
export type ManifestMsg = {
entity: string,
keyId: string,
body: ManifestBodyMsg,
timestamp: number,
signature: Buffer
}

export type ManifestBodyMsg = {node: NodeManifestMsg}

export type NodeManifestMsg = {
peer: string,
publisher: string
}

export type ProtoCodec<T> = { encode: (obj: T) => Buffer, decode: (buf: Buffer) => T }